/*
 * Aethis brand theme overrides for the licensing portal.
 * Palette + Firmament typeface sourced from aethis.agastyajindal.com.
 * Loaded after project.css so it wins specificity-wise.
 */

@font-face {
  font-family: "Firmament";
  src: url("../fonts/firmament/Firmament-Regular.175c31383ec4.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Firmament";
  src: url("../fonts/firmament/Firmament-Bold.8da24e51a236.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --aethis-ink:        #1a1518;  /* deep brand black */
  --aethis-ink-soft:   #2a2226;  /* secondary dark surface */
  --aethis-ink-muted:  #a08890;  /* muted mauve, body text on dark */

  --aethis-pink:       #e8a0a8;  /* primary brand pink */
  --aethis-pink-soft:  #f0c8d0;  /* light blush */
  --aethis-pink-deep:  #c87a86;  /* darker pink for hover/active */
  --aethis-lilac:      #c0a8d0;  /* secondary accent */
  --aethis-purple:     #a855f7;  /* accent purple */

  --aethis-bg:         #fdf8f9;  /* page wash */
  --aethis-surface:    #ffffff;
  --aethis-border:     #f0d7dc;  /* soft pink border */

  /* Override Bootstrap / Tabler primary so any built-in `btn-primary`,
     `text-primary`, `bg-primary`, etc. picks up the brand pink. */
  --bs-primary:        #e8a0a8;
  --bs-primary-rgb:    232, 160, 168;
  --bs-link-color:     #c87a86;
  --bs-link-hover-color: #1a1518;
  --bs-body-color:     #1a1518;
  --bs-body-bg:        #fdf8f9;
  --bs-border-color:   #f0d7dc;
}

/* Body + headings ----------------------------------------------------- */
body {
  background: linear-gradient(180deg, #fdf8f9 0%, #fff 280px) no-repeat;
  color: var(--aethis-ink);
  font-feature-settings: "ss01", "cv11";
}
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6,
.page-title, .card-title, .navbar-brand {
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  color: var(--aethis-ink);
  letter-spacing: -0.005em;
  font-weight: 700;
}

/* Brand wordmark uses Firmament Regular at large weight for a closer match
   to the marketing site rather than the heavier 700. */
.aethis-wordmark,
.aethis-wordmark span,
.container-tight .fs-1 {
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1;
}

.aethis-wordmark {
  font-size: 2rem;        /* navbar wordmark */
}
@media (min-width: 768px) {
  .aethis-wordmark { font-size: 2.4rem; }
}

/* Pull the navbar a touch taller so the larger wordmark breathes. */
.navbar .container-xl {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

/* Brand wordmark ------------------------------------------------------ */
.navbar-brand a .text-primary {
  color: var(--aethis-pink-deep) !important;
}
.navbar-brand a .text-dark {
  color: var(--aethis-ink) !important;
}

/* Header -------------------------------------------------------------- */
.navbar {
  background-color: rgba(255, 255, 255, 0.85);
  backdrop-filter: saturate(160%) blur(10px);
  -webkit-backdrop-filter: saturate(160%) blur(10px);
  border-bottom: 1px solid var(--aethis-border);
  /* backdrop-filter creates a stacking context; without an explicit z-index the
     header sits below later page content (e.g. the dashboard "Signed in as"
     card), which clips the account dropdown. Lift it above the page. */
  position: relative;
  z-index: 1030;
}
/* Ensure the account/profile dropdown always renders above page content. */
.navbar .dropdown-menu {
  z-index: 1031;
}
.navbar .nav-link {
  color: var(--aethis-ink);
  font-weight: 500;
}
.navbar .nav-link:hover,
.navbar .nav-link.active {
  color: var(--aethis-pink-deep);
}

/* Profile avatar circle ---------------------------------------------- */
.navbar .avatar {
  background: linear-gradient(135deg, var(--aethis-pink) 0%, var(--aethis-lilac) 100%) !important;
  color: #fff !important;
  width: 2rem;
  height: 2rem;
  line-height: 2rem;
  font-size: 0.85rem;
  box-shadow: 0 2px 6px rgba(232, 160, 168, 0.4);
}

/* Buttons ------------------------------------------------------------- */
.btn-primary {
  background-color: var(--aethis-pink);
  border-color: var(--aethis-pink);
  color: #fff;
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(26, 21, 24, 0.08);
}
.btn-primary:hover,
.btn-primary:focus,
.btn-primary:active,
.btn-primary.active {
  background-color: var(--aethis-pink-deep) !important;
  border-color: var(--aethis-pink-deep) !important;
  color: #fff !important;
}
.btn-outline-primary {
  border-color: var(--aethis-pink);
  color: var(--aethis-pink-deep);
}
.btn-outline-primary:hover {
  background-color: var(--aethis-pink);
  border-color: var(--aethis-pink);
  color: #fff;
}
.btn-outline-secondary {
  border-color: var(--aethis-border);
  color: var(--aethis-ink);
}
.btn-outline-secondary:hover {
  background-color: var(--aethis-ink);
  border-color: var(--aethis-ink);
  color: #fff;
}

/* Cards --------------------------------------------------------------- */
.card {
  border: 1px solid var(--aethis-border);
  border-radius: 14px;
  box-shadow: 0 1px 2px rgba(26, 21, 24, 0.04);
  background: var(--aethis-surface);
}
.card-header {
  background: transparent;
  border-bottom: 1px solid var(--aethis-border);
}
.card-link:hover {
  border-color: var(--aethis-pink);
  box-shadow: 0 8px 24px rgba(232, 160, 168, 0.18);
  transform: translateY(-1px);
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}

/* Tables -------------------------------------------------------------- */
.table > thead {
  background-color: #fbf3f5;
}
.table > thead > tr > th {
  color: var(--aethis-ink);
  border-bottom: 1px solid var(--aethis-border);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  font-size: 0.78rem;
}
.table > tbody > tr {
  border-color: #fce9ec;
}
.table > tbody > tr:hover > * {
  background-color: #fdf3f5;
}
code {
  color: var(--aethis-pink-deep);
  background: #fbf0f3;
  padding: 0.1em 0.35em;
  border-radius: 4px;
  font-size: 0.85em;
}

/* Sidebar (admin & end-user) ----------------------------------------- */
.list-group-item {
  border-color: var(--aethis-border);
  color: var(--aethis-ink);
}
.list-group-item.active,
.list-group-item-action.active {
  background-color: var(--aethis-pink);
  border-color: var(--aethis-pink);
  color: #fff;
  font-weight: 600;
}
.list-group-item-action:hover:not(.active) {
  background-color: #fbf0f3;
  color: var(--aethis-pink-deep);
}

/* Badges -------------------------------------------------------------- */
.badge.bg-secondary-lt {
  background-color: #fbf0f3 !important;
  color: var(--aethis-pink-deep) !important;
}
.badge.bg-success-lt {
  background-color: #e6f6ec !important;
  color: #1f7a3d !important;
}
.badge.bg-danger-lt {
  background-color: #fbe6e9 !important;
  color: #9c2a3a !important;
}

/* Forms --------------------------------------------------------------- */
.form-control,
.form-select {
  border-color: var(--aethis-border);
  background-color: #fff;
}
.form-control:focus,
.form-select:focus {
  border-color: var(--aethis-pink);
  box-shadow: 0 0 0 3px rgba(232, 160, 168, 0.25);
}
.form-label,
.col-form-label {
  color: var(--aethis-ink);
  font-weight: 500;
}

/* Login / signup card glow ------------------------------------------- */
.account .container-tight .card,
.container-tight > .card {
  border-radius: 18px;
  box-shadow: 0 24px 60px -28px rgba(232, 160, 168, 0.45),
              0 2px 6px rgba(26, 21, 24, 0.05);
}
.container-tight .card-footer {
  background: transparent;
  border-top: 1px solid var(--aethis-border);
}

/* Footer (ported from aethisdsp.com homepage) ------------------------- */
.aethis-footer {
  background: #F7F4EF;
  border-top: 1px solid rgba(0, 0, 0, 0.1);
  padding: 2.5rem 1.5rem;
}
.aethis-footer__inner {
  max-width: 72rem;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}
@media (min-width: 768px) {
  .aethis-footer__inner {
    flex-direction: row;
  }
}
.aethis-footer__brand {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.aethis-footer__logo {
  height: 2rem;
  width: auto;
  opacity: 0.7;
}
.aethis-footer__sep {
  color: rgba(0, 0, 0, 0.15);
  user-select: none;
}
.aethis-footer__copy {
  color: rgba(0, 0, 0, 0.4);
  font-size: 0.75rem;
}
.aethis-footer__links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 2rem;
  color: rgba(0, 0, 0, 0.4);
  font-size: 0.75rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  letter-spacing: 0.05em;
}
.aethis-footer__links a {
  color: inherit;
  text-decoration: none;
  transition: color 0.15s ease;
}
.aethis-footer__links a:hover {
  color: rgba(0, 0, 0, 0.8);
}

/* Alerts -------------------------------------------------------------- */
.alert-warning {
  background-color: #fff5dc;
  border-color: #f5d977;
  color: #6b4a00;
}
.alert-info {
  background-color: #fbf0f3;
  border-color: var(--aethis-border);
  color: var(--aethis-pink-deep);
}

/* Subtle gradient hairline above page on auth screens ---------------- */
.container-tight {
  position: relative;
}
.container-tight::before {
  content: "";
  position: absolute;
  inset: -120px -40px auto -40px;
  height: 240px;
  background: radial-gradient(ellipse at top, rgba(232, 160, 168, 0.35) 0%, transparent 60%);
  filter: blur(40px);
  z-index: -1;
  pointer-events: none;
}

/* Dropdown menu ------------------------------------------------------- */
.dropdown-menu {
  border: 1px solid var(--aethis-border);
  border-radius: 12px;
  box-shadow: 0 12px 32px rgba(26, 21, 24, 0.1);
  padding: 0.35rem;
}
.dropdown-item {
  border-radius: 8px;
  padding: 0.45rem 0.75rem;
  color: var(--aethis-ink);
}
.dropdown-item:hover,
.dropdown-item:focus {
  background-color: #fbf0f3;
  color: var(--aethis-pink-deep);
}
.dropdown-divider {
  border-color: var(--aethis-border);
  margin: 0.35rem 0;
}

/* Pagination --------------------------------------------------------- */
.page-link {
  color: var(--aethis-pink-deep);
  border-color: var(--aethis-border);
}
.page-item.active .page-link {
  background-color: var(--aethis-pink);
  border-color: var(--aethis-pink);
  color: #fff;
}
.page-item.disabled .page-link {
  color: var(--aethis-ink-muted);
  background-color: transparent;
}

/* Links --------------------------------------------------------------- */
a {
  color: var(--aethis-pink-deep);
  text-decoration: none;
}
a:hover {
  color: var(--aethis-ink);
}

/* Focus states ------------------------------------------------------- */
.btn:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible {
  outline: 2px solid var(--aethis-pink);
  outline-offset: 2px;
}

/* ====================================================================== *
 * Auth split-screen (login / signup) — ported from the marketing site's
 * LoginForm.tsx. Uses the website's exact auth tokens, scoped to .auth-page
 * so the rest of the dashboard theme is unaffected.
 * ====================================================================== */
:root {
  --auth-ink:   #0a0a0a;  /* website black */
  --auth-cream: #F7F4EF;  /* website page bg */
  --auth-pink:  #c4819a;  /* glow */
  --auth-blush: #f0c8d0;  /* accent */
}

body.auth-page {
  margin: 0;
  background: var(--auth-cream);
}

.auth-split {
  display: flex;
  min-height: 100vh;
  background: var(--auth-cream);
}

/* Left — brand panel (desktop only) --------------------------------- */
.auth-brand {
  position: relative;
  width: 44%;
  flex-shrink: 0;
  overflow: hidden;
  background: var(--auth-ink);
  color: #fff;
}
@media (max-width: 991.98px) {
  .auth-brand { display: none; }
}
@media (min-width: 1280px) {
  .auth-brand { width: 40%; }
}
.auth-brand__dots {
  position: absolute;
  inset: 0;
  opacity: 0.05;
  pointer-events: none;
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.55) 1px, transparent 1px);
  background-size: 24px 24px;
}
.auth-brand__glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(140px);
  pointer-events: none;
}
.auth-brand__glow--1 {
  top: -8rem; right: -8rem;
  width: 480px; height: 480px;
  background: var(--auth-pink);
  opacity: 0.18;
}
.auth-brand__glow--2 {
  bottom: -10rem; left: -8rem;
  width: 420px; height: 420px;
  background: var(--auth-blush);
  opacity: 0.08;
}
.auth-brand__wave {
  position: absolute;
  left: 0; right: 0; top: 50%;
  transform: translateY(-50%);
  width: 100%;
  opacity: 0.10;
  pointer-events: none;
}
.auth-brand__inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 100%;
  min-height: 100vh;
  padding: 3rem;
  box-sizing: border-box;
}
.auth-wordmark {
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 400;
  letter-spacing: 0.2em;
  text-decoration: none;
  color: #fff;
  font-size: 1.5rem;
}
.auth-backlink {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 1.5rem;
  padding: 0.5rem 1rem;
  border-radius: 9999px;
  border: 1px solid rgba(255, 255, 255, 0.15);
  background: rgba(255, 255, 255, 0.04);
  color: rgba(255, 255, 255, 0.75);
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  text-decoration: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.auth-backlink:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.3);
  color: #fff;
}
.auth-brand__eyebrow {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1.75rem;
}
.auth-brand__eyebrow .dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--auth-blush);
}
.auth-brand__eyebrow span {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.3em;
  color: rgba(255, 255, 255, 0.45);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.auth-brand__headline {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 2.6rem;
  line-height: 1.05;
  letter-spacing: -0.02em;
  font-weight: 500;
  margin: 0;
  color: #fff;
}
@media (min-width: 1280px) {
  .auth-brand__headline { font-size: 3rem; }
}
.auth-brand__headline .muted { color: rgba(255, 255, 255, 0.55); }
.auth-brand__sub {
  margin-top: 1.5rem;
  max-width: 28rem;
  font-size: 0.875rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.45);
}
.auth-brand__foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
}
.auth-brand__copy {
  font-size: 10px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.auth-eq {
  display: flex;
  align-items: flex-end;
  gap: 3px;
}
.auth-eq span {
  width: 2px;
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.4);
}

/* Right — form column ------------------------------------------------ */
.auth-main {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4rem 1.5rem;
}
.auth-card {
  width: 100%;
  max-width: 24rem;
}
.auth-wordmark--mobile {
  display: none;
  justify-content: center;
  margin-bottom: 3rem;
  color: var(--auth-ink);
}
.auth-wordmark--mobile .auth-wordmark { color: var(--auth-ink); }
@media (max-width: 991.98px) {
  .auth-wordmark--mobile { display: flex; }
}
.auth-heading {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 28px;
  line-height: 1.1;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--auth-ink);
  margin: 0;
}
.auth-subhead {
  font-size: 0.875rem;
  color: rgba(0, 0, 0, 0.45);
  margin: 0.5rem 0 0;
}
.auth-headwrap { margin-bottom: 2.5rem; }

/* Tabs */
.auth-tabs {
  display: flex;
  gap: 1.75rem;
  margin-bottom: 2rem;
  border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
.auth-tab {
  position: relative;
  margin-bottom: -1px;
  padding-bottom: 0.75rem;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: rgba(0, 0, 0, 0.35);
  text-decoration: none;
  transition: color 0.15s ease;
}
.auth-tab:hover { color: rgba(0, 0, 0, 0.65); }
.auth-tab--active { color: var(--auth-ink); }
.auth-tab--active::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: var(--auth-ink);
}

/* Fields */
.auth-form { display: flex; flex-direction: column; gap: 1rem; }
.auth-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
}
.auth-group { display: flex; flex-direction: column; }
.auth-label {
  display: block;
  margin-bottom: 0.5rem;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: rgba(0, 0, 0, 0.45);
}
.auth-field {
  width: 100%;
  box-sizing: border-box;
  background: #fff;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 12px;
  padding: 0.75rem 1rem;
  font-size: 15px;
  color: var(--auth-ink);
  transition: border-color 0.15s ease;
}
.auth-field::placeholder { color: rgba(0, 0, 0, 0.25); }
.auth-field:focus {
  outline: none;
  border-color: rgba(0, 0, 0, 0.4);
  box-shadow: none;
}
.auth-field--pw { padding-right: 2.75rem; }

/* Password show/hide */
.auth-pw { position: relative; }
.auth-pw__toggle {
  position: absolute;
  right: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  padding: 0;
  background: none;
  border: none;
  color: rgba(0, 0, 0, 0.35);
  cursor: pointer;
  transition: color 0.15s ease;
}
.auth-pw__toggle:hover { color: var(--auth-ink); }
.auth-pw__toggle .icon-eye-off { display: none; }
.auth-pw--shown .auth-pw__toggle .icon-eye { display: none; }
.auth-pw--shown .auth-pw__toggle .icon-eye-off { display: inline-flex; }

.auth-pwlabel {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
}
.auth-pwlabel .auth-label { margin-bottom: 0; }
.auth-forgot {
  font-size: 11px;
  color: rgba(0, 0, 0, 0.4);
  text-decoration: none;
  text-underline-offset: 2px;
}
.auth-forgot:hover { color: var(--auth-ink); text-decoration: underline; }

/* Submit pill */
.auth-btn {
  width: 100%;
  margin-top: 1rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.85rem 1rem;
  border: none;
  border-radius: 9999px;
  font-size: 14px;
  font-weight: 500;
  color: #fff;
  background: var(--auth-ink);
  cursor: pointer;
  transition: background 0.15s ease, transform 0.15s ease;
}
.auth-btn:hover { background: rgba(0, 0, 0, 0.85); color: #fff; }
.auth-btn svg { transition: transform 0.15s ease; }
.auth-btn:hover svg { transform: translateX(2px); }

/* Flash chips */
.auth-flash {
  padding: 0.75rem 1rem;
  border-radius: 12px;
  font-size: 14px;
}
.auth-flash + .auth-flash { margin-top: 0.5rem; }
.auth-flash--error {
  color: #b91c1c;
  background: #fef2f2;
  border: 1px solid rgba(252, 165, 165, 0.6);
}
.auth-flash--success {
  color: #047857;
  background: #ecfdf5;
  border: 1px solid rgba(110, 231, 183, 0.6);
}
.auth-fielderror {
  margin-top: 0.35rem;
  font-size: 12px;
  color: #b91c1c;
}

/* Consent row: the Terms tick on sign-up.
   ------------------------------------------------------------------------
   A real <input type="checkbox"> rather than a drawn one, because this is the
   control a customer is agreeing with: it has to be reachable by keyboard, be
   announced as a checkbox, and be found by the browser's own form restore
   after a failed submit. `accent-color` tints the native box with the brand
   accent and costs none of that.

   Deep rose on the links, not the pale blush: this card sits on cream, where
   #f0c8d0 all but disappears. */
.auth-consent {
  margin-top: 1.5rem;
}
.auth-consent__row {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  cursor: pointer;
}
.auth-consent__box {
  flex: none;
  width: 1rem;
  height: 1rem;
  /* Nudged down to sit on the first line's cap height rather than its box. */
  margin-top: 0.125rem;
  accent-color: #b06a78;
  cursor: pointer;
}
.auth-consent__text {
  font-size: 12px;
  line-height: 1.5;
  color: rgba(0, 0, 0, 0.55);
}
.auth-consent__text a {
  color: #b06a78;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.auth-consent__text a:hover { color: var(--auth-ink); }
.auth-consent .auth-fielderror { margin-left: 1.625rem; }

/* Bottom helper */
.auth-helper {
  margin-top: 2.5rem;
  text-align: center;
  font-size: 12px;
  color: rgba(0, 0, 0, 0.35);
}
.auth-helper a {
  color: rgba(0, 0, 0, 0.75);
  text-decoration: none;
  text-underline-offset: 2px;
}
.auth-helper a:hover { color: var(--auth-ink); text-decoration: underline; }
/* The signup confirmation code: six slots, ONE input.
   ------------------------------------------------------------------------
   Six separate <input>s are the usual way to get this look and they are a
   trap — the browser drops a pasted code entirely into the first box, and
   password managers, autofill and screen readers all have to be taught the
   group is one value. So there is one field, and six boxes drawn behind it
   with the digits mirrored into them by auth.js.

   This replaces a version that spaced one line of monospace text with
   letter-spacing and painted the underlines as a repeating gradient on the
   same rhythm. The arithmetic was right and it drifted anyway: `ch` is the
   advance of "0", which the fallback faces do not all share with every digit,
   and letter-spacing is rounded to whole device pixels per character, so the
   error compounds across six of them. Sub-pixel drift is invisible on the
   first slot and obvious by the sixth. A digit that has a box of its own
   cannot come apart from that box, so there is no longer a sum to get wrong.

   Progressive enhancement: auth.js builds the slots and sets
   .auth-otp--enhanced. Until then — and forever, with no script — the plain
   field below stays visible and submits exactly as it did. */
.auth-otp {
  display: flex;
  justify-content: center;
}
/* The bare field, which is what a scriptless browser keeps. Wide spacing and
   a monospace face so it still reads as a code rather than a text box. */
.auth-field--code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Courier New", monospace;
  font-size: 22px;
  letter-spacing: 0.35em;
  /* Compensates for the trailing letter-space after the last digit, which
     would otherwise push the run left of centre by half a space. */
  text-indent: 0.35em;
  text-align: center;
  width: 100%;
  max-width: 17rem;
}

/* --- enhanced: the input goes transparent and covers the slots ------------
   Kept in the layout (opacity, not display or visibility) so it stays
   focusable, still receives the paste, still autofills from the one-time-code
   hint, and is still what a screen reader announces. It covers the full
   wrapper so a tap anywhere on the slots lands on the field. */
.auth-otp--enhanced {
  /* Only adds the containing block for the field below. The display stays
     flex, from .auth-otp: switching it to inline-block would drop the
     centring, and the confirm page — where the field has the card to itself
     with no button beside it — would silently go left-aligned. */
  position: relative;
}
.auth-otp--enhanced .auth-field--code {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* .auth-field--code caps its visible self at 17rem. Left in place that cap
     applies to the invisible field too, which is anchored left — so on the
     confirm page the tappable area would stop short of the centred slots and
     the last of them would not respond to a tap. */
  max-width: none;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  z-index: 1;
  cursor: text;
}
.auth-otp--enhanced .auth-field--code:focus {
  border: 0;
  box-shadow: none;
}
.auth-otp__slots {
  display: flex;
  gap: 0.4rem;
  /* Decoration only — every click belongs to the field on top. */
  pointer-events: none;
}
.auth-otp__slot {
  width: 2.4rem;
  height: 3rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Courier New", monospace;
  font-size: 22px;
  line-height: 1;
  color: var(--auth-ink);
  border-bottom: 2px solid rgba(0, 0, 0, 0.18);
  transition: border-color 0.12s ease;
}
.auth-otp__slot--filled { border-bottom-color: var(--auth-ink); }
.auth-otp--enhanced:focus-within .auth-otp__slot--active {
  border-bottom-color: rgba(0, 0, 0, 0.5);
}
/* Stands in for the real caret, which went invisible with the input. Only
   while focused, and only on the slot the next digit will land in. */
.auth-otp--enhanced:focus-within .auth-otp__slot--active::after {
  content: "";
  width: 1px;
  height: 1.4rem;
  background: var(--auth-ink);
  animation: auth-otp-caret 1.06s steps(1) infinite;
}
@keyframes auth-otp-caret {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .auth-otp--enhanced:focus-within .auth-otp__slot--active::after { animation: none; }
  .auth-otp__slot { transition: none; }
}
/* Six slots and the button beside them is the widest thing on the card. On a
   small phone the button has already wrapped below (see .auth-codeline); this
   is so the slots themselves still fit the card rather than running past it. */
@media (max-width: 400px) {
  .auth-otp__slots { gap: 0.3rem; }
  .auth-otp__slot {
    width: 2rem;
    height: 2.6rem;
    font-size: 19px;
  }
}
.auth-hint {
  margin-top: 0.75rem;
  font-size: 12px;
  color: rgba(0, 0, 0, 0.4);
  text-align: center;
}

/* Code field and its Send code button on one line. Wraps rather than squeezes:
   the six slots have a fixed width by construction, so on a narrow card the
   button drops beneath them instead of crushing the digits out of alignment. */
.auth-codeline {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.auth-codeline .auth-otp { justify-content: flex-start; }

/* The verify-your-email panel.
   ------------------------------------------------------------------------
   A boxed step rather than another field in the stack, because it is one: the
   code has to be sent, waited for, and typed, and its Send code button spends
   a real message. Everything above the box is typing you can do in any order;
   inside it, the order matters. A hairline and a tint are enough to say that —
   a heavier card would read as a second form.

   The tint is the same blush the accent comes from, at a fraction of a
   percent, so it separates from the cream card without introducing a colour. */
.auth-verify {
  margin-top: 1.5rem;
  padding: 1.125rem 1.125rem 1.25rem;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 10px;
  background: rgba(176, 106, 120, 0.03);
}
.auth-verify__head {
  margin-bottom: 0.875rem;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(0, 0, 0, 0.45);
}
/* The group is the panel's last child; its own bottom margin would show as
   dead space inside the border. Same for the Turnstile when it renders. */
.auth-verify .auth-group:last-child { margin-bottom: 0; }
.auth-verify .auth-turnstile:not(:empty) {
  margin-top: 1rem;
  margin-bottom: 0;
}

/* Send code is secondary — it must not compete with Sign up, which is the
   button that actually finishes the job. */
.auth-btn--ghost {
  width: auto;
  flex: 0 0 auto;
  padding: 0.6rem 1rem;
  font-size: 13px;
  background: transparent;
  color: var(--auth-ink);
  border: 1px solid rgba(0, 0, 0, 0.18);
}
.auth-btn--ghost:hover:not(:disabled) {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.32);
}
.auth-btn--ghost:disabled { opacity: 0.5; cursor: default; }
/* Turnstile. Renders nothing at all for visitors that pass silently
   (interaction-only), so this must collapse to zero height rather than
   reserve a gap that would sit empty on almost every load. */
.auth-turnstile:not(:empty) { margin-bottom: 1.25rem; }
.auth-turnstile .cf-turnstile:empty { display: none; }

/* [hidden] must beat the layout display these elements set for themselves. */
.auth-form [hidden] { display: none !important; }

/* "Send a new code" — a POST, so it must be a real button, styled to read as
   the link it sits inside a sentence as. */
.auth-helper .auth-linkbtn {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
  color: rgba(0, 0, 0, 0.75);
  text-underline-offset: 2px;
}
.auth-helper .auth-linkbtn:hover { color: var(--auth-ink); text-decoration: underline; }
/* The resend form is a sentence, not a form — drop the stacked-field gap so it
   sits tight under the code field instead of a block away from it. */
.auth-form + .auth-form { margin-top: 0; }

/* ============================================================
   Toast notifications (Django messages)
   Fixed top-right, floating over content, auto-dismissing.
   ============================================================ */
.toast-stack {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 1080;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  width: min(360px, calc(100vw - 2rem));
  pointer-events: none; /* let clicks pass through the gaps */
}
.toast-item {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.8rem 0.9rem;
  border-radius: 0.7rem;
  background: var(--aethis-surface, #fff);
  color: var(--aethis-ink, #1a1518);
  border: 1px solid var(--aethis-border, #f0d7dc);
  border-left: 4px solid var(--aethis-pink, #e8a0a8);
  box-shadow: 0 10px 30px -8px rgba(26, 21, 24, 0.25);
  font-size: 0.9rem;
  line-height: 1.35;
  animation: toast-in 0.28s cubic-bezier(0.21, 1, 0.4, 1);
}
.toast-item.toast-hide {
  animation: toast-out 0.25s ease forwards;
}
.toast-item__icon { font-size: 1.15rem; line-height: 1.1; }
.toast-item__body { flex: 1 1 auto; word-break: break-word; }
.toast-item__close {
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: inherit;
  opacity: 0.5;
  cursor: pointer;
  padding: 0;
  font-size: 1.1rem;
  line-height: 1;
}
.toast-item__close:hover { opacity: 1; }

.toast-item--success { border-left-color: #2f9e6f; }
.toast-item--success .toast-item__icon { color: #2f9e6f; }
.toast-item--error,
.toast-item--danger { border-left-color: #c0392b; }
.toast-item--error .toast-item__icon,
.toast-item--danger .toast-item__icon { color: #c0392b; }
.toast-item--warning { border-left-color: #d99a16; }
.toast-item--warning .toast-item__icon { color: #d99a16; }
.toast-item--info { border-left-color: var(--aethis-lilac, #c0a8d0); }
.toast-item--info .toast-item__icon { color: var(--aethis-lilac, #c0a8d0); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(-12px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes toast-out {
  to { opacity: 0; transform: translateX(16px); }
}
@media (prefers-reduced-motion: reduce) {
  .toast-item, .toast-item.toast-hide { animation: none; }
}

/* ============================================================
   Resource create/edit forms (crispy grouped sections)
   ============================================================ */
.resource-form-card .card-body { padding-top: 1.25rem; }

.form-section {
  border: 0;
  margin: 0 0 1.75rem;
  padding: 0;
}
.form-section > legend {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--aethis-pink-deep, #c87a86);
  border-bottom: 1px solid var(--aethis-border, #f0d7dc);
  padding-bottom: 0.4rem;
  margin-bottom: 1rem;
  width: 100%;
  float: none;
}
.form-section:last-of-type { margin-bottom: 0.5rem; }

/* "Advanced settings" disclosure — collapses the long tail of policy fields so
   the modal leads with the essentials. Native <details> so it needs no JS. */
.form-advanced {
  border-top: 1px solid var(--aethis-border, #f0d7dc);
  margin-top: 0.5rem;
  padding-top: 1rem;
}
.form-advanced > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--aethis-pink-deep, #c87a86);
  user-select: none;
}
.form-advanced > summary::-webkit-details-marker { display: none; }
.form-advanced > summary::before {
  content: "▸";
  font-size: 0.8em;
  transition: transform 0.15s ease;
}
.form-advanced[open] > summary::before { transform: rotate(90deg); }
.form-advanced-body { padding-top: 1.25rem; }
.admin-console .form-advanced { border-top-color: var(--con-border); }
.admin-console .form-advanced > summary { color: var(--con-accent); }

/* Tom Select — match the Bootstrap form controls and keep options on one tidy
   line (truncate with an ellipsis) instead of wrapping long emails/names. */
.ts-wrapper.form-select { padding: 0; }
.ts-control {
  border-radius: var(--bs-border-radius, 0.375rem);
  border-color: var(--aethis-border, #f0d7dc);
  min-height: calc(1.5em + 0.75rem + 2px);
  padding: 0.25rem 0.6rem;
}
.ts-wrapper.focus .ts-control {
  border-color: var(--aethis-pink, #e8a0a8);
  box-shadow: 0 0 0 3px rgba(232, 160, 168, 0.25);
}
.ts-control,
.ts-dropdown { font-size: 0.95rem; }

/* Selected value (single-select): one line, ellipsis — never wrap in the box. */
.ts-wrapper.single .ts-control { flex-wrap: nowrap; }
.ts-wrapper.single .ts-control > .item {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* Dropdown options: single line, ellipsis, comfortable padding. */
.ts-dropdown .option {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 0.45rem 0.7rem;
  line-height: 1.35;
}
.ts-dropdown .active {
  background-color: #fbf0f3;
  color: var(--aethis-pink-deep, #c87a86);
}
.ts-dropdown .ts-dropdown-content { padding: 0.2rem; }

/* Resource detail definition grid */
.detail-grid dt { font-weight: 500; padding-top: 0.55rem; }
.detail-grid dd { padding-top: 0.55rem; word-break: break-word; }
.detail-grid dt:not(:first-of-type),
.detail-grid dd:not(:nth-of-type(1)) { border-top: 1px solid #fbeef1; }
.detail-grid pre { background: #fbf3f5; padding: 0.6rem 0.75rem; border-radius: 0.5rem; }

/* Tom Select dropdown must sit above Unpoly overlays (z-index ~2000) so the
   searchable list is never clipped/hidden behind a modal. */
.ts-dropdown { z-index: 2100; }
.ts-wrapper.single .ts-control { cursor: text; }

/* ==========================================================================
   End-user portal (My Account / Products / Machines)
   White page, black type, sakura pink. Three colours and no fourth: the paper
   is a cool off-white (not the old warm washi, not a flat white), the ink is
   black, and the pink is spent on exactly two things — the rail and the badge
   spine. Dashed black hairlines are the connective tissue. Chrome (top bar,
   footer) stays charcoal plum, the same #1a1518 the marketing site uses.
   Scoped under `body.portal-shell`; the admin console is untouched.
   ========================================================================== */
body.portal-shell {
  --p-bg:       #f3f3f5;  /* page base, under the washes below */
  --p-surface:  #ffffff;  /* cards */
  --p-inset:    #f7f7f9;  /* nested key / machine rows on cards */
  --p-elevated: #f0f0f3;  /* hover, thead, chips */
  --p-ink:      #0a0a0a;  /* primary text */
  --p-line:     rgba(10, 10, 10, 0.13);   /* hairlines */
  --p-line-2:   rgba(10, 10, 10, 0.08);   /* quieter hairlines (inside cards) */
  --p-line-3:   rgba(10, 10, 10, 0.28);   /* stronger dividers + dashes */
  --p-muted:    rgba(10, 10, 10, 0.58);
  --p-faint:    rgba(10, 10, 10, 0.42);
  --p-rose:     #b06a78;  /* accent text on light (the pales fail contrast here) */
  --p-rose-ink: #8a4655;  /* accent text, hover / emphasis */
  --p-tint:     rgba(240, 200, 208, 0.32);
  --p-topbar:   #1a1518;  /* charcoal plum nav + footer (aethisdsp.com chrome) */
  --p-hot:      #f0c8d0;  /* accent on the dark chrome */
  --p-soft:     #f0c8d0;  /* pale sakura pink (site "soft") */
  --p-soft-2:   #f7dde2;  /* lighter sakura, for gradients */

  /* Status colours, tuned for a white card rather than a charcoal one. */
  --p-ok:     #1f7a44;
  --p-ok-bg:  rgba(31, 122, 68, 0.10);
  --p-warn:   #8a5a0c;
  --p-warn-bg: rgba(180, 120, 20, 0.12);
  --p-bad:    #c0414e;
  --p-bad-bg: rgba(192, 65, 78, 0.10);

  color: var(--p-ink);
  background-color: var(--p-bg);
}
/* Not a flat fill: a sakura bloom off the top-right corner and a cooler wash
   rising up the page, so large empty areas have somewhere to go. Gradients,
   never blurs — see the performance rules.

   Painted onto a `position: fixed` layer of its own rather than set on the
   body with `background-attachment: fixed`. The two look identical and behave
   very differently: a fixed *attachment* is re-rasterised against the viewport
   on every scroll frame — four stacked gradients' worth, the classic mobile
   scroll-jank pattern — whereas a fixed *element* is painted once and left
   alone by the compositor. `-1` puts it above the body's flat fill and below
   every scrap of content. */
body.portal-shell::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background-image:
    radial-gradient(1150px 620px at 96% -10%, rgba(240, 200, 208, 0.8), transparent 64%),
    radial-gradient(820px 520px at -10% 4%, rgba(232, 160, 168, 0.3), transparent 68%),
    radial-gradient(900px 600px at 30% 108%, rgba(240, 200, 208, 0.26), transparent 66%),
    linear-gradient(180deg, #fdfdfe 0%, #f4f4f7 56%, #eeeef2 100%);
  background-repeat: no-repeat;
}
/* Bootstrap's muted grey was picked for a white page; on charcoal it reads as
   noise. Same override the admin console makes. */
body.portal-shell .text-muted { color: var(--p-muted) !important; }

/* Sticky footer: make the page a full-height column so short pages (e.g. a
   single-row Licenses table, or the dashboard) push the dark footer to the
   bottom of the viewport instead of leaving it floating mid-screen with dead
   space beneath. Both shells share the `.portal-footer` (which has no `mt-auto`
   of its own), so both need this; auth + marketing pages are untouched. */
body.portal-shell .page-wrapper,
body.admin-console .page-wrapper {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
body.portal-shell [up-main],
body.admin-console [up-main] { flex: 1 0 auto; }
body.portal-shell .portal-footer,
body.admin-console .portal-footer { flex-shrink: 0; }

/* …and the console gets the whole first screen to itself. `min-height: 100vh`
   on the wrapper alone isn't enough: the footer is ~780px tall, so it already
   satisfies the 100vh on its own, `[up-main]` never grows, and the footer's
   top edge lands above the fold on a short page. Reserving a full viewport
   *below the top bar* for the content is what pushes the footer off-screen
   until you scroll for it. The height is then handed down the chain so the
   left rail actually fills what it was given rather than stopping at its
   content. `--p-topbar-h` also pins the bar itself, so the reservation and the
   thing it subtracts can't drift apart. */
body.portal-shell [up-main] {
  display: flex; flex-direction: column;
  min-height: calc(100vh - var(--p-topbar-h));
}
/* Bootstrap's `pb-3` on the main region leaves a 16px band between the rail's
   bottom edge and the footer, which the rail's bleed can't reach. The wrap's
   own bottom gutter is the spacing here. `!important` because that is what the
   utility class it overrides uses. */
body.portal-shell [up-main].pb-3 { padding-bottom: 0 !important; }
body.portal-shell .portal { flex: 1 1 auto; display: flex; flex-direction: column; }
body.portal-shell .portal__wrap { flex: 1 1 auto; display: flex; flex-direction: column; }
body.portal-shell .portal-body { flex: 1 1 auto; }

/* Firmament is a caps-only display face; keep proper-noun headings in the
   clean sans so names/titles aren't forced to uppercase. */
body.portal-shell .idbadge__name,
body.portal-shell .portal-empty__title,
body.portal-shell .portal-welcome__title,
body.portal-shell .product-card__name,
body.portal-shell .portal-table__name {
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  letter-spacing: -0.01em;
}

/* --- Top bar — clone of the aethisdsp.com dark nav ----------------------- */
/* Declared globally because `.portal-topbar` is shared chrome, and pinned so
   the portal can subtract an exact number from the viewport (see the sticky
   footer block above). It has to be a floor the bar never actually exceeds,
   or the reservation below it under-shoots and the footer creeps back above
   the fold — so the one thing that could grow the bar (a long display name)
   is truncated rather than wrapped. Everything here is sized in rem, the same
   unit as the token, so the two scale together. */
:root { --p-topbar-h: 4rem; }
.portal-topbar {
  position: sticky; top: 0; z-index: 1030;
  min-height: var(--p-topbar-h);
  background: var(--p-topbar);
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.portal-topbar__inner {
  max-width: 1760px; margin: 0 auto;
  padding: 0.9rem 1.75rem;
  display: flex; align-items: center; gap: 1.5rem;
}
.portal-topbar__brand {
  flex-shrink: 0; text-decoration: none;
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 1.25rem; letter-spacing: 0.1em; text-transform: uppercase; line-height: 1;
  color: #fff;
}
.portal-topbar__brand span { color: var(--p-hot); }
/* Center hairline + mono strip (decorative). */
.portal-topbar__rule { flex: 1 1 auto; display: none; align-items: center; gap: 1rem; min-width: 0; }
.portal-topbar__hair { flex: 3 1 0; height: 1px; background: rgba(255, 255, 255, 0.12); }
.portal-topbar__hair--short { flex: 2 1 0; }
.portal-topbar__meta {
  flex-shrink: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.62rem; letter-spacing: 0.25em; text-transform: uppercase; color: rgba(255, 255, 255, 0.3);
}
@media (min-width: 900px) { .portal-topbar__rule { display: flex; } }
.portal-topbar__account { margin-left: auto; flex-shrink: 0; }
.portal-topbar__user {
  display: flex; align-items: center; gap: 0.6rem;
  text-decoration: none; cursor: pointer;
}
.portal-topbar__id {
  display: none; flex-direction: column; text-align: right; line-height: 1.25;
  /* Capped and clipped, never wrapped: a two-line name would push the bar past
     `--p-topbar-h` and quietly break the viewport reservation that keeps the
     footer below the fold. */
  min-width: 0; max-width: 15rem;
}
@media (min-width: 576px) { .portal-topbar__id { display: flex; } }
.portal-topbar__name,
.portal-topbar__email { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.portal-topbar__name { font-size: 0.82rem; font-weight: 500; color: #fff; }
.portal-topbar__email { font-size: 0.72rem; color: rgba(255, 255, 255, 0.45); }
.portal-topbar__avatar {
  width: 34px; height: 34px; flex-shrink: 0; border-radius: 50%;
  display: grid; place-items: center; color: #fff;
  background: #000; border: 1.5px solid #fff;
}
.portal-topbar__avatar i { font-size: 1.2rem; line-height: 1; }
.portal-topbar__chev { color: rgba(255, 255, 255, 0.4); font-size: 1.1rem; }
.portal-topbar__login {
  color: rgba(255, 255, 255, 0.8); text-decoration: none;
  font-size: 0.8rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  padding: 0.4rem 1rem; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 999px;
}
/* Literal ink, not `--p-ink`: the bar is dark on both shells, but under the
   portal that token now means "light text" and would give white-on-white. */
.portal-topbar__login:hover { background: #fff; color: #0a0a0c; }
/* Dark dropdown to match the bar. */
.portal-topbar__menu {
  background: #17141a; border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 18px 40px -20px rgba(0, 0, 0, 0.8); border-radius: 0.7rem; padding: 0.35rem;
}
.portal-topbar__menu .dropdown-item {
  color: rgba(255, 255, 255, 0.75); border-radius: 0.5rem; font-size: 0.88rem; padding: 0.5rem 0.7rem;
}
.portal-topbar__menu .dropdown-item:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }
.portal-topbar__menu .dropdown-divider { border-color: rgba(255, 255, 255, 0.1); }

/* --- Shell + page header ------------------------------------------------- */
.portal { padding: 0; }
/* No max-width: the rail is meant to sit against the left edge of the window
   and the ID badge against the right, not float in a centred column. The top
   bar and footer are unclamped to match, so the brand chrome spans the same
   full width the console does. */
.portal__wrap {
  /* Held as variables because the left rail cancels them with negative margins
     to sit flush against the window — change the gutter in one place and the
     bleed follows instead of drifting out of step. */
  --p-gut-x: 1.75rem;
  --p-gut-top: 1.75rem;
  --p-gut-bottom: 3rem;
  max-width: none;
  margin: 0;
  padding: var(--p-gut-top) var(--p-gut-x) var(--p-gut-bottom);
}
body.portal-shell .portal-topbar__inner,
body.portal-shell .portal-footer__inner { max-width: none; }
.portal-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 1.5rem 2rem; flex-wrap: wrap;
  padding: 0.25rem 0 1.6rem; margin-bottom: 1.6rem;
  border-bottom: 1px dashed var(--p-line-3);
}
.portal-head__text { min-width: 0; }
.portal-head__title {
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 500; letter-spacing: -0.02em; line-height: 1.05;
  font-size: clamp(1.85rem, 3.2vw, 2.5rem);
  color: var(--p-ink); margin: 0.5rem 0 0;
}
.portal-head__stop { color: var(--p-rose); }
.portal-head__sub { margin: 0.7rem 0 0; color: var(--p-muted); font-size: 0.95rem; max-width: 74ch; }
/* `align-items: center` so the tall trial alert and the sell button sit level
   with each other rather than on a shared baseline. */
.portal-head__actions { display: flex; align-items: center; gap: 0.85rem; flex-wrap: wrap; flex-shrink: 0; }
.portal-head__actions:empty { display: none; }

.portal-eyebrow {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  text-transform: uppercase; letter-spacing: 0.28em; font-size: 0.66rem; font-weight: 600;
  color: var(--p-faint);
}
.portal-eyebrow__dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--p-rose); box-shadow: 0 0 0 3px var(--p-tint);
}

/* --- Layout: full-height rail + content + optional right rail ------------ */
/* `stretch` (not `flex-start`) is what lets the nav panel run the whole height
   of the page next to a short table — the rail is meant to read as the edge of
   the console, not as three links hanging off the top. */
.portal-body { display: flex; align-items: stretch; gap: 1.5rem; }
/* The rail is joined to the window, not floated inside the gutter: it cancels
   the wrap's left, top and bottom padding so the plate meets the edge of the
   viewport, the underside of the top bar, and the top of the footer. It reads
   as the wall of the console rather than as a card that happens to be tall. */
.portal-aside {
  flex: 0 0 264px; width: 264px;
  margin: calc(var(--p-gut-top) * -1) 0
          calc(var(--p-gut-bottom) * -1) calc(var(--p-gut-x) * -1);
}
/* The header and the content share this column, so the two rails run the full
   height of the page beside them. */
.portal-column { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.portal-main { flex: 1 1 auto; min-width: 0; }
.portal-rail { flex: 0 0 360px; width: 360px; }

/* --- Left rail nav — the near-white sakura plate ------------------------- */
/* Barely-there pink, black type, everything flush left. `min-height` keeps the
   plate long on a page with almost nothing on it (an empty Machines list);
   `height: 100%` lets it grow with a long one. */
/* No radius and no outer border: three of its four edges are now the window,
   and a rounded corner against the edge of the screen reads as a rendering
   fault. The single right-hand hairline is the whole frame — it's the seam
   between the rail and the page, which is the only edge that exists. */
.portal-nav {
  display: flex; flex-direction: column;
  height: 100%; min-height: min(38rem, calc(100vh - 8rem));
  background: linear-gradient(180deg, #fefbfc 0%, #fdf3f6 52%, #fbe9ee 100%);
  border-right: 1px solid rgba(10, 10, 10, 0.14);
  overflow: hidden;
}

/* A flush list, the way the admin console's own sidebar is built. No per-row
   surface, border, radius or shadow: on a tinted rail four bordered cards read
   as a component demo, and the row itself is a big enough target to be an
   obvious control without being dressed as one. */
.portal-nav__links { display: flex; flex-direction: column; padding: 0.6rem 0; }
.portal-nav__link {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.8rem 1.35rem;
  color: var(--p-ink); text-decoration: none;
  /* The hover/active marker lives here so the row never changes width. */
  border-left: 3px solid transparent;
  transition: background 0.14s ease, border-color 0.14s ease;
}
.portal-nav__icon { font-size: 1.25rem; line-height: 1; color: rgba(10, 10, 10, 0.55); transition: color 0.14s ease; }
.portal-nav__name {
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  text-transform: uppercase; letter-spacing: 0.13em; font-size: 0.95rem; line-height: 1;
  color: var(--p-ink);
}
/* Only surviving per-row glyph: it says the link leaves the portal. */
.portal-nav__ext { margin-left: auto; font-size: 0.95rem; color: rgba(10, 10, 10, 0.35); }
.portal-nav__link:hover { background: rgba(255, 255, 255, 0.72); border-left-color: var(--p-rose); }
.portal-nav__link:hover .portal-nav__icon { color: var(--p-ink); }
/* Current page: the whole row flipped to ink with sakura type — the
   white/pink/black move the marketing site makes with its hover-reveal brand. */
.portal-nav__link.is-active { background: var(--p-ink); border-left-color: var(--p-ink); }
.portal-nav__link.is-active .portal-nav__name,
.portal-nav__link.is-active .portal-nav__icon { color: var(--p-soft); }
.portal-nav__link:focus-visible { outline: 2px solid var(--p-ink); outline-offset: -2px; }

/* `auto` margin rather than a decorative filler element: the slack between the
   last link and the support note is just space, and it doesn't need a job. */
.portal-nav__foot {
  margin-top: auto;
  border-top: 1px solid rgba(10, 10, 10, 0.12); padding: 1.05rem 1.35rem;
}
.portal-nav__note {
  margin: 0; display: block;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  text-transform: uppercase; letter-spacing: 0.12em; font-size: 0.58rem; line-height: 1.7;
  color: rgba(10, 10, 10, 0.5);
}
.portal-nav__note a { color: var(--p-ink); text-decoration: none; border-bottom: 1px dashed rgba(10, 10, 10, 0.4); }
.portal-nav__note a:hover { border-bottom-style: solid; }

/* --- Buttons ------------------------------------------------------------- */
.portal-btn {
  display: inline-flex; align-items: center; gap: 0.45rem;
  padding: 0.6rem 1.15rem; border-radius: 999px;
  font-family: inherit; font-weight: 500; font-size: 0.88rem; line-height: 1;
  border: 1px solid transparent; cursor: pointer; text-decoration: none;
  transition: background 0.14s ease, border-color 0.14s ease, color 0.14s ease;
}
.portal-btn:disabled { cursor: wait; opacity: 0.62; }
/* Store shut ("not available yet"). Distinct from :disabled's wait state — this
   button isn't busy, it's switched off — so it flattens to inset grey and holds
   that look through hover/focus. The class is doubled to out-specify the colour
   variants it sits beside (--primary, --sell), which are declared further down
   and would otherwise win on source order. */
.portal-btn--off.portal-btn--off,
.portal-btn--off.portal-btn--off:hover,
.portal-btn--off.portal-btn--off:focus {
  background: var(--p-inset); color: var(--p-muted); border-color: var(--p-line);
  cursor: not-allowed; opacity: 1; box-shadow: none;
}
/* Hero scale — the page's one real decision, not a toolbar control. Big enough
   to stand level with the trial alert beside it and to be the thing the eye
   lands on after the headline. Only ever used in `portal_cta`; every other
   button on the portal stays at the base size, because a page where
   everything shouts says nothing. */
.portal-btn--hero {
  padding: 1.1rem 2rem; font-size: 1.05rem; gap: 0.8rem;
  border-radius: 1rem 0.35rem 1rem 0.35rem;   /* petal cut, matching the alert */
}
.portal-btn--hero i { font-size: 1.5rem; }
/* The sell button carries two lines: the invitation, and the offer under it.
   The price is on the button's own face because a CTA that hides the number
   gets clicked by people who bounce at the checkout. */
.portal-btn--sell { align-items: center; padding: 1rem 2.2rem; }
.portal-btn__stack { display: flex; flex-direction: column; gap: 0.28rem; text-align: left; }
.portal-btn__lead { font-size: 1.2rem; font-weight: 600; line-height: 1.1; }
.portal-btn__price {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.7rem; font-weight: 600; line-height: 1;
  text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--p-soft); opacity: 0.88;
}
/* Lit rather than merely dark: a sakura halo under the ink so it reads as the
   live control on a near-white page. Gradient shadow, never a blur filter. */
.portal-btn--sell { box-shadow: 0 16px 34px -18px rgba(176, 106, 120, 0.85); }
.portal-btn--sell:hover .portal-btn__price { opacity: 1; }
/* Ink fill on white paper, and the pink kept for the hover — the same
   black/pink pair the rail's active tab uses. */
.portal-btn--primary {
  background: var(--p-ink); color: #ffffff; font-weight: 500;
  box-shadow: 0 12px 26px -16px rgba(10, 10, 10, 0.7);
}
.portal-btn--primary:hover { background: #241d20; color: var(--p-soft); }
.portal-btn--ghost {
  background: transparent; color: var(--p-ink);
  border-color: var(--p-line-3);
}
.portal-btn--ghost:hover { border-color: var(--p-ink); background: var(--p-tint); color: var(--p-ink); }
.portal-btn--ghost-dark {
  background: #ffffff; color: var(--p-ink); border-color: var(--p-line-3);
}
.portal-btn--ghost-dark:hover { border-color: var(--p-ink); background: var(--p-tint); }
.portal-btn--copy {
  padding: 0.3rem 0.6rem; font-size: 0.78rem; border-radius: 999px;
  background: #ffffff; color: var(--p-muted);
  border-color: var(--p-line); vertical-align: middle;
}
.portal-btn--copy:hover { color: var(--p-ink); border-color: var(--p-line-3); }
.portal-btn--copy.is-copied { border-color: var(--p-rose); color: var(--p-rose-ink); background: var(--p-tint); }
/* Square icon-only variant — small enough to sit inside a mono label row. */
.portal-btn--icon {
  padding: 0; width: 26px; height: 26px; border-radius: 0.45rem;
  justify-content: center; flex-shrink: 0;
}
.portal-btn--icon i { font-size: 0.95rem; }

/* --- Cards --------------------------------------------------------------- */
.portal-card {
  background: var(--p-surface);
  border: 1px solid var(--p-line);
  border-radius: 1rem; overflow: hidden; margin-bottom: 1.25rem;
}
.portal-card--plain { background: transparent; border: 0; }
.portal-card__head {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  padding: 1.15rem 1.4rem 0.9rem;
}
.portal-card__title {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.66rem; font-weight: 600; margin: 0; color: var(--p-faint);
  text-transform: uppercase; letter-spacing: 0.16em;
}
.portal-card__count {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.62rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--p-faint); padding: 0.24rem 0.6rem; border-radius: 999px;
  background: var(--p-inset); border: 1px solid var(--p-line);
}
.portal-card__action { font-size: 0.82rem; font-weight: 500; color: var(--p-rose); text-decoration: none; }
.portal-card__action:hover { color: var(--p-rose-ink); }
.portal-card__body { padding: 0.4rem 1.4rem 1.4rem; }
.portal-card__desc { color: var(--p-muted); font-size: 0.92rem; line-height: 1.55; max-width: 62ch; margin: 0 0 1.1rem; }
.portal-card__empty { color: var(--p-muted); text-align: center; padding: 2rem 1.4rem; }

/* --- Member ID badge (right rail on the account page) -------------------- */
/* A physical credential, not a profile box: white card stock, black print,
   dashed rules, and a sakura spine down the RIGHT edge carrying the tier.
   Sticky, because it is the identity the rest of the page is about. */
.portal-rail .idbadge { position: sticky; top: 5rem; }
.idbadge {
  display: flex; overflow: hidden;
  background:
    radial-gradient(120% 70% at 100% 0%, rgba(240, 200, 208, 0.28), transparent 62%),
    var(--p-surface);
  border: 1px solid rgba(10, 10, 10, 0.18);
  border-radius: 1.25rem 0.5rem 1.25rem 0.5rem;  /* petal cut */
  box-shadow: 0 26px 50px -34px rgba(10, 10, 10, 0.42);
}

/* The spine — a sakura bar down the badge's right edge, cut top to bottom with
   45° bars and nothing else. No label: the tier is already printed on the spec
   plate, and a bar that is purely a mark reads as part of the card stock. */
.idbadge__spine {
  order: 2; flex: 0 0 54px; position: relative; overflow: hidden;
  background: linear-gradient(200deg, var(--p-soft-2) 0%, var(--p-soft) 52%, #e9b3bf 100%);
  border-left: 1px solid rgba(10, 10, 10, 0.18);
}
.idbadge__spine::before {
  content: ""; position: absolute; left: 7px; top: 0.9rem; bottom: 0.9rem;
  border-left: 1px dashed rgba(10, 10, 10, 0.32); z-index: 1;
}
.idbadge__spine::after {
  content: ""; position: absolute; inset: 0;
  background: repeating-linear-gradient(
    135deg,
    rgba(10, 10, 10, 0.9) 0 5px,
    transparent 5px 13px
  );
}
/* Trial and unlicensed accounts get an ink bar rather than a sakura one, so the
   tier still reads at a glance without inventing a fourth colour. */
.idbadge--trial .idbadge__spine,
.idbadge--free .idbadge__spine { background: linear-gradient(200deg, #241d20 0%, #14100f 100%); }
.idbadge--trial .idbadge__spine::before,
.idbadge--free .idbadge__spine::before { border-left-color: rgba(255, 255, 255, 0.3); }
/* Cut the other way round on an ink spine: sakura bars on black. */
.idbadge--trial .idbadge__spine::after,
.idbadge--free .idbadge__spine::after {
  background: repeating-linear-gradient(
    135deg,
    rgba(240, 200, 208, 0.92) 0 5px,
    transparent 5px 13px
  );
}

.idbadge__body { order: 1; flex: 1 1 auto; min-width: 0; padding: 1.35rem 1.4rem 1.2rem; }

.idbadge__brand {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding-bottom: 1.15rem; border-bottom: 1px dashed var(--p-line-3);
}
.idbadge__wordmark {
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  text-transform: uppercase; letter-spacing: 0.16em; font-size: 1rem; line-height: 1;
  color: var(--p-ink);
}
.idbadge__wordmark span { color: var(--p-rose); }
/* The lanyard slot — a real punched groove, sized to look like something a
   strap goes through. Two shadows: a hard inner lip and a soft depth, with a
   highlight along the bottom edge so it reads as cut *into* the card. */
.idbadge__slot {
  width: 76px; height: 17px; flex-shrink: 0; border-radius: 999px;
  background: linear-gradient(180deg, #d8d8dd 0%, #eeeef1 62%, #ffffff 100%);
  border: 1px solid rgba(10, 10, 10, 0.3);
  box-shadow:
    inset 0 2px 3px rgba(10, 10, 10, 0.42),
    inset 0 -1px 0 rgba(255, 255, 255, 0.9);
}

/* Portrait — a rectangular plate, not a bubble avatar. */
.idbadge__portrait {
  position: relative; margin: 1.25rem 0 1.35rem;
  width: 108px; height: 126px;
  display: grid; place-items: center;
  border-radius: 0.85rem 0.25rem 0.85rem 0.25rem;
  background: linear-gradient(160deg, var(--p-soft-2) 0%, var(--p-soft) 100%);
  border: 1px solid rgba(10, 10, 10, 0.22);
}
.idbadge__initials {
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 2.5rem; letter-spacing: 0.08em; line-height: 1; color: var(--p-ink);
}
.idbadge__portrait > i { font-size: 2.6rem; color: rgba(10, 10, 10, 0.5); }

.idbadge__name {
  font-size: 1.25rem; font-weight: 600; line-height: 1.2; margin: 0;
  color: var(--p-ink); word-break: break-word;
}
.idbadge__email { margin: 0.25rem 0 0; color: var(--p-muted); font-size: 0.85rem; word-break: break-word; }
/* Status, role and join date as one printed line — see the template. */
.idbadge__meta {
  margin: 0.7rem 0 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  text-transform: uppercase; letter-spacing: 0.14em; font-size: 0.6rem; font-weight: 600;
  line-height: 1.7; color: var(--p-muted);
}
.idbadge__meta span { color: var(--p-rose); margin: 0 0.15rem; }

/* Spec plate — the printed details, mono labels over dashed rules. */
.idbadge__specs { margin: 1.25rem 0 0; display: grid; grid-template-columns: 1fr 1fr; gap: 0; }
.idbadge__spec {
  padding: 0.85rem 0 0.85rem; border-top: 1px dashed var(--p-line-3); min-width: 0;
}
.idbadge__spec--wide { grid-column: 1 / -1; }
.idbadge__spec dt {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  min-height: 1.4rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  text-transform: uppercase; letter-spacing: 0.18em; font-size: 0.58rem; font-weight: 600;
  color: var(--p-faint); margin-bottom: 0.45rem;
}
.idbadge__spec dd { margin: 0; display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.idbadge__spec .portal-id,
.idbadge__spec .portal-key {
  font-size: 0.78rem; overflow-wrap: anywhere; min-width: 0;
  background: var(--p-inset); border-color: var(--p-line-3); color: var(--p-ink);
}
/* The masked user ID keeps its own line, but the copy button rides beside the
   reveal eye rather than wrapping under the field as a lone floating square.
   `min-width: 0` lets a revealed UUID wrap inside the field instead of pushing
   the button off the card. */
.idbadge__spec .portal-keyfield--block { flex: 1 1 auto; min-width: 0; }
.idbadge__spec .portal-key--block { padding: 0.5rem 0.6rem; font-size: 0.76rem; letter-spacing: 0.01em; }
.idbadge__figure {
  font-size: 1.35rem; font-weight: 600; line-height: 1; color: var(--p-ink); text-decoration: none;
}
a.idbadge__figure:hover { color: var(--p-rose-ink); }
.idbadge__figure--plan { font-size: 1rem; }
.idbadge__of { color: var(--p-faint); font-size: 0.95rem; font-weight: 500; }
.idbadge__warn { flex-basis: 100%; font-size: 0.72rem; color: var(--p-warn); }

/* Credential footer — one mono legend, the way the bottom of a laminated pass
   is set. The fake barcode that used to sit above it encoded nothing. */
.idbadge__foot {
  margin-top: 1.1rem; padding-top: 0.9rem; border-top: 1px dashed var(--p-line-3);
  display: flex;
}
.idbadge__serial {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  text-transform: uppercase; letter-spacing: 0.2em; font-size: 0.55rem; font-weight: 600;
  color: var(--p-muted);
}

/* --- Definition list ----------------------------------------------------- */
.portal-dl { margin: 0; display: grid; gap: 0; }
.portal-dl > div { display: grid; grid-template-columns: 9rem 1fr; gap: 1rem; padding: 0.7rem 0; }
.portal-dl > div + div { border-top: 1px solid var(--p-line-2); }
.portal-dl dt { margin: 0; font-weight: 500; color: var(--p-muted); }
.portal-dl dd { margin: 0; color: var(--p-ink); word-break: break-word; }

/* --- Key / id chips ------------------------------------------------------ */
.portal-key, .portal-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.84rem;
  background: var(--p-inset); border: 1px solid var(--p-line-2);
  border-radius: 0.45rem; padding: 0.14rem 0.45rem; color: var(--p-ink);
}
.portal-key--block { display: block; padding: 0.75rem 0.9rem; font-size: 0.92rem; letter-spacing: 0.03em; }

/* Masked key + reveal toggle (_license_key.html). A key is a bearer credential,
   so it ships masked and the eye swaps in the full string on demand. */
.portal-keyfield { display: inline-flex; align-items: center; gap: 0.35rem; max-width: 100%; }
.portal-keyfield--block { display: flex; }
.portal-keyfield--block .portal-key { flex: 1 1 auto; }
.portal-key__toggle {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 1.65rem; height: 1.65rem; padding: 0; cursor: pointer;
  border: 1px solid var(--p-line-2); border-radius: 0.45rem;
  background: var(--p-inset); color: var(--p-faint);
  transition: color 0.15s ease, border-color 0.15s ease;
}
.portal-key__toggle:hover { color: var(--p-ink); border-color: var(--p-line); }
.portal-key__toggle:focus-visible { outline: 2px solid var(--p-rose); outline-offset: 1px; }

.portal-keyrow { margin-bottom: 1.1rem; }
.portal-keyrow__label {
  display: block; text-transform: uppercase; letter-spacing: 0.14em;
  font-size: 0.62rem; font-weight: 600; color: var(--p-faint); margin-bottom: 0.4rem;
}

/* --- Tables -------------------------------------------------------------- */
.portal-table { width: 100%; border-collapse: collapse; margin: 0; }
.portal-table thead th {
  text-align: left; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.18em;
  font-weight: 700; color: var(--p-muted);
  padding: 0.75rem 1.4rem; background: var(--p-elevated);
  border-top: 1px solid var(--p-line-2); border-bottom: 1px solid var(--p-line);
}
.portal-table tbody td { padding: 0.95rem 1.4rem; border-top: 1px dashed var(--p-line-3); color: var(--p-ink); vertical-align: middle; }
.portal-table tbody tr:first-child td { border-top: none; }
.portal-table tbody tr:hover { background: var(--p-inset); }
.portal-link {
  display: inline-flex; align-items: center; gap: 0.15rem;
  color: var(--p-rose); font-weight: 500; text-decoration: none; white-space: nowrap;
}
.portal-link:hover { color: var(--p-rose-ink); }
.portal-link i { font-size: 1.05rem; transition: transform 0.14s ease; }
.portal-link:hover i { transform: translateX(2px); }

/* Licence table — the account page's centrepiece. Two rows at most, so each one
   gets room: the name over its tier, the key on its own line with reveal+copy,
   and the seat count set as a figure rather than as body text. */
/* Column widths are declared rather than left to the browser: the licence names
   are hyphenated ("Aethis-Perpetual") and the keys are one long token, so auto
   layout gave both of them two cramped lines while Status and Expiry sat in
   half the table doing nothing. */
.portal-table--licenses tbody td { padding: 1.1rem 1.1rem; }
.portal-table--licenses thead th { padding-left: 1.1rem; padding-right: 1.1rem; }
/* Seats and the Details link are short, known-length values; letting them wrap
   cost the key chip a line each. Status is left wrappable because the expiry
   line under the badge is the one cell whose length we don't control. */
.portal-table--licenses th:nth-child(4),
.portal-table--licenses th:nth-child(5) { width: 1%; }
.portal-table--licenses td:nth-child(4),
.portal-table--licenses td:nth-child(5) { white-space: nowrap; }
.portal-table--licenses td:nth-child(3) { max-width: 14rem; }
/* Names are hyphenated ("Aethis-Perpetual") and browsers break after a hyphen,
   which split every licence name across two lines. */
.portal-table__name { display: block; font-size: 1rem; font-weight: 600; color: var(--p-ink); white-space: nowrap; }
.portal-table__tier {
  display: block; margin-top: 0.3rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  text-transform: uppercase; letter-spacing: 0.14em; font-size: 0.56rem; font-weight: 600;
  color: var(--p-faint);
}
.portal-table__key { display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap; }
/* A masked key is a fixed-length string; letting it break gave a three-line
   chip in a column with room to spare. Nowrap makes it the column's
   min-content, so auto table layout sizes the column to hold it. */
.portal-table__key .portal-key { white-space: nowrap; }
.portal-table__seats { font-size: 1.05rem; font-weight: 600; white-space: nowrap; }
.portal-table__expiry {
  display: block; margin-top: 0.4rem; white-space: normal;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.62rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--p-faint);
}
.portal-table__of { color: var(--p-faint); font-size: 0.85rem; font-weight: 500; }

/* --- Forms (the password page is the portal's only one) ------------------ */
/* Plain stacked fields on the washi ground rather than the auth pages' inverted
   panel: this sits inside a portal card, and a second field style two shades
   away from the card it lives in reads as a widget dropped in from elsewhere.
   Bounded width because a password field stretched across the content column
   looks like a search box. */
.portal-form { max-width: 26rem; }
.portal-field { margin-bottom: 0.95rem; }
.portal-field__label {
  display: block; margin-bottom: 0.35rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.6rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--p-faint);
}
.portal-input {
  width: 100%; padding: 0.6rem 0.8rem;
  background: var(--p-surface); color: var(--p-ink);
  border: 1px solid var(--p-line-3); border-radius: 0.55rem;
  font-size: 0.95rem; line-height: 1.4;
  transition: border-color 0.14s ease, box-shadow 0.14s ease;
}
.portal-input:focus {
  outline: none; border-color: var(--p-rose);
  box-shadow: 0 0 0 3px var(--p-tint);
}
.portal-input::placeholder { color: var(--p-faint); }
/* The six digits, set like the licence keys: mono and tracked, so a mistyped
   code is visible as a shape rather than having to be re-read. */
.portal-input--code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 1.15rem; letter-spacing: 0.34em; font-weight: 600;
}
.portal-field__error { margin: 0.35rem 0 0; color: var(--p-bad); font-size: 0.8rem; }
.portal-form__actions { display: flex; gap: 0.6rem; flex-wrap: wrap; margin-top: 1.1rem; }

/* --- Authenticator enrolment (the QR and its typed key) ------------------- */
/* Side by side where there is room, stacked on a phone — which is where this is
   most often read, since the phone holding the app is the thing being set up. */
.otp-enrol {
  display: flex; flex-wrap: wrap; gap: 1.4rem; align-items: center;
  padding: 1.1rem; margin-bottom: 1.2rem;
  border: 1px solid var(--p-line); border-radius: 0.8rem; background: var(--p-inset);
}
/* Sized here rather than in the SVG so the QR scales with the card. The white
   plate is not decoration: a QR needs light quiet-zones to scan, and the inset
   ground behind it is not reliably light enough. */
.otp-enrol__qr {
  flex: 0 0 auto; width: 168px; height: 168px; padding: 0.5rem;
  background: #ffffff; border-radius: 0.5rem;
}
.otp-enrol__qr svg { display: block; width: 100%; height: 100%; }
.otp-enrol__key { flex: 1 1 14rem; min-width: 12rem; }
/* The key is read a character at a time off a screen and typed into a phone, so
   it is set wide and broken into something the eye can keep its place in. */
.otp-enrol__secret {
  display: block; margin: 0.4rem 0 0.7rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.92rem; letter-spacing: 0.12em; word-break: break-all; color: var(--p-ink);
}

/* --- Reassurance footnote (privacy / "what we collect") ------------------ */
.portal-note {
  display: flex; align-items: flex-start; gap: 0.55rem;
  padding: 0.85rem 1.4rem; margin: 0;
  border-top: 1px solid var(--p-line-2);
  color: var(--p-muted); font-size: 0.78rem; line-height: 1.5;
}
.portal-note i { color: var(--p-rose); font-size: 0.95rem; flex-shrink: 0; margin-top: 0.05rem; }

/* --- Status badges ------------------------------------------------------- */
.portal-badge {
  display: inline-block; padding: 0.16rem 0.55rem; border-radius: 999px;
  font-size: 0.64rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
  background: var(--p-elevated); color: var(--p-muted); border: 1px solid var(--p-line-3);
}
.portal-badge--active   { background: var(--p-ok-bg); color: var(--p-ok); border-color: rgba(31, 122, 68, 0.24); }
.portal-badge--expiring  { background: var(--p-warn-bg); color: var(--p-warn); border-color: rgba(180, 120, 20, 0.28); }
.portal-badge--inactive,
.portal-badge--expired   { background: var(--p-elevated); color: var(--p-muted); border-color: var(--p-line); }
.portal-badge--suspended,
.portal-badge--banned     { background: var(--p-bad-bg); color: var(--p-bad); border-color: rgba(192, 65, 78, 0.24); }

/* --- Empty state --------------------------------------------------------- */
.portal-empty { text-align: center; padding: 2.75rem 1.5rem; }
.portal-empty__icon {
  width: 56px; height: 56px; margin: 0 auto 1.1rem;
  border-radius: 0.9rem 0.3rem 0.9rem 0.3rem;
  display: grid; place-items: center; font-size: 1.7rem; color: var(--p-rose-ink);
  background: var(--p-tint); border: 1px solid rgba(176, 106, 120, 0.3);
}
.portal-empty__title { font-weight: 600; color: var(--p-ink); margin: 0 0 0.4rem; font-size: 1.05rem; }
.portal-empty__text { color: var(--p-muted); max-width: 46ch; margin: 0 auto 1.4rem; font-size: 0.92rem; }
.portal-empty__actions { display: flex; gap: 0.6rem; justify-content: center; flex-wrap: wrap; }

/* --- Alert --------------------------------------------------------------- */
.portal-alert {
  background: var(--p-tint); border: 1px solid rgba(176, 106, 120, 0.3); color: var(--p-rose-ink);
  border-radius: 0.7rem; padding: 0.75rem 1rem; margin-bottom: 1.25rem; font-size: 0.9rem;
}
/* Amber "your license lapsed" notice — distinct from the rose error alert. */
.portal-alert--warn {
  display: flex; align-items: center; gap: 0.55rem;
  background: var(--p-warn-bg); border-color: rgba(180, 120, 20, 0.3); color: var(--p-warn);
}
.portal-alert--warn i { font-size: 1.15rem; flex-shrink: 0; }

/* --- Owner's welcome ----------------------------------------------------- */
/* What a paying customer gets where the buy buttons used to be. Reads as a
   certificate rather than a banner: mono eyebrow on a hairline, one display
   line, and a spec strip of what the licence actually grants. Sakura wash on
   white, so it lifts off the licence card below without shouting. */
.portal-welcome {
  position: relative; overflow: hidden;
  padding: 1.5rem 1.6rem 1.35rem; margin-bottom: 1.25rem;
  border: 1px solid rgba(176, 106, 120, 0.28);
  border-radius: 1.25rem 0.5rem 1.25rem 0.5rem;
  background:
    radial-gradient(520px 220px at 100% 0%, rgba(240, 200, 208, 0.55), transparent 70%),
    linear-gradient(180deg, #fffdfe 0%, #fdf5f7 100%);
}
.portal-welcome__eyebrow {
  display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.7rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.62rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.26em; color: var(--p-rose-ink);
}
.portal-welcome__hair { display: block; width: 3rem; height: 1px; background: rgba(176, 106, 120, 0.45); }
.portal-welcome__title {
  margin: 0 0 0.55rem; font-size: 1.5rem; font-weight: 600;
  color: var(--p-ink);
}
.portal-welcome__stop { color: var(--p-rose); }
.portal-welcome__text { margin: 0; max-width: 62ch; font-size: 0.94rem; line-height: 1.65; color: var(--p-muted); }
.portal-welcome__specs {
  display: flex; flex-wrap: wrap; gap: 0.35rem 1.4rem;
  margin-top: 1.1rem; padding-top: 0.9rem; border-top: 1px solid rgba(10, 10, 10, 0.1);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.62rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.14em; color: var(--p-faint);
}

/* Days-left chip beside the trial's buy button. */
/* The running-trial alert. It used to be a mono pill the size of a table
   label, which put the one deadline the customer has on the page at the same
   weight as the word "STATUS". Now it is a plate: the day count at display
   scale, the caption underneath, amber because a deadline is a deadline — and
   tall enough to stand level with the sell button beside it, so the two read
   as one unit ("this much time left / here's the way out") rather than as a
   badge that happens to sit near a button. */
.portal-trialalert {
  display: inline-flex; align-items: center; gap: 0.85rem;
  padding: 0.9rem 1.6rem 0.9rem 1.35rem;
  border-radius: 1rem 0.35rem 1rem 0.35rem;   /* petal cut */
  border: 1px solid rgba(180, 120, 20, 0.38);
  background: linear-gradient(180deg, rgba(255, 250, 240, 0.95) 0%, var(--p-warn-bg) 100%);
  color: var(--p-warn);
}
.portal-trialalert__num {
  font-size: 2.6rem; font-weight: 700; line-height: 1;
  letter-spacing: -0.03em; color: #6b4408;
}
.portal-trialalert__text {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.78rem; font-weight: 700; line-height: 1.35;
  text-transform: uppercase; letter-spacing: 0.14em;
}
.portal-trialalert__sub { font-weight: 600; opacity: 0.7; letter-spacing: 0.1em; }

/* --- Store / product cards ---------------------------------------------- */
/* Rebuilt as a poster: the hardware render runs full width across the top (so a
   wide render stays wide instead of being letterboxed into a side panel), the
   pitch reads underneath, and the two big buttons at the bottom are the same
   controls the plugin's own licence overlay uses — 56px tall, 6px radius, pink
   for Buy and white-on-black-border for the trial (see
   aethis-v2/src/ui/source/panels/LicenseOverlay.cpp). */
.product-card {
  display: flex; flex-direction: column; overflow: hidden;
  max-width: 62rem; margin-bottom: 1.5rem;
  background: var(--p-surface); border: 1px solid var(--p-line);
  border-radius: 1.25rem 0.5rem 1.25rem 0.5rem;  /* petal cut */
  transition: border-color 0.16s ease;
}
.product-card:hover { border-color: var(--p-line-3); }

/* Banner — the render, full bleed across the top. The oxblood gradient is the
   fallback surface behind it (and for image-less products). */
.product-card__media {
  position: relative; width: 100%; aspect-ratio: 5 / 2; max-height: 26rem; overflow: hidden;
  background: linear-gradient(155deg, #241016 0%, #45202a 58%, #5c2937 100%);
  border-bottom: 1px solid var(--p-line);
}
.product-card__img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 34%;
  transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
.product-card:hover .product-card__img { transform: scale(1.025); }
/* Depth vignette so the mono tag stays readable over any part of the render. */
.product-card__media::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(0deg, rgba(14, 7, 9, 0.62) 0%, transparent 38%);
}
/* Patch-bay tag — a true capability label, mono, over the render. */
.product-card__tag {
  position: absolute; left: 1.4rem; bottom: 1.1rem; z-index: 1;
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  text-transform: uppercase; letter-spacing: 0.2em; font-size: 0.62rem; font-weight: 600;
  color: rgba(255, 255, 255, 0.88);
}
.product-card__tag::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--p-soft); box-shadow: 0 0 8px rgba(240, 200, 208, 0.75);
}
/* Fallback for products without a render (e.g. coming-soon). */
.product-card__fallback {
  position: absolute; inset: 0; z-index: 1;
  display: grid; place-content: center; justify-items: center; gap: 0.55rem; text-align: center;
}
.product-card__fallback i { font-size: 1.7rem; color: var(--p-soft); }
.product-card__fallback span {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  text-transform: uppercase; letter-spacing: 0.26em; font-size: 0.62rem; font-weight: 600;
  color: rgba(255, 255, 255, 0.6);
}

.product-card__main { flex: 1; min-width: 0; display: flex; flex-direction: column; padding: 1.9rem 2rem 1.7rem; }
.product-card__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1.5rem; flex-wrap: wrap; }
.product-card__eyebrow {
  display: inline-flex; align-items: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  text-transform: uppercase; letter-spacing: 0.22em; font-size: 0.62rem; font-weight: 600; color: var(--p-rose);
}
.product-card__eyebrow::after {
  content: ""; width: 32px; height: 1px; margin-left: 0.7rem; background: rgba(176, 106, 120, 0.45);
}
.product-card__name { font-weight: 600; font-size: 2rem; margin: 0.5rem 0 0.2rem; color: var(--p-ink); letter-spacing: -0.02em; line-height: 1.05; }
.product-card__tagline { margin: 0; color: var(--p-muted); font-size: 1rem; }
.product-card__price { text-align: right; flex-shrink: 0; }
.product-card__price-value { display: block; font-size: 2rem; font-weight: 600; color: var(--p-ink); line-height: 1.1; letter-spacing: -0.02em; }
.product-card__price-note {
  display: block; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.16em; color: var(--p-faint);
  font-weight: 600; margin-top: 0.3rem;
}
.product-card__desc { margin: 1.2rem 0 1.35rem; color: var(--p-muted); max-width: 62ch; font-size: 1rem; line-height: 1.6; }

/* Feature list — two columns of named claims, not a bullet soup. */
.product-card__features {
  list-style: none; padding: 0; margin: 0 0 1.7rem;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr)); gap: 0.7rem 2rem;
}
.product-card__features li { display: flex; align-items: flex-start; gap: 0.55rem; font-size: 0.92rem; color: var(--p-ink); line-height: 1.45; }
.product-card__features i { color: var(--p-ok); font-size: 1.05rem; flex-shrink: 0; margin-top: 0.05rem; }

/* --- The two big controls ----------------------------------------------- */
.product-card__actions { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; max-width: 44rem; }
.product-card__actions form { margin: 0; display: block; }
.product-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.55rem;
  width: 100%; min-height: 56px; padding: 0 1.5rem;
  border-radius: 6px; border: 1px solid transparent;
  font-family: inherit; font-size: 1rem; font-weight: 500; line-height: 1;
  text-decoration: none; cursor: pointer; text-align: center;
  transition: background 0.14s ease, border-color 0.14s ease, color 0.14s ease;
}
.product-btn:disabled { cursor: wait; opacity: 0.62; }
/* See .portal-btn--off — the storefront's "not available yet" state. Doubled
   for the same reason: --buy/--trial are declared below it. */
.product-btn--off.product-btn--off,
.product-btn--off.product-btn--off:hover,
.product-btn--off.product-btn--off:focus {
  background: var(--p-inset); color: var(--p-muted); border-color: var(--p-line);
  cursor: not-allowed; opacity: 1; font-weight: 500;
}
.product-card__purchase-status--soon {
  color: var(--p-muted); display: inline-flex; align-items: center; gap: 0.4rem;
}
.product-btn i { font-size: 1.2rem; }
/* The overlay's PrimaryPink: brand pink fill, dark plum ink. */
.product-btn--buy { background: #e8a0a8; color: #2a1014; border-color: #e8a0a8; font-weight: 600; }
.product-btn--buy:hover { background: var(--p-soft); border-color: var(--p-soft); color: #2a1014; }
/* The overlay's SecondaryWhite: white fill, black hairline. */
.product-btn--trial { background: #ffffff; color: var(--p-ink); border-color: var(--p-ink); }
.product-btn--trial:hover { background: var(--p-tint); }
/* The overlay's PrimaryBlack — used once the product is owned. */
.product-btn--ink { background: var(--p-ink); color: #ffffff; border-color: var(--p-ink); }
.product-btn--ink:hover { background: #241d20; color: var(--p-soft); }
.product-card__purchase-status { grid-column: 1 / -1; color: var(--p-bad); font-size: 0.85rem; }

/* Spec strip footer — mono, hairline-topped documentation voice. */
.product-card__spec {
  margin-top: 1.7rem; padding-top: 1.2rem; border-top: 1px dashed var(--p-line-3);
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 1.4rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  text-transform: uppercase; letter-spacing: 0.12em; font-size: 0.64rem; font-weight: 500; color: var(--p-faint);
}
.product-card__spec span { display: inline-flex; align-items: center; gap: 0.45rem; }
.product-card__spec i { font-size: 0.9rem; color: var(--p-muted); }
.product-card--soon { opacity: 0.72; }
.product-card__soon-badge {
  display: inline-block; padding: 0.4rem 0.9rem; border-radius: 999px; font-weight: 500; font-size: 0.85rem;
  background: var(--p-inset); color: var(--p-muted); border: 1px solid var(--p-line);
}
.product-card__owned-badge {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
  min-height: 56px; padding: 0 1.5rem; border-radius: 6px; font-weight: 600; font-size: 1rem;
  background: var(--p-ok-bg); color: var(--p-ok); border: 1px solid rgba(31, 122, 68, 0.24);
}
.product-card__owned-badge i { font-size: 1.2rem; }
/* Trial running — states a fact next to the Buy button rather than replacing
   it: buying is how a trial ends well. Rose, not green; it isn't ownership. */
.product-card__trial-badge {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
  min-height: 56px; padding: 0 1.5rem; border-radius: 6px; font-weight: 600; font-size: 0.95rem;
  background: var(--p-tint); color: var(--p-rose-ink); border: 1px dashed rgba(176, 106, 120, 0.5);
}
.product-card__trial-badge i { font-size: 1.2rem; }
.product-note {
  display: flex; align-items: center; gap: 0.5rem; margin: 0.4rem 0 0;
  color: var(--p-faint); font-size: 0.86rem; max-width: 62rem;
}
.product-note i { color: var(--p-rose); font-size: 1rem; }

/* --- Footer — clone of the aethisdsp.com dark footer --------------------- */
.portal-footer {
  background: var(--p-topbar);
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  padding: 3.5rem 1.75rem 2.25rem;
  color: rgba(255, 255, 255, 0.4);
}
.portal-footer__inner { max-width: 1760px; margin: 0 auto; }
.portal-footer__top {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 3rem; flex-wrap: wrap; margin-bottom: 3.5rem;
}
.portal-footer__brandcol { max-width: 22rem; }
.portal-footer__tag {
  color: rgba(255, 255, 255, 0.5); font-weight: 700; font-size: 0.85rem;
  text-transform: uppercase; letter-spacing: 0.05em; line-height: 1.6; margin: 0 0 0.9rem;
}
.portal-footer__mail {
  color: rgba(255, 255, 255, 0.4); text-decoration: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.78rem; letter-spacing: 0.03em;
}
.portal-footer__mail:hover { color: var(--p-hot); }
.portal-footer__cols { display: flex; gap: 4rem; flex-wrap: wrap; }
.portal-footer__col { display: flex; flex-direction: column; gap: 0.9rem; }
.portal-footer__col-h {
  font-family: "Firmament", "Inter", -apple-system, sans-serif;
  color: #fff; text-transform: uppercase; letter-spacing: 0.2em; font-size: 0.72rem;
  padding-bottom: 0.55rem; margin-bottom: 0.35rem; border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}
.portal-footer__col a {
  color: rgba(255, 255, 255, 0.4); text-decoration: none;
  text-transform: uppercase; letter-spacing: 0.12em; font-size: 0.72rem;
  font-family: "Firmament", "Inter", -apple-system, sans-serif;
}
.portal-footer__col a:hover { color: var(--p-hot); }

/* Massive hover-reveal brand — the marketing footer's signature: the wordmark
   set enormous between two hairlines, the whole band flipping to sakura with
   ink type on hover. Two lines, the second indented, as on the site. */
.portal-footer__brand {
  position: relative; overflow: hidden;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  padding: 3rem 0; margin-top: 1rem;
  display: flex; align-items: center; justify-content: center;
  user-select: none;
}
.portal-footer__brand::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: var(--p-soft); opacity: 0;
  transition: opacity 0.7s ease;
}
.portal-footer__brand:hover::before { opacity: 1; }
.portal-footer__brand-mark {
  position: relative; z-index: 1; margin: 0; width: 100%;
  display: flex; flex-direction: column;
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  text-transform: uppercase; text-align: center; color: #fff;
  font-size: clamp(3rem, 13vw, 12rem); line-height: 0.85; letter-spacing: 0.04em;
  font-weight: 400; transition: color 0.7s ease;
}
.portal-footer__brand:hover .portal-footer__brand-mark { color: #1a1518; }
.portal-footer__brand-mark span:first-child { margin-left: -0.04em; }
.portal-footer__brand-mark span:last-child { margin-left: 8vw; }

.portal-footer__bottom {
  padding-top: 1.75rem; margin-top: 0.5rem;
  display: flex; justify-content: space-between; align-items: center;
  gap: 1rem 1.5rem; flex-wrap: wrap;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.7rem; letter-spacing: 0.18em; text-transform: uppercase; color: rgba(255, 255, 255, 0.35);
}
.portal-footer__meta { display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap; }
/* Outlined pill, filling with sakura on hover — same control as the site. */
.portal-footer__pill {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.5rem 1rem; border-radius: 999px; text-decoration: none;
  border: 1px solid rgba(240, 200, 208, 0.4); color: rgba(255, 255, 255, 0.8);
  transition: background 0.16s ease, border-color 0.16s ease, color 0.16s ease;
}
.portal-footer__pill:hover { background: var(--p-soft); border-color: var(--p-soft); color: #1a1518; }
.portal-footer__pill i { font-size: 0.95rem; }
.portal-footer__product { display: inline-flex; align-items: center; gap: 0.55rem; }
.portal-footer__bloom { width: 12px; height: 12px; color: rgba(240, 200, 208, 0.5); }

/* --- Responsive ---------------------------------------------------------- */
/* Three columns need ~1300px before the licence table starts losing columns;
   below that the ID badge drops under the content and stops being sticky. */
@media (max-width: 1399.98px) {
  .portal-body { flex-wrap: wrap; }
  /* The ID badge wraps onto its own row here, so the rail no longer reaches
     the footer — bleeding past the bottom would run it under that row. */
  .portal-aside { order: 1; margin-bottom: 0; }
  .portal-column { order: 2; flex: 1 1 420px; }
  .portal-rail { order: 3; flex: 1 1 100%; width: auto; }
  .portal-rail .idbadge { position: static; max-width: 27rem; }
}
@media (max-width: 991.98px) {
  /* `nowrap` matters: a wrapping *column* flex container lays its overflow out
     in a second column, which pushed the whole page sideways on a phone. */
  .portal-body { flex-direction: column; flex-wrap: nowrap; gap: 1.5rem; }
  /* Stacked, the rail is a strip across the top — so it bleeds to *both* side
     edges and its seam moves to the bottom. */
  .portal-aside {
    width: auto; flex-basis: auto;
    margin: calc(var(--p-gut-top) * -1) calc(var(--p-gut-x) * -1) 0;
  }
  .portal-nav { border-right: 0; border-bottom: 1px solid rgba(10, 10, 10, 0.14); }
  .portal-column { flex: 1 1 auto; }
  .portal-rail { flex-basis: auto; width: auto; }
  /* The rail folds into a tab strip: the header and the vertical spine are
     signatures of a *tall* column, so they go rather than being squashed. */
  .portal-nav { position: static; height: auto; min-height: 0; }
  .portal-nav__links { flex-direction: row; flex-wrap: wrap; padding: 0; }
  /* Stacked, the accent moves from the left edge to the bottom — the rows are
     side by side, so a left bar would mark the wrong axis. */
  .portal-nav__link {
    flex: 1 1 13rem; justify-content: center;
    border-left: 0; border-bottom: 3px solid transparent;
  }
  .portal-nav__link:hover { border-bottom-color: var(--p-rose); }
  .portal-nav__link.is-active { border-bottom-color: var(--p-ink); }
  .portal-nav__ext { margin-left: 0; }
  .portal-nav__foot { padding: 0.85rem 1.1rem; }
}
@media (max-width: 767.98px) {
  /* The hero pair goes one-per-row rather than shrinking: these are the two
     things the page most wants read, so they take the width instead of
     competing for half of it. */
  .portal-head__actions { width: 100%; }
  .portal-head__actions .portal-trialalert,
  .portal-head__actions .portal-btn--hero { flex: 1 1 100%; justify-content: center; }
  /* One control per row once two 56px buttons can't sit side by side. */
  .product-card__actions { grid-template-columns: 1fr; }
  .product-card__main { padding: 1.4rem 1.25rem 1.25rem; }
  .product-card__media { aspect-ratio: 16 / 9; }
  .product-card__price { text-align: left; }
  .product-card__name { font-size: 1.6rem; }
  .product-card__price-value { font-size: 1.6rem; }
}
@media (max-width: 575.98px) {
  /* Set the gutter, not the padding — the rail's bleed is derived from it. */
  .portal__wrap { --p-gut-x: 1.15rem; --p-gut-top: 1.5rem; --p-gut-bottom: 2.5rem; }
  .portal-dl > div { grid-template-columns: 1fr; gap: 0.1rem; }
  .portal-head__actions { width: 100%; }
  .idbadge__spine { flex-basis: 38px; }
  .idbadge__specs { grid-template-columns: 1fr; }
}

/* ======================================================================== *
 * ADMIN CONSOLE — dark, full-bleed data console (Supabase/Firebase-style).
 * Scoped entirely under `body.admin-console`, so the marketing site, auth
 * split-panel, and end-user portal keep the light brand theme untouched.
 *
 * On-brand twist on the reference look: charcoal surfaces, the Aethis pink
 * reserved for active/focus/links, Firmament for titles, and a monospace data
 * column + status "LED" dots as the signature — a hardware-licensing console.
 * ======================================================================== */
.admin-console {
  --con-bg:            #101216;  /* page */
  --con-surface:       #171a1f;  /* panels / cards */
  --con-surface-2:     #1c2026;  /* nested surfaces */
  --con-elevated:      #20242b;  /* thead, hover, toolbar */
  --con-border:        #262a31;  /* hairlines */
  --con-border-strong: #333842;  /* stronger dividers */
  --con-text:          #e7e9ee;
  --con-muted:         #969aa4;
  --con-faint:         #6a6e78;
  --con-accent:        #e8a0a8;  /* brand pink */
  --con-accent-deep:   #c87a86;
  --con-accent-tint:   rgba(232, 160, 168, 0.12);
  --con-sales-end:     #d99878;

  --con-ok:     #46c98a;
  --con-ok-bg:  rgba(70, 201, 138, 0.12);
  --con-warn:   #e0b341;
  --con-warn-bg:rgba(224, 179, 65, 0.12);
  --con-bad:    #e06a7e;
  --con-bad-bg: rgba(224, 106, 126, 0.12);

  /* Shared dark brand chrome (the AETHIS·DSP topbar + footer are the same
     components as the end-user portal). Those `.portal-*` rules read these
     tokens, which otherwise live only under `body.portal-shell`. */
  --p-topbar: #0a0a0c;
  --p-ink:    #0a0a0a;
  --p-hot:    #c4819a;
  --p-soft:   #f0c8d0;
}

/* --- Page canvas --------------------------------------------------------- */
body.admin-console {
  background: var(--con-bg);
  color: var(--con-text);
}
.admin-console .page,
.admin-console .page-wrapper { background: transparent; }
.admin-console .text-muted { color: var(--con-muted) !important; }
.admin-console .text-primary { color: var(--con-accent) !important; }
.admin-console code,
.admin-console .admin-mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  color: var(--con-text);
  background: var(--con-surface-2);
  border: 1px solid var(--con-border);
  border-radius: 6px;
  padding: 0.1em 0.4em;
  font-size: 0.85em;
}

/* Top bar + footer are the shared `.portal-topbar` / `.portal-footer` brand
   chrome (see the end-user portal); the admin console no longer renders the
   light Tabler navbar, so no `.admin-console .navbar` overrides are needed. */

/* --- Shell layout (fixed rail + fluid canvas) ---------------------------- */
.admin-shell {
  max-width: 1760px;
  margin: 0 auto;
  padding: 0 1.75rem 2rem;
}
.admin-grid {
  display: flex;
  align-items: flex-start;
  gap: 1.5rem;
}
.admin-grid > aside {
  flex: 0 0 256px;
  width: 256px;
  max-width: 256px;
  padding: 0;
}
.admin-main { flex: 1 1 auto; min-width: 0; }

/* --- Left rail ----------------------------------------------------------- */
.admin-console aside .card {
  position: sticky;
  top: 1rem;
  background: var(--con-surface);
  border: 1px solid var(--con-border);
  border-radius: 14px;
  box-shadow: 0 12px 32px -24px rgba(0, 0, 0, 0.8);
  overflow: hidden;
}
.admin-console aside .card-body { padding: 1rem 1.1rem; }
.admin-console aside .card-body .h3 { color: var(--con-text); margin-bottom: 0.1rem; font-size: 1.05rem; }
.admin-console aside .card-body .text-muted { color: var(--con-faint) !important; }
.admin-console .list-group-flush { border-top: 1px solid var(--con-border); }
.admin-console .list-group-item {
  background: transparent;
  border: 0;
  border-bottom: 1px solid transparent;
  color: var(--con-muted);
  padding: 0.55rem 1.1rem;
  font-size: 0.9rem;
  font-weight: 500;
  display: flex;
  align-items: center;
}
.admin-console .list-group-item i { color: var(--con-faint); transition: color 0.12s ease; }
.admin-console .list-group-item-action:hover:not(.active) {
  background: var(--con-elevated);
  color: var(--con-text);
}
.admin-console .list-group-item-action:hover:not(.active) i { color: var(--con-muted); }
.admin-console .list-group-item.active {
  background: var(--con-accent-tint);
  color: var(--con-accent);
  font-weight: 600;
  box-shadow: inset 3px 0 0 var(--con-accent);
}
.admin-console .list-group-item.active i { color: var(--con-accent); }
/* Section dividers in the rail */
.admin-console .list-group-item.bg-light {
  background: transparent !important;
  color: var(--con-faint) !important;
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  margin-top: 0.35rem;
  padding-bottom: 0.3rem;
  border-top: 1px solid var(--con-border);
}

/* --- Page header --------------------------------------------------------- */
.admin-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.25rem;
  flex-wrap: wrap;
  padding: 0.25rem 0 1.5rem;
}
.admin-head__text { min-width: 0; }
.admin-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 0.66rem;
  font-weight: 600;
  color: var(--con-faint);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.admin-eyebrow__dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--con-accent);
  box-shadow: 0 0 0 3px var(--con-accent-tint);
}
.admin-title {
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 700;
  font-size: 1.7rem;
  letter-spacing: -0.01em;
  color: #f3f4f6;
  margin: 0.35rem 0 0.2rem;
}
.admin-subtitle { color: var(--con-muted); font-size: 0.92rem; }
.admin-subtitle--sm { font-size: 0.82rem; margin-top: 0.35rem; }
.admin-crumb {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font-size: 0.8rem; color: var(--con-faint); margin-bottom: 0.15rem;
}
.admin-crumb a { color: var(--con-muted); }
.admin-crumb a:hover { color: var(--con-accent); }
.admin-head__actions { display: flex; gap: 0.5rem; flex-shrink: 0; flex-wrap: wrap; }

/* "Signed in as" chip on the dashboard */
.admin-whoami {
  display: flex; flex-direction: column;
  padding: 0.65rem 1rem;
  border: 1px solid var(--con-border);
  border-radius: 10px;
  background: var(--con-surface);
}
.admin-whoami__label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--con-faint); }
.admin-whoami__name { font-weight: 600; color: var(--con-text); }
.admin-whoami__role { font-size: 0.78rem; color: var(--con-muted); }

/* --- Stat tiles (dashboard) ---------------------------------------------- */
.admin-sales-goal {
  margin-bottom: 1rem;
  padding: 1.15rem 1.25rem;
  background: var(--con-surface);
  border: 1px solid var(--con-border);
  border-radius: 12px;
}
.admin-sales-goal__head,
.admin-sales-goal__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.admin-sales-goal__heading { margin: 0; font: inherit; }
.admin-sales-goal__label {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  color: var(--con-text);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-decoration: none;
  text-transform: uppercase;
}
.admin-sales-goal__label:hover { color: var(--con-accent); }
.admin-sales-goal__icon {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border-radius: 8px;
  color: var(--con-accent);
  background: var(--con-accent-tint);
  font-size: 1.05rem;
}
.admin-sales-goal__description {
  margin: 0.3rem 0 0 2.85rem;
  color: var(--con-muted);
  font-size: 0.8rem;
}
.admin-sales-goal__total {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  color: var(--con-muted);
  white-space: nowrap;
}
.admin-sales-goal__total strong {
  color: var(--con-text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 2rem;
  font-weight: 600;
  line-height: 1;
}
.admin-sales-goal__bar {
  width: 100%;
  height: 12px;
  display: block;
  margin: 1rem 0 0.8rem;
  overflow: hidden;
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: var(--con-surface-2);
}
.admin-sales-goal__bar::-webkit-progress-bar {
  border-radius: 999px;
  background: var(--con-surface-2);
}
.admin-sales-goal__bar::-webkit-progress-value {
  border-radius: 999px;
  background: linear-gradient(90deg, var(--con-accent), var(--con-sales-end));
}
.admin-sales-goal__bar::-moz-progress-bar {
  border-radius: 999px;
  background: linear-gradient(90deg, var(--con-accent), var(--con-sales-end));
}
.admin-sales-goal__remaining {
  color: var(--con-muted);
  font-size: 0.8rem;
}
.admin-sales-goal__remaining .bx { color: var(--con-ok); }
.admin-sales-goal__form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.admin-sales-goal__form label {
  color: var(--con-muted);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.admin-goal-input {
  width: 92px;
  min-height: 32px;
  padding: 0.3rem 0.55rem;
  color: var(--con-text);
  background: var(--con-surface-2);
  border: 1px solid var(--con-border-strong);
  border-radius: 7px;
  font: 0.82rem ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.admin-goal-input:focus {
  outline: 0;
  border-color: var(--con-accent);
  box-shadow: 0 0 0 3px var(--con-accent-tint);
}

.admin-stats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1rem;
}
.admin-stat {
  display: flex; flex-direction: column;
  padding: 1.1rem 1.2rem;
  background: var(--con-surface);
  border: 1px solid var(--con-border);
  border-radius: 12px;
  text-decoration: none;
  transition: border-color 0.12s ease, transform 0.12s ease, background 0.12s ease;
}
.admin-stat:hover {
  border-color: var(--con-border-strong);
  background: var(--con-surface-2);
  transform: translateY(-2px);
}
.admin-stat__top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem; }
.admin-stat__label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--con-faint); }
.admin-stat__icon {
  width: 30px; height: 30px; border-radius: 8px;
  display: grid; place-items: center; font-size: 1.05rem;
  color: var(--con-accent); background: var(--con-accent-tint);
}
.admin-stat__value {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 2rem; font-weight: 600; line-height: 1; color: #f3f4f6;
}
.admin-stat__desc { font-size: 0.8rem; color: var(--con-muted); margin-top: 0.35rem; }
.admin-stat__warn { font-size: 0.74rem; color: var(--con-warn); margin-top: 0.4rem; }

/* --- Panels (cards) ------------------------------------------------------ */
.admin-console .card,
.admin-panel {
  background: var(--con-surface);
  border: 1px solid var(--con-border);
  border-radius: 14px;
  box-shadow: 0 14px 38px -28px rgba(0, 0, 0, 0.85);
}
.admin-panel { overflow: hidden; }
.admin-panel__head {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  padding: 0.95rem 1.25rem;
  border-bottom: 1px solid var(--con-border);
}
.admin-panel__head--stack { display: block; }
.admin-panel__title {
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 1rem; font-weight: 700; color: var(--con-text); margin: 0;
  display: flex; align-items: center; gap: 0.5rem;
}
.admin-panel__pad { padding: 1.25rem; }
.admin-pill {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.4rem; height: 1.4rem; padding: 0 0.45rem;
  border-radius: 999px; font-size: 0.72rem; font-weight: 600;
  background: var(--con-elevated); color: var(--con-muted); border: 1px solid var(--con-border);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.admin-panel__foot {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 0.8rem 1.25rem;
  border-top: 1px solid var(--con-border);
  background: var(--con-surface);
}

/* --- Toolbar (filters + count) ------------------------------------------- */
.admin-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--con-border);
  background: var(--con-surface);
}
.admin-filters { display: flex; align-items: flex-end; gap: 0.6rem; flex-wrap: wrap; }
/* Each filter is a labelled control, not a bare box: with six of them on the
   licences table, placeholder-only inputs gave no way to tell which column a
   value belonged to. */
.admin-filter { display: flex; flex-direction: column; gap: 0.25rem; min-width: 0; }
.admin-filter__label {
  font-size: 0.66rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--con-faint);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.admin-filter .admin-select,
.admin-filter .admin-input { min-width: 9.5rem; max-width: 14rem; padding: 0.32rem 0.55rem; }
.admin-filter--actions { flex-direction: row; align-items: center; gap: 0.4rem; }

/* --- /stats board -------------------------------------------------------- */
/* Meters first, then the operator-owned panels. `auto-fit` + a minimum rather
   than a fixed column count: three meters sit in a row on a desktop console and
   stack cleanly on a laptop without a breakpoint per arrangement. */
.admin-meters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: 1rem;
  margin-bottom: 1rem;
}
.admin-meter { padding: 1.1rem 1.25rem 1.25rem; }
.admin-meter__head {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  margin-bottom: 0.85rem;
}
.admin-meter__title {
  display: flex; align-items: center; gap: 0.55rem; margin: 0;
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 0.95rem; font-weight: 700; color: var(--con-text);
}
.admin-meter__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.85rem; height: 1.85rem; border-radius: 9px;
  background: var(--con-accent-tint); color: var(--con-accent); font-size: 1.05rem;
}
.admin-meter__link {
  display: inline-flex; align-items: center; gap: 0.15rem;
  font-size: 0.78rem; color: var(--con-muted); text-decoration: none;
}
.admin-meter__link:hover { color: var(--con-accent); }
.admin-meter__value {
  display: flex; align-items: baseline; gap: 0.5rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 2.1rem; font-weight: 600; line-height: 1; color: #f3f4f6;
  margin-bottom: 0.7rem;
}
.admin-meter__goal { font-size: 0.8rem; font-weight: 400; color: var(--con-faint); }
.admin-meter__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.75rem; flex-wrap: wrap;
  margin-top: 0.6rem; font-size: 0.78rem; color: var(--con-muted);
}
.admin-meter__reached { color: var(--con-ok); display: inline-flex; align-items: center; gap: 0.3rem; }
.admin-meter__deltas { display: flex; gap: 0.85rem; flex-wrap: wrap; color: var(--con-faint); }
.admin-meter__deltas strong { color: var(--con-text); font-weight: 600; }
.admin-meter__split { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-top: 0.8rem; }
.admin-pill-stat {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.2rem 0.55rem; border-radius: 999px;
  border: 1px solid var(--con-border); background: var(--con-elevated);
  font-size: 0.72rem; color: var(--con-muted);
}
.admin-pill-stat strong {
  color: var(--con-text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.admin-meter__note { margin: 0.75rem 0 0; font-size: 0.72rem; color: var(--con-faint); line-height: 1.5; }
.admin-meter__error {
  padding: 0.65rem 0.85rem; border-radius: 10px;
  border: 1px solid rgba(224, 179, 65, 0.3);
  background: var(--con-warn-bg); color: #f0d394; font-size: 0.82rem;
}
.admin-panel--pad { margin-bottom: 1rem; }

/* Manual counters: each row is label / value / note / order, editable in place
   so a number can be corrected without a round trip through another screen. */
.admin-counter-grid { display: grid; gap: 0.5rem; }
.admin-counter { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.admin-counter__form { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; flex: 1 1 auto; }
.admin-counter .admin-input { padding: 0.32rem 0.55rem; }
.admin-counter__label { flex: 1 1 11rem; min-width: 9rem; }
.admin-counter__value { flex: 0 0 6rem; }
.admin-counter__note { flex: 2 1 12rem; min-width: 9rem; }
.admin-counter__pos { flex: 0 0 4.5rem; }
.admin-counter--new {
  margin-top: 0.85rem; padding-top: 0.85rem;
  border-top: 1px dashed var(--con-border);
}
.admin-counter--new .admin-input { flex: 1 1 8rem; }
.admin-counter__readonly { display: flex; align-items: baseline; gap: 0.5rem; }
.admin-counter__readonly strong {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 1.1rem; color: var(--con-text);
}
.admin-counter__readonly span { color: var(--con-muted); font-size: 0.85rem; }
.admin-counter__readonly em { color: var(--con-faint); font-size: 0.78rem; font-style: normal; }
.admin-targets { display: flex; align-items: flex-end; gap: 0.75rem; flex-wrap: wrap; }
.admin-targets .admin-goal-input {
  background: var(--con-surface-2);
  border: 1px solid var(--con-border);
  color: var(--con-text);
  border-radius: 9px;
  font-size: 0.875rem;
  padding: 0.32rem 0.55rem;
  width: 9rem;
}
@media (max-width: 575.98px) {
  .admin-counter__form { flex-direction: column; align-items: stretch; }
  .admin-counter__form > * { flex: 1 1 auto; width: 100%; }
}
.admin-toolbar__right { display: flex; align-items: center; gap: 0.75rem; }
.admin-count {
  font-size: 0.8rem; color: var(--con-muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  white-space: nowrap;
}
.admin-search { position: relative; display: flex; align-items: center; }
.admin-search > i {
  position: absolute; left: 0.7rem; color: var(--con-faint); font-size: 1rem; pointer-events: none;
}
.admin-input, .admin-select,
.admin-console .form-control,
.admin-console .form-select {
  background: var(--con-surface-2);
  border: 1px solid var(--con-border);
  color: var(--con-text);
  border-radius: 9px;
  font-size: 0.875rem;
}
.admin-input {
  padding: 0.45rem 0.7rem 0.45rem 2rem;
  min-width: 200px;
}
.admin-select { padding: 0.45rem 1.8rem 0.45rem 0.7rem; }
.admin-input::placeholder { color: var(--con-faint); }
.admin-input:focus, .admin-select:focus,
.admin-console .form-control:focus,
.admin-console .form-select:focus {
  outline: none;
  background: var(--con-surface-2);
  border-color: var(--con-accent);
  box-shadow: 0 0 0 3px var(--con-accent-tint);
  color: var(--con-text);
}

/* --- Data table ---------------------------------------------------------- */
.admin-console .admin-table {
  margin: 0;
  width: 100%;
  color: var(--con-text);
  --bs-table-bg: transparent;
  --bs-table-color: var(--con-text);
  --bs-table-border-color: var(--con-border);
}
.admin-console .admin-table > :not(caption) > * > * {
  background: transparent;
  color: var(--con-text);
  box-shadow: none;
  border-color: var(--con-border);
}
.admin-console .admin-table > thead > tr > th {
  position: sticky; top: 0; z-index: 2;
  background: var(--con-elevated);
  color: var(--con-faint);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.68rem;
  font-weight: 600;
  padding: 0.7rem 1.25rem;
  border-bottom: 1px solid var(--con-border-strong);
  white-space: nowrap;
}
.admin-console .admin-table > tbody > tr > td {
  padding: 0.8rem 1.25rem;
  border-top: 1px solid var(--con-border);
  vertical-align: middle;
  font-size: 0.9rem;
}
.admin-console .admin-table > tbody > tr:first-child > td { border-top: 0; }
.admin-console .admin-table > tbody > tr:hover > td { background: var(--con-elevated); }
.admin-cell-id {
  width: 1%;
  white-space: nowrap;
}
.admin-cell-id > span {
  display: inline-block;
  max-width: 14ch;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.82rem;
  color: var(--con-faint);
}
/* "Who did this" cell: the person's name reads first, with a short, copyable
   id chip beside it so two customers sharing a name stay distinguishable. */
.admin-who {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}
.admin-who__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.admin-idchip {
  flex: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.72rem;
  line-height: 1;
  padding: 0.22rem 0.4rem;
  border-radius: 0.3rem;
  border: 1px solid var(--con-border);
  background: var(--con-elevated);
  color: var(--con-faint);
  text-decoration: none;
}
a.admin-idchip:hover { color: var(--con-accent); border-color: var(--con-border-strong); }

.admin-td-empty { text-align: center; color: var(--con-muted); padding: 2rem 1.25rem !important; }
.admin-dash { color: var(--con-faint); }
.admin-mono-cell {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.82rem;
  color: var(--con-muted);
}

/* Status "LED" pills */
.admin-status {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.16rem 0.6rem 0.16rem 0.5rem;
  border-radius: 999px;
  font-size: 0.74rem; font-weight: 600;
  text-transform: capitalize;
  background: var(--con-elevated); color: var(--con-muted);
  border: 1px solid var(--con-border);
}
.admin-status > i { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.admin-status--active, .admin-status--alive,
.admin-status--available, .admin-status--enabled,
.admin-status--succeeded, .admin-status--paid {
  background: var(--con-ok-bg); color: var(--con-ok); border-color: transparent;
}
.admin-status--expiring, .admin-status--inactive,
.admin-status--idle, .admin-status--not_started,
.admin-status--processing, .admin-status--pending,
.admin-status--requires_payment_method, .admin-status--requires_customer_action {
  background: var(--con-warn-bg); color: var(--con-warn); border-color: transparent;
}
.admin-status--expired, .admin-status--suspended, .admin-status--banned,
.admin-status--dead, .admin-status--revoked, .admin-status--cancelled,
.admin-status--failed {
  background: var(--con-bad-bg); color: var(--con-bad); border-color: transparent;
}

/* Settlement column. Not a status of the payment itself — it says whether the
   money has reached the bank, which lags the capture by ~18-21 days. */
.admin-settle { font-size: 0.78rem; font-weight: 500; }
.admin-settle--ok { color: var(--con-ok); }
.admin-settle--muted { color: var(--con-muted); }
.admin-settle--bad { color: var(--con-bad); font-weight: 600; }

/* --- Pager --------------------------------------------------------------- */
.admin-pager { display: flex; gap: 0.4rem; }
.admin-pager__btn {
  display: inline-flex; align-items: center; gap: 0.2rem;
  padding: 0.4rem 0.75rem;
  border: 1px solid var(--con-border);
  border-radius: 8px;
  font-size: 0.82rem; font-weight: 500;
  color: var(--con-text);
  background: var(--con-surface-2);
  transition: background 0.12s ease, border-color 0.12s ease;
}
.admin-pager__btn:hover { background: var(--con-elevated); color: var(--con-text); border-color: var(--con-border-strong); }
.admin-pager__btn.is-disabled { opacity: 0.4; pointer-events: none; }

/* --- Definition list (account / attributes) ------------------------------ */
.admin-dl { margin: 0; }
.admin-dl > div {
  display: grid; grid-template-columns: 12rem 1fr; gap: 1rem;
  padding: 0.7rem 1.25rem;
}
.admin-dl > div + div { border-top: 1px solid var(--con-border); }
.admin-dl dt { margin: 0; font-weight: 500; color: var(--con-muted); font-size: 0.88rem; }
.admin-dl dd { margin: 0; color: var(--con-text); word-break: break-word; }
.admin-dl dd pre { background: var(--con-surface-2); border: 1px solid var(--con-border); border-radius: 8px; padding: 0.6rem 0.75rem; color: var(--con-text); margin: 0; }
.admin-dl__empty { color: var(--con-muted); }

/* --- Release board: a folder per product, panels per platform inside ------ */
/* The Installers page (keygen_api/installers.html) is a set of product folders.
   Each is a <details>, so opening and closing needs no JavaScript — the page
   runs under a CSP with no inline script, and a folder that fails to open is a
   folder whose builds cannot be published. */
.admin-folder {
  border: 1px solid var(--con-border);
  border-radius: 14px;
  background: var(--con-surface);
  margin-bottom: 1rem;
  overflow: hidden;
}
.admin-folder__head {
  display: flex; align-items: center; gap: 0.65rem; flex-wrap: wrap;
  padding: 0.9rem 1.1rem;
  cursor: pointer;
  user-select: none;
  /* The default disclosure triangle is replaced by the chevron below, which can
     be positioned and coloured with the rest of the console. */
  list-style: none;
}
.admin-folder__head::-webkit-details-marker { display: none; }
.admin-folder__head:hover { background: var(--con-elevated); }
.admin-folder__head:focus-visible {
  outline: 2px solid var(--con-accent);
  outline-offset: -2px;
}
.admin-folder__chevron {
  color: var(--con-faint); font-size: 1.1rem;
  transition: transform 0.15s ease;
}
.admin-folder[open] > .admin-folder__head > .admin-folder__chevron { transform: rotate(90deg); }
.admin-folder[open] > .admin-folder__head { border-bottom: 1px solid var(--con-border); }
.admin-folder__icon { color: var(--con-accent); font-size: 1.05rem; }
.admin-folder__name {
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 1.05rem; font-weight: 700; color: var(--con-text);
}
.admin-folder__meta {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--con-faint);
  /* Pushes the status chips to the right edge, whatever the product name's
     length — they read as a column down the page that way. */
  margin-right: auto;
}
.admin-folder__body { padding: 1.1rem 1.1rem 0.1rem; background: var(--con-bg); }
/* The panels inside a folder sit on the folder's own ground, so they need the
   nested surface rather than the same colour they would use standing alone. */
.admin-folder__body > .admin-panel { background: var(--con-surface-2); }
@media (prefers-reduced-motion: reduce) {
  .admin-folder__chevron { transition: none; }
}

/* The live build's own block. Not .admin-dl: that is a two-column
   description-list grid, and these are plain lines of prose about one file —
   in the grid they were squeezed into its 12rem first column. */
.admin-live {
  border: 1px solid var(--con-border);
  border-radius: 10px;
  background: var(--con-surface-2);
  padding: 0.85rem 1rem;
}
.admin-live > div { min-width: 0; word-break: break-word; }

/* --- Search query row ---------------------------------------------------- */
.admin-query { display: flex; align-items: flex-end; gap: 0.75rem; flex-wrap: wrap; }
.admin-field { display: flex; flex-direction: column; gap: 0.35rem; }
.admin-field--grow { flex: 1 1 16rem; min-width: 12rem; }
.admin-flabel { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--con-faint); font-weight: 600; }
.admin-ferror { color: var(--con-bad); font-size: 0.82rem; margin-top: 0.6rem; }
/* SearchForm renders bare Django widgets (no .form-control), so style them here. */
.admin-query input,
.admin-query select {
  width: 100%;
  background: var(--con-surface-2);
  border: 1px solid var(--con-border);
  color: var(--con-text);
  border-radius: 9px;
  padding: 0.5rem 0.7rem;
  font-size: 0.875rem;
}
.admin-query input::placeholder { color: var(--con-faint); }
.admin-query input:focus,
.admin-query select:focus {
  outline: none;
  border-color: var(--con-accent);
  box-shadow: 0 0 0 3px var(--con-accent-tint);
}

/* --- Empty states -------------------------------------------------------- */
.admin-empty { text-align: center; padding: 3rem 1.5rem; }
.admin-empty--row { padding: 2.5rem 1.5rem; }
.admin-empty__icon {
  width: 56px; height: 56px; margin: 0 auto 0.9rem; border-radius: 14px;
  display: grid; place-items: center; font-size: 1.6rem;
  color: var(--con-accent); background: var(--con-accent-tint);
}
.admin-empty__title { font-weight: 600; color: var(--con-text); margin: 0 0 0.35rem; }
.admin-empty__text { color: var(--con-muted); max-width: 40ch; margin: 0 auto; font-size: 0.9rem; }

/* --- Buttons on dark ----------------------------------------------------- */
.admin-console .btn-outline-secondary {
  border-color: var(--con-border-strong);
  color: var(--con-text);
  background: transparent;
}
.admin-console .btn-outline-secondary:hover {
  background: var(--con-elevated);
  border-color: var(--con-border-strong);
  color: var(--con-text);
}
.admin-console .btn-ghost,
.btn-ghost {
  border: 1px solid var(--con-border-strong);
  color: var(--con-text);
  background: transparent;
}
.admin-console .btn-ghost:hover { background: var(--con-elevated); color: var(--con-text); }

/* The global `.btn-primary` is brand pink with white text — fine on the light
   marketing site, but on the console it reads as a blank white pill: #fff on
   #e8a0a8 is about 1.9:1, so the label is effectively invisible. Keep the pink
   fill, put the dark ink back on it (~12:1). */
.admin-console .btn-primary {
  background-color: var(--con-accent);
  border-color: var(--con-accent);
  color: var(--p-ink);
}
.admin-console .btn-primary:hover,
.admin-console .btn-primary:focus,
.admin-console .btn-primary:active,
.admin-console .btn-primary.active {
  background-color: var(--con-accent-deep) !important;
  border-color: var(--con-accent-deep) !important;
  color: var(--p-ink) !important;
}
.admin-console .btn-primary:disabled,
.admin-console .btn-primary.disabled {
  background-color: var(--con-border-strong);
  border-color: var(--con-border-strong);
  color: var(--con-faint);
  opacity: 1;
}

/* `.btn-ghost` sets its own colour, which out-specifies the `.text-danger`
   utility (0,2,0 vs 0,1,0) — so destructive buttons were rendering in plain
   body text and did not read as destructive at all. */
.admin-console .btn-ghost.text-danger { color: var(--con-bad) !important; }
.admin-console .btn-ghost.text-danger:hover {
  background: var(--con-bad-bg);
  border-color: var(--con-bad);
  color: var(--con-bad) !important;
}
.admin-console .btn-icon,
.admin-console .admin-rowmenu {
  color: var(--con-muted);
  background: transparent;
  border: 1px solid transparent;
}
.admin-console .btn-icon:hover,
.admin-console .admin-rowmenu:hover { background: var(--con-elevated); color: var(--con-text); }

/* --- Dropdown menus (row actions + profile) ------------------------------ */
.admin-console .dropdown-menu {
  background: var(--con-surface-2);
  border: 1px solid var(--con-border-strong);
  box-shadow: 0 18px 44px -18px rgba(0, 0, 0, 0.9);
}
.admin-console .dropdown-item { color: var(--con-text); }
.admin-console .dropdown-item:hover,
.admin-console .dropdown-item:focus { background: var(--con-elevated); color: var(--con-text); }
.admin-console .dropdown-item.text-danger,
.admin-console .dropdown-item.text-danger:hover { color: var(--con-bad) !important; }
.admin-console .dropdown-divider { border-color: var(--con-border); }
.admin-console .dropdown-header { color: var(--con-faint); }

/* Row-action menus are promoted to the top layer (see project.js). The UA
   stylesheet gives [popover] its own dialog-ish box — centred by `inset: 0` +
   `margin: auto`, with a solid border and padding — all of which has to be
   undone before it can read as a dropdown. `inset: auto` is what lets the
   inline left/top the compiler computes actually position it. */
.admin-console .dropdown-menu[popover] {
  position: fixed;
  inset: auto;
  margin: 0;
  padding: 0.25rem 0;
  width: auto;
  max-width: min(20rem, calc(100vw - 1rem));
  overflow: visible;
  color: var(--con-text);
}
/* An `auto` popover paints a backdrop; a dropdown must not dim the page behind
   it, but the element still needs to catch the outside click that dismisses it. */
.admin-console .dropdown-menu[popover]::backdrop { background: transparent; }

/* --- Destructive confirm dialog ------------------------------------------ */
/* Shown in a 650px Unpoly overlay. The old markup put a Bootstrap card inside a
   `col-lg-6`, which halved that to ~325px of light-theme card on a dark backdrop
   — the reason this dialog was unreadable. This fills the overlay and speaks the
   console's own language. */
.admin-confirm { padding: 0.25rem 0.25rem 0; }
.admin-confirm__head { margin-bottom: 0.9rem; }
.admin-eyebrow--danger { color: var(--con-bad); }
.admin-eyebrow--danger .admin-eyebrow__dot {
  background: var(--con-bad);
  box-shadow: 0 0 0 3px var(--con-bad-bg);
}
.admin-confirm__title {
  font-family: "Firmament", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 700; font-size: 1.35rem; letter-spacing: -0.01em;
  color: #f3f4f6; margin: 0.4rem 0 0;
}
.admin-confirm__lead {
  color: var(--con-muted); font-size: 0.92rem; line-height: 1.55; margin: 0 0 1.1rem;
}
.admin-confirm__lead strong { color: var(--con-text); font-weight: 600; }

/* The identity of the record being destroyed, as a label/value ledger. Grid
   rather than a `dl.row`, so a long key or UUID wraps in its own column instead
   of shoving the label out of alignment. */
.admin-confirm__facts {
  display: grid; gap: 0.1rem; margin: 0 0 1.1rem;
  border: 1px solid var(--con-border);
  border-radius: 10px;
  background: var(--con-surface-2);
  padding: 0.85rem 1rem;
}
.admin-confirm__facts > div {
  display: grid; grid-template-columns: 5.5rem minmax(0, 1fr); gap: 0.75rem;
  padding: 0.3rem 0;
}
.admin-confirm__facts dt {
  font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--con-faint);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  padding-top: 0.1rem;
}
.admin-confirm__facts dd {
  margin: 0; color: var(--con-text); font-size: 0.9rem;
  overflow-wrap: anywhere;
}
.admin-confirm__warn {
  display: flex; align-items: center; gap: 0.55rem;
  padding: 0.65rem 0.85rem; margin: 0 0 1.25rem;
  border: 1px solid rgba(224, 106, 126, 0.3);
  border-radius: 10px;
  background: var(--con-bad-bg);
  color: #f3b3bd; font-size: 0.85rem; font-weight: 500;
}
.admin-confirm__warn i { font-size: 1.05rem; flex: none; }
.admin-confirm__actions {
  display: flex; justify-content: flex-end; gap: 0.6rem;
  padding-top: 0.25rem;
}
@media (max-width: 575.98px) {
  .admin-confirm__facts > div { grid-template-columns: 1fr; gap: 0.15rem; }
  .admin-confirm__actions { flex-direction: column-reverse; }
  .admin-confirm__actions > * { width: 100%; }
}

/* --- Radio / check controls in console modals ---------------------------- */
/* crispy renders these as Bootstrap `.form-check`; without these they inherit
   the light theme's near-white borders and vanish against the console surface. */
.admin-console .form-check-label { color: var(--con-text); font-size: 0.9rem; }
.admin-console .form-check-input {
  background-color: var(--con-surface-2);
  border-color: var(--con-border-strong);
}
.admin-console .form-check-input:checked {
  background-color: var(--con-accent);
  border-color: var(--con-accent);
}
.admin-console .form-check-input:focus {
  border-color: var(--con-accent);
  box-shadow: 0 0 0 0.2rem var(--con-accent-tint);
}

/* --- Badges / code / alerts on dark -------------------------------------- */
.admin-console .badge.bg-secondary-lt { background: var(--con-elevated) !important; color: var(--con-muted) !important; }
.admin-console .badge.bg-success-lt { background: var(--con-ok-bg) !important; color: var(--con-ok) !important; }
.admin-console .badge.bg-danger-lt { background: var(--con-bad-bg) !important; color: var(--con-bad) !important; }
.admin-console .alert-warning { background: var(--con-warn-bg); border-color: rgba(224, 179, 65, 0.3); color: #f0d394; }
.admin-console .alert-danger { background: var(--con-bad-bg); border-color: rgba(224, 106, 126, 0.3); color: #f3b3bd; }
.admin-console .alert-info { background: var(--con-accent-tint); border-color: rgba(232, 160, 168, 0.3); color: var(--con-accent); }

/* --- Forms inside modal create/edit (crispy) ----------------------------- */
.admin-console .form-label,
.admin-console .col-form-label { color: var(--con-muted); }
.admin-console .form-section > legend { color: var(--con-accent); border-bottom-color: var(--con-border); }
.admin-console .resource-form-card .card-header { border-bottom-color: var(--con-border); }
.admin-console .card-title { color: var(--con-text); }
.admin-console .card-header { background: transparent; border-bottom-color: var(--con-border); }
.admin-console .ts-control {
  background: var(--con-surface-2);
  border-color: var(--con-border);
  color: var(--con-text);
}
.admin-console .ts-control .item { color: var(--con-text); }
.admin-console .ts-dropdown {
  background: var(--con-surface-2);
  border-color: var(--con-border-strong);
  color: var(--con-text);
}
.admin-console .ts-dropdown .active { background: var(--con-elevated); color: var(--con-text); }

/* Unpoly modal box matches the console surface so create/edit forms blend in */
.admin-console up-modal-box,
.admin-console up-drawer-box {
  background: var(--con-surface);
  color: var(--con-text);
}

/* Footer is now the shared `.portal-footer` (see brand chrome), so the old
   `.admin-console .aethis-footer` dark overrides are no longer needed. */

/* --- Responsive ---------------------------------------------------------- */
@media (max-width: 991.98px) {
  .admin-grid { flex-direction: column; align-items: stretch; }
  .admin-grid > aside { flex: 1 1 auto; width: 100%; max-width: 100%; min-width: 0; }
  .admin-main { width: 100%; }
  .admin-console aside .card { position: static; }
  .admin-shell { padding: 0 1rem 2rem; }
}
@media (max-width: 575.98px) {
  .admin-head { flex-direction: column; align-items: flex-start; }
  .admin-toolbar { flex-direction: column; align-items: stretch; }
  .admin-filters { width: 100%; }
  .admin-input { min-width: 0; flex: 1; }
  .admin-stats { grid-template-columns: 1fr 1fr; }
  .admin-sales-goal__head,
  .admin-sales-goal__foot { align-items: flex-start; flex-direction: column; }
  .admin-sales-goal__form { width: 100%; flex-wrap: wrap; }
}

/* --- Purchase / licence notice modal ------------------------------------- */
/* One-shot dialog shown after a payment webhook fires. Same warm-washi card
   language as the rest of the portal: hairline borders, mono eyebrow, rose
   accent on the full stop. */
.notice-modal {
  /* Below .toast-stack (1080) so transient toasts stay readable over the
     backdrop, matching Bootstrap's toast-above-modal convention. Equal
     z-indexes here would leave the stacking order up to DOM position. */
  position: fixed; inset: 0; z-index: 1070;
  display: grid; place-items: center; padding: 1.25rem;
}
.notice-modal--closed { display: none; }
/* The modal is aria-modal; the page behind it must not scroll away underneath.
   Set by project.js while the dialog is up and removed when it is dismissed. */
body.notice-modal-open { overflow: hidden; }
.notice-modal__backdrop {
  position: absolute; inset: 0;
  background: rgba(10, 10, 10, 0.55);
  backdrop-filter: blur(3px);
}
.notice-modal__card {
  /* Sized to carry the moment. This dialog is the only thing on screen when a
     purchase lands or a refund clears, and at 34rem it read as a cookie
     banner — the licence key, the one thing the buyer came for, sat in a strip
     narrower than the table behind it. */
  position: relative; width: min(46rem, 100%);
  max-height: calc(100vh - 3rem); overflow-y: auto;
  background: var(--p-surface); color: var(--p-ink);
  border: 1px solid var(--p-line-3); border-radius: 1.25rem 0.5rem 1.25rem 0.5rem;
  padding: 2.6rem 2.75rem 2.35rem;
  box-shadow: 0 40px 80px -30px rgba(10, 10, 10, 0.6);
  animation: notice-modal-in 220ms cubic-bezier(0.2, 0.8, 0.3, 1) both;
}
@keyframes notice-modal-in {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .notice-modal__card { animation: none; }
}

.notice-modal__eyebrow {
  display: inline-flex; align-items: center; gap: 0.6rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  text-transform: uppercase; letter-spacing: 0.3em; font-size: 0.72rem; font-weight: 600;
  color: var(--p-faint);
}
.notice-modal__dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--p-rose); box-shadow: 0 0 0 3px var(--p-tint);
}
.notice-modal__eyebrow--warn .notice-modal__dot {
  background: var(--p-bad); box-shadow: 0 0 0 3px var(--p-bad-bg);
}
.notice-modal__title {
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 500; letter-spacing: -0.02em; line-height: 1.08;
  font-size: clamp(1.75rem, 3.4vw, 2.35rem);
  color: var(--p-ink); margin: 0.8rem 0 0;
}
.notice-modal__stop { color: var(--p-rose); }
.notice-modal__sub {
  margin: 0.95rem 0 0; color: var(--p-muted);
  /* Held to a readable measure rather than the card's full width — long lines
     on a 46rem dialog are harder to read than the extra width is worth. */
  font-size: 1.02rem; line-height: 1.6; max-width: 52ch;
}
.notice-modal__sub + .notice-modal__sub { margin-top: 0.7rem; }
.notice-modal__reason {
  margin: 1rem 0 0; padding: 0.7rem 0.9rem;
  background: var(--p-inset); border: 1px solid var(--p-line-2); border-radius: 0.55rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8rem; color: var(--p-muted); overflow-wrap: anywhere;
}

/* Key block — masked by default, revealed by the eye toggle. */
.notice-modal__keyblock {
  margin-top: 1.75rem; padding: 1.25rem 1.4rem 1.3rem;
  background: linear-gradient(135deg, rgba(240, 200, 208, 0.55), rgba(232, 160, 168, 0.3));
  border: 1px solid rgba(10, 10, 10, 0.2); border-radius: 0.9rem 0.3rem 0.9rem 0.3rem;
}
.notice-modal__keylabel {
  display: block; text-transform: uppercase; letter-spacing: 0.16em;
  font-size: 0.68rem; font-weight: 700; color: var(--p-rose-ink); margin-bottom: 0.6rem;
}
.notice-modal__keyrow { display: flex; align-items: center; gap: 0.5rem; }
.notice-modal__key {
  flex: 1; min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.05rem; letter-spacing: 0.04em;
  background: #ffffff; border: 1px solid rgba(10, 10, 10, 0.16);
  border-radius: 0.55rem; padding: 0.7rem 0.85rem; color: var(--p-ink);
  overflow-wrap: anywhere;
}
.notice-modal__iconbtn {
  flex-shrink: 0; display: grid; place-items: center;
  width: 2.75rem; height: 2.75rem;
  background: #ffffff; color: var(--p-ink);
  border: 1px solid rgba(10, 10, 10, 0.16); border-radius: 0.55rem;
  cursor: pointer; transition: background 140ms ease, color 140ms ease;
}
.notice-modal__iconbtn:hover { background: var(--p-tint); color: var(--p-ink); }
.notice-modal__iconbtn i { font-size: 1.2rem; line-height: 1; }
.notice-modal__iconbtn.is-copied { background: var(--p-ink); color: var(--p-soft); border-color: var(--p-ink); }
.notice-modal__iconbtn.is-copied i { font-size: 1.05rem; }
.notice-modal__hint { margin: 0.85rem 0 0; font-size: 0.88rem; color: var(--p-muted); }

.notice-modal__actions {
  /* Full-width hairline over the buttons. The copy is held to a reading
     measure, so without a rule spanning the card the right third reads as
     dead space rather than margin. Same device as the portal's card footers. */
  display: flex; gap: 0.7rem; flex-wrap: wrap;
  margin-top: 2rem; padding-top: 1.6rem;
  border-top: 1px solid var(--p-line-2);
}
.notice-modal__btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
  padding: 0.75rem 1.5rem; border-radius: 0.6rem;
  font-size: 0.96rem; font-weight: 500; text-decoration: none; cursor: pointer;
  background: #ffffff; color: var(--p-ink); border: 1px solid var(--p-line-3);
  transition: background 140ms ease, border-color 140ms ease;
}
.notice-modal__btn:hover { background: var(--p-tint); border-color: var(--p-ink); color: var(--p-ink); }
.notice-modal__btn--primary {
  background: var(--p-ink); color: #ffffff; border-color: var(--p-ink);
}
.notice-modal__btn--primary:hover { background: #241d20; border-color: #241d20; color: var(--p-soft); }
/* Store shut — see .portal-btn--off. */
.notice-modal__btn--off,
.notice-modal__btn--off:hover,
.notice-modal__btn--off:focus {
  background: var(--p-inset); color: var(--p-muted); border-color: var(--p-line);
  cursor: not-allowed; opacity: 1;
}
.notice-modal__status { flex-basis: 100%; font-size: 0.85rem; color: var(--p-warn); }

/* "Don't show this again" — deliberately the quietest thing in the dialog.
   It's a real control, so it stays legible and keyboard-reachable, but it is
   not competing with the two decisions above it. */
.notice-modal__optout { margin: 1rem 0 0; }
.notice-modal__optout-btn {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.25rem 0; border: 0; background: none; cursor: pointer;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.66rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.14em; color: var(--p-faint);
  border-bottom: 1px solid transparent;
  transition: color 140ms ease, border-color 140ms ease;
}
.notice-modal__optout-btn:hover { color: var(--p-ink); border-bottom-color: var(--p-line-3); }
.notice-modal__optout-btn i { font-size: 0.95rem; }

/* Free-trial reminder. Same shell, one addition: the days-left counter, set
   at display scale in the top-right corner so the number lands before the
   sentence does. Days only — an hours-and-minutes countdown is a pressure
   tactic, and it's not what the customer plans around. */
.notice-modal__card--trial { padding-right: 9.5rem; }
.trial-countdown {
  position: absolute; top: 1.9rem; right: 2rem;
  display: flex; align-items: baseline; gap: 0.45rem;
  padding: 0.55rem 0.9rem;
  border-radius: 0.9rem 0.3rem 0.9rem 0.3rem;
  border: 1px solid rgba(176, 106, 120, 0.32);
  background: linear-gradient(180deg, #fffdfe 0%, #fbe9ee 100%);
}
.trial-countdown__num {
  font-size: 2.4rem; font-weight: 700; line-height: 1;
  letter-spacing: -0.03em; color: var(--p-ink);
}
.trial-countdown__unit {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.6rem; font-weight: 600; line-height: 1.25;
  text-transform: uppercase; letter-spacing: 0.16em; color: var(--p-rose-ink);
}

@media (max-width: 767.98px) {
  .notice-modal { padding: 1rem; }
  .notice-modal__card { padding: 1.8rem 1.5rem 1.6rem; }
  .notice-modal__card--trial { padding-right: 8rem; }
  .trial-countdown { top: 1.4rem; right: 1.4rem; }
}
@media (max-width: 575.98px) {
  .notice-modal__card { padding: 1.5rem 1.2rem 1.3rem; }
  .notice-modal__key { font-size: 0.92rem; }
  .notice-modal__actions .notice-modal__btn { flex: 1; }
  /* No room to corner the counter — put it back in the flow above the title. */
  .notice-modal__card--trial { padding-right: 1.2rem; }
  .trial-countdown { position: static; margin: 0 0 1rem; width: max-content; }
}
