/**
 * WPC UI — the portal's one design layer.
 *
 * The portal loads ~22 stylesheets per page; buttons and popups were styled in
 * three of them (dashboard.css, popups.css, view-site.css) with conflicting
 * rules, so what a button looked like depended on sheet load order. This file
 * is enqueued LAST (enqueues.php, priority 999) and referees the fight:
 * it neutralizes the legacy rules and applies one system.
 *
 * Tokens — type: 11/13/15/19/24, weights 400/600 · spacing: 4/6/8/10/12/20 ·
 * radius: 6/8/10/14/18 (see --wpc-r-*) · control heights 34/42/52 · one accent
 * (brand indigo) · hairlines, not shadow stacks.
 *
 * Buttons carry FOUR roles — accent, ghost, solid, danger — under one rule:
 * one filled button per view. Read the section header before adding a fifth.
 */

:root {
  --wpc-accent: #3c4cdf;
  --wpc-accent-deep: #2f3dbd;
  --wpc-accent-soft: #eef0fd;
  --wpc-grad: linear-gradient(25deg, #1c87f1 0%, #3c4cdf 100%);
  --wpc-ink: #1c2430;
  --wpc-ink-soft: #5b6c80;
  /* There is no third ink level. --wpc-dim used to sit here at #8595a8 and
     measured 3.06:1 on white — a token that cannot legally carry text, which is
     the only thing an ink token is for. Any value that does pass 4.5:1 lands
     within a hair of --wpc-ink-soft, so the third level was never a level. Two
     inks, both readable. */
  --wpc-line: #e3e9f1;
  --wpc-line-strong: #cbd5e1;
  --wpc-sunk: #f4f7fb;
  --wpc-panel: #ffffff;
  /* Semantic three, each measured as TEXT on every ground it is drawn on —
     white, the #fbfbfb table zebra and the #eceefb header chip. The previous
     values were picked to look right as fills and then reused as labels, where
     they measured 4.45 / 3.39 / 3.19. Contrast is symmetric, so the darker
     values still carry white text when the same token becomes a fill. */
  --wpc-danger: #c0392f;  /* was #cf4a44 — 4.45:1 on white */
  --wpc-ok: #1f7d4c;      /* was #2e9e63 — 3.39:1 on white */
  --wpc-warn: #9d5f00;    /* was #d97706 — 3.19:1; matches the quota amber */
  --wpc-shadow-pop: 0 24px 64px -16px rgba(24, 41, 66, 0.28);

  /* Radius scale. The portal's worst tell was mixed radii on one screen — a
     3px input beside an 8px button beside a 50px pill. Rounded rectangles are
     the default everywhere; the pill is a documented exception, not a style.
     Buttons get their own step so they never accidentally match the input
     they sit beside. */
  --wpc-r-xs: 6px;    /* badges, chips, inline tags        */
  --wpc-r-sm: 8px;    /* inputs, selects, small buttons    */
  --wpc-r-md: 10px;   /* THE BUTTON RADIUS                 */
  --wpc-r-lg: 14px;   /* cards, white-box containers       */
  --wpc-r-xl: 18px;   /* popups and the largest surfaces   */
  --wpc-r-pill: 999px;/* one standalone CTA ending a flow  */

  /* Control heights. Three steps, no others: a row of buttons that disagree
     by 2px reads as broken before anyone can say why. */
  --wpc-h-sm: 34px;
  --wpc-h-md: 42px;
  --wpc-h-lg: 52px;
}

/* ══ Neutralize legacy ═══════════════════════════════════════════════════ */

/* popups.css paints a 5px solid #6598f2 strip under every popup */
.swal2-popup {
  border-bottom: 0 !important;
}

/* dashboard.css/popups.css SHOUT buttons: uppercase 11px, tracked, 50px pills.
   The `[class]` suffix is a deliberate specificity bump, not decoration:
   dashboard.css:742 sets `.button.button-reporting { text-transform: uppercase
   !important }` at (0,2,0), which outranked a plain `a.button` no matter how
   late this sheet loads — that is why Clear Cache and WP Admin still shouted
   after the first pass. `.button[class]` is (0,2,1) and settles it. */
a.button[class],
.button[class],
.button.button-primary,
.swal2-styled,
.swal2-popup .button-primary,
.swal2-html-container .button-popup-confirm,
.swal2-popup .btn-save,
.swal2-popup .btn-close {
  text-transform: none !important;
  letter-spacing: 0 !important;
  font-size: 14px !important;
  font-weight: 600 !important;
  line-height: 20px !important;
  border-radius: var(--wpc-r-md) !important;
}

/* dashboard.css:293 paints .white-box-container at a 5px radius with a
   66px-blur shadow. --wpc-r-lg's own comment above already names this
   class as its 14px target, and the doctrine reserves shadow for floating
   layers only — a card sitting in the page flow isn't one, so it keeps its
   hairline border and loses the shadow rather than just softening it. */
.white-box-container {
  border-radius: var(--wpc-r-lg) !important;
  box-shadow: none !important;
}

/* The plugin's settings panel draws its own WP Compress logo at the top of the
   card, directly under the portal's own header logo — the same mark twice on
   one screen, and on a whitelabelled portal it is our brand leaking into an
   agency's. The header row still carries the save pill and the optimization
   buttons, so only the mark goes. */


/* ══ Popup shell ═════════════════════════════════════════════════════════ */

.swal2-popup {
  border-radius: var(--wpc-r-xl) !important;
  box-shadow: var(--wpc-shadow-pop) !important;
  padding: 0 0 28px !important;
  font-family: inherit !important;
  color: var(--wpc-ink) !important;
}
.swal2-container.swal2-backdrop-show,
.swal2-container.swal2-shown {
  background: rgba(16, 28, 44, 0.55) !important;
  backdrop-filter: blur(3px);
}
.swal2-title {
  font-size: 19px !important;
  font-weight: 600 !important;
  letter-spacing: -0.01em;
  color: var(--wpc-ink) !important;
  padding-top: 28px !important;
}
.swal2-html-container {
  margin: 0 !important;
  font-size: 15px;
  line-height: 1.6;
  color: var(--wpc-ink-soft);
}
.swal2-actions {
  margin: 20px 0 0 !important;
  gap: 10px;
}
.swal2-close {
  width: 36px !important;
  height: 36px !important;
  margin: 10px 10px 0 0 !important;
  border-radius: 50% !important;
  color: #9aa9bb !important;
  font-size: 26px !important;
  transition: background 0.15s ease, color 0.15s ease;
}
.swal2-close:hover {
  background: #eef3f9 !important;
  color: var(--wpc-ink) !important;
}
.swal2-close:focus {
  outline: none !important;
  box-shadow: none !important;
}
.swal2-close:focus-visible {
  outline: 2px solid var(--wpc-accent) !important;
  outline-offset: 2px;
}

/* ══ Status popups (Success! / Error! / Working…) ════════════════════════
   The plugin's v4 legacy sheet (wp-compress-image-optimizer/assets/v4/css/style.css) forces unlisted popups to 1050px wide !important and
   paints a background image on the empty actions strip — both killed here.
   Scoped via :has(.message-header):has(.message-body) so only the small
   status dialogs match; `body` prefix wins the specificity tie against the
   later-loading v4 sheet on new-UI pages. */
body .swal2-container .swal2-popup:has(.message-header):has(.message-body) {
  width: 420px !important;
  max-width: calc(100vw - 32px) !important;
  padding: 0 0 40px !important;
}
body .swal2-container .swal2-popup:has(.message-header):has(.message-body) .swal2-actions {
  display: none !important;
}
.swal2-popup .message-header {
  padding: 44px 0 4px;
  display: flex;
  justify-content: center;
  background: none;
}
.swal2-popup .message-header lottie-player {
  width: 84px !important;
  height: 84px !important;
  background: #e9f6ef !important;
  border-radius: 50%;
  padding: 10px;
  box-sizing: content-box;
}
.swal2-popup .message-header.red-background lottie-player {
  background: #fdedec !important;
}
.swal2-popup .message-header.blue-background lottie-player {
  background: #e9effc !important;
}
.swal2-popup .message-body {
  padding: 14px 44px 0;
}
.swal2-popup .message-body h2 {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 6px;
  color: var(--wpc-ink);
}
.swal2-popup .message-body p {
  font-size: 15px;
  line-height: 1.55;
  color: var(--wpc-ink-soft);
  margin: 0 auto;
  max-width: 36ch;
}
.swal2-popup .message-body .error-message {
  margin-top: 2px;
}
.swal2-popup .message-body .error-message code {
  background: #f1f4f9;
  border-radius: var(--wpc-r-xs);
  padding: 1px 6px;
  font-size: 13px;
}

/* ══ Buttons — four roles and one exception ══════════════════════════════
   THE GOVERNING RULE: one filled button per view. If two controls on a screen
   are both filled, neither is the primary and the customer is being asked to
   make a decision we should have made for them. That rule is the whole reason
   this section exists — the previous pass flattened every button to the same
   accent, which removed the four competing gradients but left every control
   shouting at the same volume. Same problem, quieter.

   Accent  — the one primary action on the view.
   Ghost   — everything sitting beside it: cancel, clear, back, secondary nav.
             This is the correct default when you are unsure.
   Solid   — primary for dense admin surfaces (settings rows, table toolbars)
             where the brand accent would be too much ceremony for a save.
   Danger  — only for what cannot be undone. Outlined at rest, filled on
             hover, so the weight arrives at the moment of commitment.

   All four share the 42px control height, `--wpc-r-md`, and a 1px border, so
   the same label is the same width in any role and a row of mixed roles still
   lines up. Press compresses (scale .97 / 120ms); focus ring is keyboard-only.
   Sizes are --wpc-h-sm / -md / -lg and nothing in between. */

/* 1 · ACCENT — the single primary */
.swal2-styled.swal2-confirm,
.swal2-popup .btn-save,
.swal2-html-container .button-popup-confirm,
.button.button-primary,
a.button.button-primary {
  background: var(--wpc-accent) !important;
  background-image: none !important;
  border: 1px solid var(--wpc-accent) !important;
  color: #fff !important;
  padding: 0 20px !important;
  min-height: var(--wpc-h-md);
  box-shadow: none !important;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.12s ease;
  cursor: pointer;
}
.swal2-styled.swal2-confirm:hover,
.swal2-popup .btn-save:hover,
.swal2-html-container .button-popup-confirm:hover,
.button.button-primary:hover {
  background: var(--wpc-accent-deep) !important;
  border-color: var(--wpc-accent-deep) !important;
  color: #fff !important;
}

/* 2 · GHOST — transparent, not white.
   White-filled was wrong: the view-site header row sits on a pale lavender
   band, and a white box with a hairline on a tinted ground is exactly what an
   empty text input looks like — Clear Cache and WP Admin read as two disabled
   fields beside a button. Transparent works on every ground the portal has,
   because a ghost sits ON the surface instead of punching a hole in it.

   The default for everything beside a primary.
   `.button-primary-grd-empty` is the important one here: 22 uses across the
   theme, and it had NO rule of its own in any of the 22 stylesheets — the
   name promised an outlined variant and it inherited the filled primary
   instead. Every screen carrying one was showing two primaries. Same for
   `.button-convert-to-pro` and `.button-grey`, which were also nowhere.
   Verified by grepping every sheet in the theme and the plugins for each
   class: no match outside this file. */
.swal2-styled.swal2-cancel,
.swal2-popup .btn-close,
.button.button-grey,
.button.button-primary-grd-empty,
a.button.button-primary-grd-empty,
.button.button-clear-cache,
a.button.button-clear-cache,
.new-search-form input[name="submit"] {
  background: transparent !important;
  background-image: none !important;
  border: 1px solid var(--wpc-line-strong) !important;
  color: var(--wpc-ink) !important;
  padding: 0 20px !important;
  min-height: var(--wpc-h-md);
  box-shadow: none !important;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease, transform 0.12s ease;
  cursor: pointer;
}
.swal2-styled.swal2-cancel:hover,
.swal2-popup .btn-close:hover,
.button.button-grey:hover,
.button.button-primary-grd-empty:hover,
a.button.button-primary-grd-empty:hover,
.button.button-clear-cache:hover,
a.button.button-clear-cache:hover,
.new-search-form input[name="submit"]:hover {
  background: var(--wpc-accent-soft) !important;
  border-color: var(--wpc-accent) !important;
  color: var(--wpc-accent) !important;
}

/* The configure-reporting popup's Save button carries `.button-grey` — it
   should be Ghost like every other button-grey. But popups.css:330 paints
   it with its own gradient at (0,2,2), and the only rule in this sheet that
   out-specifies popups.css (the "Gradients, the rest" block below) forces
   Accent onto every submit in that popup without checking for .button-grey
   first — this sheet fighting itself, not a legacy loss. Named directly so
   it stops getting caught by that wider net. */
body .configure-reporting-popup form input.button-grey[type="submit"] {
  background: transparent !important;
  background-image: none !important;
  border: 1px solid var(--wpc-line-strong) !important;
  color: var(--wpc-ink) !important;
  box-shadow: none !important;
}
body .configure-reporting-popup form input.button-grey[type="submit"]:hover {
  background: var(--wpc-accent-soft) !important;
  border-color: var(--wpc-accent) !important;
  color: var(--wpc-accent) !important;
}

/* 3 · SOLID — primary for dense admin surfaces. Ink, not brand: a settings
   row saving itself is routine, and spending the accent on it leaves nothing
   louder for the action that actually matters on the page. */
.button.button-save-config,
a.button.button-save-config,
.button.button-v3:not(.empty){
  background: var(--wpc-ink) !important;
  background-image: none !important;
  border: 1px solid var(--wpc-ink) !important;
  color: #fff !important;
  padding: 0 20px !important;
  min-height: var(--wpc-h-md);
  box-shadow: none !important;
  transition: opacity 0.15s ease, transform 0.12s ease;
  cursor: pointer;
}
.button.button-save-config:hover,
a.button.button-save-config:hover,
.button.button-v3:not(.empty):hover{
  opacity: 0.88;
  color: #fff !important;
}

/* 4 · DANGER — outlined at rest, filled on commitment.
   Retrying, saving and clearing a cache are not destructive and must never
   wear this. `.button-delete-site` was a filled red block at rest in
   dashboard.css:1109, which made "delete" the loudest thing on a row where
   the customer was usually doing something else. */
.swal2-styled.swal2-deny,
.button.button-danger,
.button.button-delete-site,
a.button.button-delete-site {
  background: transparent !important;
  background-image: none !important;
  border: 1px solid currentColor !important;
  color: var(--wpc-danger) !important;
  padding: 0 20px !important;
  min-height: var(--wpc-h-md);
  box-shadow: none !important;
  transition: background 0.15s ease, color 0.15s ease, transform 0.12s ease;
  cursor: pointer;
}
.swal2-styled.swal2-deny:hover,
.button.button-danger:hover,
.button.button-delete-site:hover,
a.button.button-delete-site:hover {
  background: var(--wpc-danger) !important;
  border-color: var(--wpc-danger) !important;
  color: #fff !important;
  filter: none;
}

/* Size steps. Anything not carrying one of these is --wpc-h-md by default. */
.button.button-sm,
a.button.button-sm {
  min-height: var(--wpc-h-sm) !important;
  padding: 0 14px !important;
  font-size: 13px !important;
  border-radius: var(--wpc-r-sm) !important;
}
.button.button-lg,
a.button.button-lg {
  min-height: var(--wpc-h-lg) !important;
  padding: 0 28px !important;
  font-size: 15px !important;
}

/* Buttons are inline-flex so the label centres against the control height
   rather than being pushed off it by whatever line-height a legacy sheet set.
   This is what lets padding be `0 20px` above with no vertical value at all.

   `[class]` is load-bearing, not decoration. dashboard.css:739 sets
   `.button.button-reporting { display: inline-block !important }` at (0,2,0),
   which beat a plain `.button` (0,1,0) no matter how late this sheet loads.
   The effect was ugly and specific: Clear Cache and WP Admin kept the 42px
   box but laid their labels out as inline-block text, so the label sat 2px
   from the top with 23px of dead space under it. Measured, not eyeballed —
   a range rect around the label against the button's own box. Every other
   button on the page was centred, which is exactly why the two of them read
   as broken rather than as a style choice. */
.button[class],
a.button[class],
.swal2-styled,
.swal2-popup .btn-save,
.swal2-popup .btn-close {
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-decoration: none;
}

/* the press — every button compresses */
.swal2-styled:active,
.swal2-popup .btn-save:active,
.swal2-popup .btn-close:active,
.swal2-html-container .button-popup-confirm:active,
.button:active,
a.button:active {
  transform: scale(0.97);
}

/* focus: keyboard only */
.swal2-styled:focus,
.swal2-popup .btn-save:focus,
.swal2-popup .btn-close:focus,
.swal2-html-container .button-popup-confirm:focus,
.button:focus {
  outline: none !important;
  box-shadow: none !important;
}
.swal2-styled:focus-visible,
.swal2-popup .btn-save:focus-visible,
.swal2-popup .btn-close:focus-visible,
.swal2-html-container .button-popup-confirm:focus-visible,
.button:focus-visible {
  outline: 2px solid var(--wpc-accent) !important;
  outline-offset: 2px;
}

/* ══ Focus belongs to the keyboard ═══════════════════════════════════════
   A mouse click must never leave a ring behind. The portal had no policy at
   all — only `.button` and the swal dialogs were covered, so every link,
   icon and tabbable cell fell through to Chrome's default ring, which traces
   the element's whole border box. On the site list that is the full Website
   column: click a site, come back, and a 300px blue rectangle is sitting
   around the row with no way to dismiss it.

   `:where()` contributes nothing to specificity, so this stays a floor that
   any component can override without an arms race; the `!important` is what
   beats the UA sheet, not the selector.

   TEXT FIELDS ARE EXCLUDED on purpose. Clicking into an input and seeing it
   light up is correct — that ring answers "where am I typing?", which is a
   question a mouse user genuinely has. A button does not have it.

   `input[type=submit|button|reset]` and `select` ARE in the list. A submit is
   a button wearing an input tag: Search and Clear on the site list are the
   only two controls on the dashboard a keyboard user could reach and get no
   ring at all, because neither carries the `.button` class the block above
   keys on. */
:where(a, button, summary, [tabindex], select, input[type="submit"],
       input[type="button"], input[type="reset"], .paginate_button, .copy-key,
       .view-site-link, .expand-apikeys):focus:not(:focus-visible) {
  outline: none !important;
  box-shadow: none !important;
}
:where(a, button, summary, [tabindex], select, input[type="submit"],
       input[type="button"], input[type="reset"], .paginate_button, .copy-key,
       .view-site-link, .expand-apikeys):focus-visible {
  outline: 2px solid var(--wpc-accent) !important;
  outline-offset: 2px;
}

/* `:where()` costs nothing, which is the point of the floor above and also its
   one weakness: new-apikey.css:`.white-box-container input:focus { outline:
   none !important }` is (0,2,0) and blanks it for every input inside a white
   box — which is where the site search lives. Search and Clear were the only
   two controls on the dashboard a keyboard user could reach and see nothing
   at all. (0,2,1) settles it. Text inputs are not in here: they answer focus
   with the border + soft ring further down, which is the better affordance
   for a field. */
.white-box-container input[type="submit"]:focus-visible,
.white-box-container input[type="button"]:focus-visible,
.new-search-form input[type="submit"]:focus-visible {
  outline: 2px solid var(--wpc-accent) !important;
  outline-offset: 2px;
}

/* The ring should hug the label, not the table cell.
   site-list.php renders the site name as `<a class="view-site-link">` inside
   a `.table-row-site` cell. Whatever makes that anchor fill its cell also
   makes the focus ring a full-width rectangle around the row — the box in
   the screenshot. Inline-block keeps the anchor the width of its own text,
   so the ring lands on the site name. */
.table-body .view-site-link {
  display: inline-block;
  border-radius: var(--wpc-r-xs);
}

/* disabled + loading.
   `transform: none` matters: without it a button disabled mid-press keeps the
   0.97 compression and sits permanently dented. */
.swal2-styled[disabled],
.button[disabled],
.button.disabled,
.button.is-loading {
  opacity: 0.4;
  pointer-events: none;
  transform: none !important;
  box-shadow: none !important;
}
.button.is-loading::after {
  content: '';
  display: inline-block;
  width: 12px;
  height: 12px;
  margin-left: 8px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  vertical-align: -2px;
  animation: wpc-spin 0.7s linear infinite;
}
@keyframes wpc-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .swal2-popup, .swal2-container { animation: none !important; }
  .swal2-styled:active, .button:active, a.button:active { transform: none; }
  .button.is-loading::after { animation-duration: 1.6s; }
}

/* ══ Text floors ═════════════════════════════════════════════════════════
   12px is the floor for anything a person has to read, 13px for chips and
   badges that carry state. Everything below is a selector gate:portal caught
   under those floors. Each is listed by name rather than swept with a blanket
   rule, so a new offender fails the gate instead of being silently propped up.
   ─────────────────────────────────────────────────────────────────────── */

/* site key line under the page title (view-site.php carries this inline) */
.view-site-key,
.view-site-key .copy-key {
  font-size: 12px !important;
  line-height: 18px;
}
.view-site-key {
  margin-top: 10px !important;
  font-weight: 400;
  color: #6d6d6d;
}

/* stat captions inside the settings panel and the credits pill */
.stats-box-single h5,
.left-side-box .youve-saved .button-primary h5 {
  font-size: 12px !important;
}

/* Compression Report legend ("Original Size" / "After Optimization"). The
   plugin sets this from an inline <style> in its own footer at (0,3,2)
   !important, and again at 10px !important inside a max-width:480 block — so
   a stylesheet cannot reach it on specificity alone. `body` in front is the
   cheapest way to outrank both without touching the plugin, which is not in
   this repo. */



/* the plugin's sample-data chip: state, so it takes the 13px chip floor. It
   sits on the Compression Report heading and is the only thing telling a
   person the numbers under it are not theirs — it does not get to be the
   smallest text on the page. */
.wpc-sample-badge {
  font-size: 13px !important;
  line-height: 1.4;
  letter-spacing: 0;
}

/* the monitoring pill is a chip too. `[class]` buys the specificity to clear
   `.wpc-settings-flex-body .wpc-stats-monitoring`, which the plugin sets to
   11px !important, and the 10px !important it drops in under 480. */



/* ══ Hover does not move things ══════════════════════════════════════════
   The plugin's v4 panel lifts 22 different elements on hover — cards, pills,
   buttons, popup rows, the export block. Some of them are not clickable at
   all, so the lift promises an interaction that isn't there, and the ones
   that are clickable each lift by a different amount. Hover changes colour,
   border and shadow here; position is reserved for :active, which is the
   press. Scoped to the plugin's own containers so the rule cannot reach
   anything the portal draws itself. `body` and `[class]` are specificity, not
   style: the plugin declares these lifts at up to (0,3,0) !important, so a
   plain descendant selector loses.

   The exclusion list is not cosmetic. Those elements carry a *positioning*
   transform at rest — translateX(-50%) to centre themselves, or a scale the
   hover state restores — so blanking transform on hover would shove them off
   their anchor. The floating save pill is the one that matters most: it is
   centred that way, and it is the control people actually reach for. */
/* `:hover:not(:active)` on every line: hover must not move the element, but a
   PRESS must. Without the :not() these rules also win during the press (a
   pressed button is hovered too) and the 0.97 compression never lands on any
   plugin control — which is most of the buttons on the settings page.

 Enumerated from the plugin's own v4 style.css rather than swept with a
   `[class]:hover` blanket. The blanket was live for one deploy and is not
   coming back: it reached every class-bearing element inside the panel,
   including ones whose resting transform is what positions them. This list
   is exactly the rules that lift, and nothing else. Regenerate with
   tools/hover-lifts.py when the plugin ships a new stylesheet. */

body .wpc-btn-return:hover:not(:active),
body .wps_ic_settings_page .button-start-bulk-compress:hover:not(:active),
body .wps_ic_settings_page .button-start-bulk-restore:hover:not(:active),
body .wpc-cf-banner:hover:not(:active) .wpc-cf-link,
body #wpc-cf-token-check:hover:not(:active),
body .wpc-cf-button:hover:not(:active),
body .wpc-cf-connect-form .wpc-cf-token-connected .wpc-cf-token-verify:hover:not(:active),
body .wpc-cf-connect-form .wpc-cf-token-connected .wpc-cf-token-refresh-whitelist:hover:not(:active),
body .wpc-cf-connect-form .wpc-cf-token-connected .wpc-cf-token-disconnect:hover:not(:active),
body .wpc-cf-token-connected-info-right .wpc-cf-button:hover:not(:active),
body .popup-per-page-settings .btn-exclude-pages-save:hover:not(:active),
body .popup-per-page-excludes .btn-exclude-pages-save:hover:not(:active),
body .no-padding-popup-bottom-bg .swal2-popup.popup-custom-cdn .btn-close.btn-active:hover:not(:active),
body .popup-custom-cdn .btn-close.btn-active:hover:not(:active),
body .wpc-compatibility-popup .wpc-popup-confirm:hover:not(:active),
body .swal2-popup.popup-hide_compress .swal2-actions .swal2-confirm:hover:not(:active),
body #export_settings .cdn-popup-inner:hover:not(:active),
body #export_settings #wpc-export-button:hover:not(:active),
body #export_settings #wpc-import-button:hover:not(:active),
body #export_settings #wpc-set-default-button:hover:not(:active),
body .wpc-bulk-hero:hover:not(:active),
body .wpc-action-btn--primary:hover:not(:active),
body .wpc-action-btn--primary:hover:not(:active) .wpc-action-btn-icon--right,
body .wpc-action-btn--stop:hover:not(:active),
body .wpc-bulk-splash-card:hover:not(:active),
body .wpc-bulk-splash-card--compress .button-start-bulk-compress:hover:not(:active),
body .wpc-bulk-splash-empty-cta:hover:not(:active),
body .wpc-confirm-btn--danger:hover:not(:active),
body .wpc-confirm-btn--warning:hover:not(:active) {
  transform: none !important;
}

/* ══ Targets ═════════════════════════════════════════════════════════════
   The control height is the touch-target floor too. The last two render as
   text links today and are missed by the button system. */
.button[class],
a.button[class],
.btn.dropdown-toggle,
.buy-more-link a {
  min-height: var(--wpc-h-md);
}
.btn.dropdown-toggle,
.buy-more-link a {
  display: inline-flex;
  align-items: center;
}

/* ══ Old-UI overflow ═════════════════════════════════════════════════════
   REMOVED 13 Aug, do not reintroduce. The v4 settings panel overflows the
   viewport under ~900px, and the obvious cure — `overflow-x: auto` on the
   panel — is worse than the disease: when one axis is not `visible` the other
   computes to `auto` too, so the panel became a scroll container. That clips
   every absolutely-positioned descendant that reaches past it (the info
   tooltips, the sample-data tooltip, dropdown menus) and kills `position:
   sticky` inside it. Measured after the fact: overflow-y resolved to `auto` on
   both 375 and 1440.

   The sideways scroll is real and still open — it belongs to whatever sets the
   panel's floor width, and it gets fixed there, not by boxing the panel. */

/* ══ Buttons are not links ═══════════════════════════════════════════════
   bootstrap.min.css:  `a:hover, a:focus { color: #23527c; text-decoration:
   underline }` at (0,1,1). Every anchor styled as a button therefore sprouted
   a link underline under the cursor — Clear Cache, WP Admin, View Reports, Add
   Website, every popup confirm. A button changes colour on hover, never
   decoration. `a.button[class]` is (0,2,1) and settles it; the bare-word
   selectors below catch the anchors the button classes never reached. */
a.button[class]:hover,
a.button[class]:focus,
a.btn[class]:hover,
a.btn[class]:focus,
.swal2-styled:hover,
.swal2-styled:focus,
.paginate_button:hover,
.paginate_button:focus,
.buy-more-link a:hover,
.buy-more-link a:focus {
  text-decoration: none !important;
}

/* ══ One accent, no gradient ═════════════════════════════════════════════
   The 25° blue-to-indigo gradient was painted on primaries from four sheets
   (dashboard.css:736 and :759, new-apikey.css:429, the plugin's .btn-gradient)
   and it is the single biggest reason the portal read as cheap: a gradient is
   an effect, and an effect on every primary means none of them is emphasis.
   One flat accent. Depth comes from the hairline and the hover, not the fill.

   The primary intent itself is declared once, in the button system above —
   these are the aliases that never carried .button-primary and so were never
   part of it. --wpc-grad stays defined and unused so anything still referencing
   it keeps resolving; nothing in this sheet paints with it any more. */
.button.button-primary-grd,
.button.button-view-reports,
.button.button-new-website,
.button.button-next-plan-v2,
input.button-next-plan-v2,
.button.button-configure-reporting,
.button.button-send-report{
  background: var(--wpc-accent) !important;
  background-image: none !important;
  border: 1px solid var(--wpc-accent) !important;
  color: #fff !important;
  padding: 0 20px !important;
  min-height: var(--wpc-h-md);
  box-shadow: none !important;
}
.button.button-primary-grd:hover,
.button.button-view-reports:hover,
.button.button-new-website:hover,
.button.button-next-plan-v2:hover,
input.button-next-plan-v2:hover{
  background: var(--wpc-accent-deep) !important;
  border-color: var(--wpc-accent-deep) !important;
  filter: none !important;
}

/* (The old `padding: 9px 18px` re-declaration for the secondary role lived
   here. Every role now shares `padding: 0 20px` on a fixed control height in
   the role block above, so the same label is the same width in any role
   without a second rule restating it further down the sheet.) */

/* The quota card saves itself — that is a Solid, not the page's Accent.
   On the not-working fork the page rendered two filled accent buttons: View
   Reports in the header and this Save in the quota card. Both are genuinely
   primary, but not at the same level — one is what the page is for, the other
   is what one card on it is for. Ink instead of brand keeps the card's save
   unambiguously the primary of its own card without competing for the page's
   single accent slot.

   Placed after the gradient-alias block above, which also matches this button
   via .button-primary-grd; the roles block near the top of the sheet would
   otherwise lose on source order alone. */
body input.button[name="save-credits"],
body input.button[name="save-credits"]:hover {
  background: var(--wpc-ink) !important;
  background-image: none !important;
  border: 1px solid var(--wpc-ink) !important;
  color: #fff !important;
  box-shadow: none !important;
}
body input.button[name="save-credits"]:hover {
  opacity: 0.88;
}

/* The quota readout is not a second primary.
   stats-local.php:45 and its four siblings render the remaining credits as
   `<a class="button button-primary requests-left"><h5>100 GB</h5></a>` — an
   anchor to wpcompress.com/pricing wearing the primary's fill, with a NUMBER
   for a label. On the old-UI view-site page that put two filled buttons in
   one view, and the second one said "100 GB", which reads as a statistic and
   gives no hint that clicking it goes anywhere.

   Demoted to a clickable stat: accent text on a hairline, so it is visibly
   interactive without competing with View Reports for the one primary slot.
   This is the portal's upgrade path for a customer who is running out of
   credits and deserves a deliberate treatment and a verb — flagged, not
   guessed at here. */
body .button.button-primary.requests-left,
body .button.button-primary.local-requests-left {
  background: transparent !important;
  background-image: none !important;
  border: 1px solid var(--wpc-line-strong) !important;
  color: var(--wpc-accent) !important;
  box-shadow: none !important;
}
body .button.button-primary.requests-left h5,
body .button.button-primary.local-requests-left h5 {
  color: var(--wpc-accent) !important;
  margin: 0 !important;
  font-size: 14px !important;
  font-weight: 600 !important;
  font-variant-numeric: tabular-nums;
}
body .button.button-primary.requests-left:hover,
body .button.button-primary.local-requests-left:hover {
  background: var(--wpc-accent-soft) !important;
  border-color: var(--wpc-accent) !important;
}

/* ══ Inputs and selects — one control ════════════════════════════════════
   Same height as a button, one step down in radius. Before this, the site
   search sat at 34px/3px-radius next to a 40px/8px-radius button in the same
   row.

   Height matches the button token exactly (--wpc-h-md) because these sit in
   the same row as one; radius does NOT (r-sm, one step below r-md) because a
   field and a button that share a radius stop reading as different kinds of
   thing. That is the one place in the scale where matching would be worse. */
.new-search-form input[type="text"],
.table_footer .dataTables_length select,
.wpc-quota-select,
.new-account-form-footer select {
  min-height: var(--wpc-h-md) !important;
  height: var(--wpc-h-md) !important;
  border-radius: var(--wpc-r-sm) !important;
  border: 1px solid var(--wpc-line-strong) !important;
  font-size: 14px !important;
  line-height: 20px !important;
  padding: 0 14px !important;
  color: var(--wpc-ink) !important;
  background-color: #fff !important;
  box-shadow: none !important;
}
.new-search-form input[type="text"]:focus,
.table_footer .dataTables_length select:focus,
.wpc-quota-select:focus,
.new-account-form-footer select:focus {
  outline: none !important;
  border-color: var(--wpc-accent) !important;
  box-shadow: 0 0 0 3px var(--wpc-accent-soft) !important;
}

/* ══ Site search — one control group ═════════════════════════════════════
   The form was an inline-block with its own border wrapping a borderless
   input and two submits, sitting beside a button that was not in the box: four
   nested rectangles for one search field. Flatten the wrapper, let the three
   controls be three controls, and put them on one baseline with Add Website.

   :has() is doing real work here — the column is a generic bootstrap class and
   must only become a flex row when it is the one holding this form. */
.new-search-form {
  display: flex !important;
  align-items: center;
  gap: 8px;
  padding: 0 !important;
  border: 0 !important;
  border-radius: 0 !important;
}
.new-search-form input[type="text"] {
  min-width: 260px;
}
/* Three weights in one row, which is the whole point of the role system:
   Add Website is the view's single Accent, Search is the Ghost beside it, and
   Clear is quieter than both. Search takes its box from the Ghost role above;
   only the submit-specific resets live here. */
.new-search-form input[type="submit"] {
  font-weight: 600 !important;
  text-transform: none !important;
  cursor: pointer;
  height: var(--wpc-h-md) !important;
  min-height: var(--wpc-h-md) !important;
  border-radius: var(--wpc-r-md) !important;
  padding: 0 20px !important;
}
/* Clear is an undo, not an action — no box at all until it is approached. */
.new-search-form input[name="clear"] {
  background: transparent !important;
  border: 1px solid transparent !important;
  color: var(--wpc-ink-soft) !important;
}
.new-search-form input[name="clear"]:hover {
  background: var(--wpc-sunk) !important;
  color: var(--wpc-ink) !important;
}
.white-box-container .row > [class*="col-"]:has(> .new-search-form) {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  flex-wrap: wrap;
}

/* ══ Table footer ════════════════════════════════════════════════════════
   DataTables 1.13.5 default chrome: a bare <select> in a sentence, and page
   numbers as 8px-padded boxes with a #c3c3c3 hairline — roughly 26×24px, well
   under the 40px floor, and visually nothing to do with any other control on
   the page. The footer is built by site-list.php's initComplete, which moves
   .dataTables_length and .dataTables_paginate into .table_footer. */
.table_footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  padding-top: 16px;
  margin-top: 4px;
  border-top: 1px solid var(--wpc-line);
}
.table_footer .dataTables_length label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 13px;
  font-weight: 400;
  color: var(--wpc-ink-soft);
}
.table_footer .dataTables_paginate,
.table_footer .sites-pagination {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 0 !important;
  text-align: left;
}
.table_footer .sites-pagination span {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.sites-pagination a,
.sites-pagination li a,
.table_footer .paginate_button {
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  min-width: var(--wpc-h-md);
  min-height: var(--wpc-h-md);
  padding: 0 10px !important;
  margin: 0 !important;
  border: 1px solid transparent !important;
  border-radius: var(--wpc-r-md) !important;
  background: transparent !important;
  color: var(--wpc-ink-soft) !important;
  font-size: 14px !important;
  font-weight: 600 !important;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.sites-pagination a:hover,
.table_footer .paginate_button:hover {
  background: #eef2f7 !important;
  color: var(--wpc-ink) !important;
}
/* The page you are on is a state, so it is filled — not merely a heavier link. */
.sites-pagination a.current,
.table_footer .paginate_button.current,
.table_footer .paginate_button.current:hover {
  background: var(--wpc-accent) !important;
  border-color: var(--wpc-accent) !important;
  color: #fff !important;
}
.table_footer .paginate_button.disabled,
.table_footer .ellipsis {
  color: #9aa8b8 !important;
  cursor: default;
  background: transparent !important;
}

/* ══ Empty settings header ═══════════════════════════════════════════════
   The plugin's .wpc-header reserves min-height:88px and paints a white card
   with a hairline and a 20px margin — unconditionally. Everything it can hold
   is conditional: the logo (hidden above, brand leak), the save pill (shown
   only when the form is dirty), the spinner, and six mutually-exclusive
   optimization buttons that all ship as style="display:none" until JS resolves
   the site's state. Measured at 1024 and 1440 on a freshly rendered page:
   nothing inside it has a box at all, so the portal drew an 88px empty card
   under the page title. That is the "missing content" in the report.

   Above 1024 the card chrome goes and the row sizes to whatever is actually
   visible — nothing, until the save pill or an optimize button appears, and
   both of those carry their own chrome. Below 1024 it stays a card, because
   there it holds the visible nav toggle and has something to frame. */
@media (min-width: 1024px) {
  
  /* The gap belongs to whichever control becomes visible, not to the empty
     row — a hidden element contributes no margin, so this is 0 until there is
     something to separate. */
  
}

/* ══ State reads as state, controls read as controls ═════════════════════
   edit-quota.php puts a .badge-credits next to each toggle carrying the same
   information the toggle already carries ("Enabled" beside a switch that is
   on), styled as a filled chip — so it competes with the row title and reads
   as something you can press. Two rows down, a real <select> is styled the
   same way, so the one thing in the group that IS pressable looks identical to
   the two that are not. Keep the word (it is the accessible label the switch
   lacks) as a quiet Ghost chip — outlined, not filled, so it reads as state
   rather than a control.

   Specificity note: two sheets already own this class at (0,2,0) — dashboard.css
   :1757 paints the gradient chip and pins `font-size: 12px !important`, and
   new-apikey.css:48 pins `color: #4373f0 !important`. A plain `.badge-credits`
   is (0,1,0) and lost both even with !important, which is why the first attempt
   went transparent but stayed blue and 12px. `body .badge-credits[class]` is
   (0,2,1) and clears them.

   The 76px width stays — it is what lines the states up into a column instead
   of letting each one start wherever its row title happens to end. */
body .badge-credits[class] {
  display: inline-flex !important;
  background: transparent !important;
  background-image: none !important;
  border: 1px solid var(--wpc-line-strong) !important;
  border-radius: var(--wpc-r-xs) !important;
  padding: 3px 10px !important;
  margin-left: 15px !important;
  min-width: 76px;
  font-size: 13px !important;
  font-weight: 600 !important;
  color: var(--wpc-ok) !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  justify-content: center !important;
  align-items: center;
}
body .badge-credits[class].badge-disabled {
  color: var(--wpc-ink-soft) !important;
}

/* Row titles share a floor width so the state column starts at one x instead
   of tracking the end of each label. Only where there is room for it: at 375
   the row is title + 76px state + 45px switch, and a 190px floor pushed it to
   426px — the gate caught it as a new horizontal overflow on the not-working
   and old-unreachable branches. Below 768 the labels align themselves. */
@media (min-width: 768px) {
  .checkbox-wrapper-6 h3 {
    min-width: 190px;
  }
}

/* ══ Tertiary actions ════════════════════════════════════════════════════
   new-apikey.css:416 paints the quota links #9e9e9e with a permanent
   underline, which is how a disabled control looks — next to a full-strength
   primary. They are real alternatives to Continue, so they take the Ghost
   chip grammar (same as the plugin-download chip): quiet outline at rest,
   accent on hover, no underline in either state. */
body .new-account-form-footer .set-unlimited-quota-link,
body .new-account-form-footer .set-quota-link,
body .new-account-form-footer .set-custom-quota-link,
body .multiple-apikeys .expand-apikeys {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--wpc-h-md); /* sits beside the 42px Continue — same height */
  padding: 0 16px;
  border: 1px solid var(--wpc-line-strong);
  border-radius: var(--wpc-r-md);
  background: transparent;
  color: var(--wpc-ink) !important;
  font-size: 13px !important;
  font-weight: 600 !important;
  line-height: normal;
  text-decoration: none !important;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
body .new-account-form-footer .set-unlimited-quota-link:hover,
body .new-account-form-footer .set-quota-link:hover,
body .new-account-form-footer .set-custom-quota-link:hover,
body .multiple-apikeys .expand-apikeys:hover {
  background: var(--wpc-accent-soft);
  border-color: var(--wpc-accent);
  color: var(--wpc-accent) !important;
  text-decoration: none !important;
}

/* ══ BOUNDARY: the plugin's settings area is not ours ════════════════════
   wpc-ui.css stops at the edge of the wp-compress plugin's UI. Nothing in this
   file may name .wpc-advanced-settings-container-v4, .wpc-lite-settings-container
   or any class the plugin paints (.btn-gradient, .wpc-box-*, .wpc-save-button,
   .save-button-lite, .wps-ic-*, .wpc-beta-badge, .wpc-oa-beta and friends).
   107 such selectors were removed on 2026-08-14. tools/gate-portal.sh now fails
   the build if one comes back, because this is the kind of rule that regrows
   every time someone fixes a visual bug from the outside.

   Why: the plugin ships its own design system and its own release cycle. A
   referee layer that reaches inside means every plugin update lands on top of
   overrides written against the version before it, and the portal silently
   becomes the plugin's stylesheet maintainer. Bugs in there get FIXED IN THE
   PLUGIN.

   Two consequences of the removal, both intended, both to fix upstream:

   1. The optimize-state button in the settings header is invisible again.
      wp-compress-image-optimizer/assets/v4/css/style.css:809

        .wpc-advanced-settings-container-v4 .btn-gradient,
        .wpc-lite-settings-container .btn-gradient { display: none !important }

      All five states — Start Optimization, Optimization in progress, Preparing,
      Optimized, Smart Optimization Locked — carry .btn-gradient, and the
      !important outranks the inline style the plugin's own jQuery writes when
      it shows one. So the plugin cannot reveal its own button. The same sheet
      contradicts itself at :7405 (a hover transform for .btn-gradient) and at
      :17962/:18262 (responsive padding), so :809 reads as a stray blanket
      rather than intent. REPORT UPSTREAM: delete style.css:809-812.

   2. Plugin-area controls no longer take the portal's flat accent, radius,
      control height or hover/press treatment. They look like the plugin again,
      which is the point. */


/* ══ The rest of the gradients ═══════════════════════════════════════════
   The flatten above only reached the controls that carry a .button class. A
   sweep of every rendered element whose computed background-image is a
   linear-gradient found four more spellings, from four sheets, on controls the
   button system never named:

     90deg  #4380E7 → #3F56DA   the top bar's Add New Website
     25deg  #1c87f1 → #3c4cdf   Add Credits, Get Unlimited Access, 100 GB
     25deg  #5cb1ff → …         portal popup Save / All Good to Go
     81.4deg #59a8e3 → …        plugin popup — LEFT ALONE, not ours to flatten

   Four gradients is not a brand, it is four accidents. Same flat accent for all
   of them. Several are class-less anchors, so they are reached through the one
   stable thing they have — their container.

   Deliberately NOT flattened: .wpc-checkbox-icon, .wpc-cname-icon and
   .wpc-eu-routing-icon paint 135deg gradients as small illustrative tiles. They
   are artwork, not controls, and a gradient is fine on artwork. */
.header-btn-filled.header-btn-active,
.buy-more-link a,
.package-credits-link a,
.button.button-primary.requests-left,
.btn.btn-primary.btn-active,
.wpc-popup-confirm,
.wpc-form-inline input[type="submit"] {
  background: var(--wpc-accent) !important;
  background-image: none !important;
  border: 1px solid var(--wpc-accent) !important;
  color: #fff !important;
  border-radius: var(--wpc-r-md) !important;
  box-shadow: none !important;
  text-decoration: none !important;
}
.header-btn-filled.header-btn-active:hover,
.buy-more-link a:hover,
.package-credits-link a:hover,
.button.button-primary.requests-left:hover,
.btn.btn-primary.btn-active:hover,
.wpc-popup-confirm:hover,
.wpc-form-inline input[type="submit"]:hover {
  background: var(--wpc-accent-deep) !important;
  border-color: var(--wpc-accent-deep) !important;
  color: #fff !important;
  filter: none !important;
}

/* ══ Gradients, the rest — matched selector for selector ═════════════════
   The block above named the controls by their own class and lost, because
   every remaining gradient is painted !important through a descendant chain
   that outranks a single class. Guessing at specificity is what shipped a
   dead rule once already, so each of these is the winning selector as read
   out of the CSSOM on a rendered page, prefixed with `body` to take the tie.

     dashboard.css        .header-bar .header-btn-filled.header-btn-active
     dashboard.css        .cdn-popup-inner .custom-cdn-steps form.wpc-form-inline input[type=submit]
     dashboard.css        .dashboard-account-container-holder .dashboard-account-header .package-credits-link a
     dashboard.css        .first-login-popup .button.button-primary
     popups.css           .swal2-popup .button-primary
     new-apikey.css       .new-account-form-footer input.button-next-plan-v2
     (two view-site-settings.css entries were here and are gone: they were
      inside the plugin's own container — see the BOUNDARY note above.
      popups.css's .configure-reporting-popup form input[type=submit] also
      left this list: it was catching that popup's Ghost-role Save button
      too, with nothing to tell it apart from a real primary submit. Named
      carve-out is next to the Ghost role block instead.) */
body .header-bar .header-btn-filled.header-btn-active,
body .cdn-popup-inner .custom-cdn-steps form.wpc-form-inline input[type="submit"],
body .dashboard-account-container-holder .dashboard-account-header .package-credits-link a,
body .first-login-popup .button.button-primary,
body .swal2-popup .button-primary,
body .new-account-form-footer input.button-next-plan-v2{
  background: var(--wpc-accent) !important;
  background-image: none !important;
  border: 1px solid var(--wpc-accent) !important;
  color: #fff !important;
  border-radius: var(--wpc-r-md) !important;
  box-shadow: none !important;
  text-decoration: none !important;
}
body .header-bar .header-btn-filled.header-btn-active:hover,
body .cdn-popup-inner .custom-cdn-steps form.wpc-form-inline input[type="submit"]:hover,
body .dashboard-account-container-holder .dashboard-account-header .package-credits-link a:hover,
body .first-login-popup .button.button-primary:hover,
body .swal2-popup .button-primary:hover,
body .new-account-form-footer input.button-next-plan-v2:hover{
  background: var(--wpc-accent-deep) !important;
  border-color: var(--wpc-accent-deep) !important;
  color: #fff !important;
  filter: none !important;
}

/* The two Sample Data markers are labels, not controls — they say where the
   numbers came from and there is nothing to press. Wearing the primary fill
   made them the loudest thing on the chart. Quiet chip instead. */

body .canvas-container-dashboard .legend-title h3 {
  background: #eef2f7 !important;
  background-image: none !important;
  color: var(--wpc-ink-soft) !important;
  border: 1px solid var(--wpc-line) !important;
  border-radius: var(--wpc-r-xs) !important;
  box-shadow: none !important;
  font-size: 13px !important;
  font-weight: 600 !important;
  text-transform: none !important;
  letter-spacing: 0 !important;
}

/* ══ Badges are chips, not buttons ═══════════════════════════════════════
   Two badges were still wearing button clothes after the flatten pass, both
   because they are <span>s and the pass only walked controls.

   dashboard.css:22 paints .unlimited-badge with the same 25° blue-to-indigo
   gradient that was taken off every button — so the loudest, most saturated
   object on the site list is a passive label saying "Unlimited". It also sat
   at radius 20px, which is on no scale: too round for a rectangle, not round
   enough to read as a pill. Soft accent chip, accent-deep text: still
   unmistakably the good state, no longer competing with Add Website.

   Radius is --wpc-r-xs, not the pill it wore on the first pass. §1 reserves
   the pill for one standalone CTA that ends a flow; a pill sitting in a table
   cell reads as a second design system, which is the exact defect the badge
   pass existed to remove.

   The plugin's .wpc-beta-badge and .wpc-oa-beta were restyled here too, and
   are not any more — see the BOUNDARY note above. The plugin's badge is 9px
   uppercase (v4/css/style.css:1561), which is below any legible floor, but it
   is the plugin's to fix. REPORT UPSTREAM with the .btn-gradient bug. */
body .unlimited-badge {
  background: var(--wpc-accent-soft) !important;
  background-image: none !important;
  color: var(--wpc-accent-deep) !important;
  border: 1px solid rgba(60, 76, 223, 0.22) !important;
  border-radius: var(--wpc-r-xs) !important;
  padding: 4px 14px !important;
  width: auto !important;
  min-width: 108px;
  font-size: 13px !important;
  font-weight: 600 !important;
  box-shadow: none !important;
}
body .unlimited-badge span {
  color: inherit !important;
}


/* the advisory panel ships its own BETA marker in a <style> inside
   optimize-advisory.php:87 — a second class for the same word, at 10px. */


/* ══ The header bar was never on the scale ═══════════════════════════════
   The single largest cluster the audit turns up, and the only one that
   appears on all seven rendered pages: every control in the top bar sits at
   46px (48px for the active one) with a 4px radius, against a system of
   34/42/52 and 6/8/10/14/18. It is the first thing on the page and the one
   row where being 4px off the scale is visible without measuring — the header
   buttons and the buttons directly under them are different shapes.

   dashboard.css builds the height out of `padding: 8px 20px` plus
   `line-height: 30px`, so it drifts with the font; `.header-bar .header-btn`
   then pins `border-radius: 5px !important` and `padding: 7px 12px !important`
   at (0,2,0). `body .header-bar …` is (0,2,1) and settles both.

   inline-flex rather than inline-block for the same reason as the buttons:
   these have an icon and a label, and inline-block leaves the label sitting
   off the optical centre. */
body .header-bar .header-btn-plain,
body .header-bar .header-btn-filled,
body .header-bar .header-btn {
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--wpc-h-md) !important;
  padding: 0 16px !important;
  line-height: 1 !important;
  border-radius: var(--wpc-r-md) !important;
  text-decoration: none !important;
}
body .header-bar .header-btn span,
body .header-bar .header-btn-plain span,
body .header-bar .header-btn-filled span {
  line-height: 1 !important;
  vertical-align: baseline !important;
}
/* the icon pseudo-element carried the gap as a margin; the flex gap owns it
   now, and a stray 10px on top of it reads as a broken hit area. */
body .header-bar .header-btn-filled .header-btn-icon {
  margin-right: 0 !important;
}

/* The single most repeated finding in the audit: this label appeared on all
   seven rendered pages at 3.35:1. dashboard.css:202/281 paints it #437FE7 on
   the #eceefb chip — a stray blue that is neither the accent nor legible, and
   the chip's own tint is what eats the ratio. The brand accent on the same
   ground measures 5.53:1, so the fix is also the fix for "why is the header a
   different blue from every other blue in the portal".

   `:not(.header-btn-active)` is load-bearing, not tidiness. The Accent button
   in this bar is `.header-btn-filled.header-btn-active`, and its white label is
   set on the anchor for the span to inherit — and an inherited value loses to
   ANY rule that targets the child directly, whatever the specificity. Without
   the exclusion this rule painted "Add New Website" accent-on-accent: 1.00:1,
   an invisible label, on all seven pages. */
body .header-bar .header-btn-filled:not(.header-btn-active),
body .header-bar .header-btn-filled:not(.header-btn-active) span {
  color: var(--wpc-accent) !important;
}

/* the two row actions are 33px — one pixel under the smallest size in the
   system, which is also one pixel under the point where a 34px icon button
   stops being a fiddly target in a dense table.

   Not scoped to .table-body: the same two classes appear outside the site
   list, and scoping the geometry to the table left them as 33px circles
   everywhere else. The style guide is where that showed up. */
body .button-view-site,
body .button-delete-site {
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  width: var(--wpc-h-sm) !important;
  height: var(--wpc-h-sm) !important;
  min-height: var(--wpc-h-sm) !important;
  border-radius: var(--wpc-r-sm) !important;
}

/* ══ The settings tab strip ══════════════════════════════════════════════
   view-site-settings.css blanks the outline on the whole tab list with
   `outline: none !important` at (0,4,x), which is above the :where() floor —
   so all seven tabs of the old settings nav are invisible to a keyboard. That
   list IS the navigation of that page: without a ring there is no way to tell
   which tab you are about to open. Same specificity, plus :focus-visible. */



/* ══ Press ═══════════════════════════════════════════════════════════════
   PORTAL-STANDARD §2: "scale(.97) / 120ms on every button. A button that
   doesn't compress isn't ours." It was written into the standard and never
   built, so every control in the portal was inert under the finger — the
   click landed, the page changed a beat later, and nothing in between said
   the press was received. That gap is most of what "cheap" means on a
   control.

   Compression, not travel: scale takes no layout, cannot nudge a neighbour,
   and works the same on a 34px icon button and a 52px CTA. The transition
   only covers transform, so it can never animate a colour change that some
   other rule owns.

   `[class]` keeps this at (0,2,0), above the hover-lift kill list once that
   list stopped matching during a press, and above the plugin's own (0,1,0)
   !important resting transforms. */
:where(.button, .btn, button, input[type="submit"], input[type="button"],
       .swal2-styled, .paginate_button, .header-btn, .header-btn-plain,
       .header-btn-filled,
       .button-view-site, .button-delete-site, .copy-key)[class] {
  transition: transform 120ms cubic-bezier(.25, .8, .25, 1),
              background-color 140ms ease, border-color 140ms ease,
              color 140ms ease, box-shadow 140ms ease;
}
:where(.button, .btn, button, input[type="submit"], input[type="button"],
       .swal2-styled, .paginate_button, .header-btn, .header-btn-plain,
       .header-btn-filled,
       .button-view-site, .button-delete-site, .copy-key)[class]:active {
  transform: scale(.97) !important;
}
/* A classless submit outside a styled form falls all the way through to the
   UA sheet: 30px tall, 5px radius, centred grey bevel. Nothing was wrong on the
   dashboard because `.new-search-form input[type=submit]` catches the two that
   live there — which is the same class-scoping trap that left the row-action
   buttons as 33px circles and the quota chips unstyled outside the site list.
   Third instance of one bug, so this time the base case gets a rule.

   Deliberately at (0,1,1) with no !important: every scoped rule in this file
   and in dashboard.css/popups.css outranks it, so this only ever paints a
   submit that nothing else claimed. It is the Ghost role, because a submit no
   one bothered to class is by definition not the view's one primary. */
input[type="submit"],
input[type="reset"] {
  height: var(--wpc-h-md);
  min-height: var(--wpc-h-md);
  padding: 0 20px;
  border: 1px solid var(--wpc-line-strong);
  border-radius: var(--wpc-r-md);
  background: var(--wpc-panel);
  color: var(--wpc-ink);
  font-size: 14px;
  font-weight: 600;
  text-transform: none;
  cursor: pointer;
}
input[type="submit"]:hover:not(:active),
input[type="reset"]:hover:not(:active) {
  background: var(--wpc-sunk);
  border-color: var(--wpc-accent);
  color: var(--wpc-accent-deep);
}

/* the site-search submits carry no class at all, so `[class]` above never
   matched them — the two controls beside the search field were the only ones
   on the dashboard that stayed rigid. Type is the hook that always exists. */
body input[type="submit"],
body input[type="button"],
body input[type="reset"] {
  transition: transform 120ms cubic-bezier(.25, .8, .25, 1),
              background-color 140ms ease, border-color 140ms ease, color 140ms ease;
}
body input[type="submit"]:active,
body input[type="button"]:active,
body input[type="reset"]:active {
  transform: scale(.97) !important;
}
body input[type="submit"]:disabled:active,
body input[type="button"]:disabled:active {
  transform: none !important;
}

/* a disabled control must not compress — §2 again: without this a button
   disabled mid-press keeps the 0.97 and sits permanently dented. */
:where(.button, .btn, button, input[type="submit"], input[type="button"],
       .swal2-styled)[class]:disabled:active,
:where(.button, .btn, button, input[type="submit"], input[type="button"],
       .swal2-styled)[class][disabled]:active,
:where(.button, .btn, button)[class].disabled:active,
:where(.button, .btn, button)[class].loading:active {
  transform: none !important;
}

/* the optimize button in the plugin header is the one control the generic
   press rule cannot reach: the plugin lifts it with translateY(-2px)
   !important at the same specificity, from a sheet that loads earlier, and it
   carries a JS-driven running state on top. Name it. */


/* ══ Hover deepens one step ══════════════════════════════════════════════
   The roles above define their own hover fills. This is the floor for the
   controls that have none — the row actions, the pagination, the copy
   affordance — so every clickable thing in the portal answers the cursor
   with the same gesture instead of a third of them staying dead. */
body .button-view-site:hover,
body .button-delete-site:hover {
  background: var(--wpc-accent-soft) !important;
  border-color: transparent !important;
}
body .button-delete-site:hover {
  background: #fdeceb !important;
  color: var(--wpc-danger) !important;
}
body .copy-key:hover {
  color: var(--wpc-accent) !important;
  background: var(--wpc-accent-soft) !important;
}

@media (prefers-reduced-motion: reduce) {
  :where(.button, .btn, button, input[type="submit"], input[type="button"],
         .swal2-styled, .paginate_button, .header-btn, .header-btn-plain,
         .header-btn-filled,
         .button-view-site, .button-delete-site, .copy-key)[class] {
    transition: background-color 1ms linear;
  }
  :where(.button, .btn, button, input[type="submit"], input[type="button"],
         .swal2-styled, .paginate_button, .header-btn, .header-btn-plain,
         .header-btn-filled,
         .button-view-site, .button-delete-site, .copy-key)[class]:active {
    transform: none !important;
  }
}

/* ══ Skeletons, not spinners ═════════════════════════════════════════════
   Every loading state in the portal was the same asset: an 80px loading.svg
   centred on a white card, in some places pulsed between 0.6 and 0.3 opacity
   forever. Two things are wrong with it and only one is aesthetic.

   The functional problem is layout shift. .box-content-loading is 25px of
   padding around an 80px image — about 130px tall. The .box-content-loaded
   it is replaced by is roughly 240px. So every dashboard card grows ~110px
   the moment its stats land, and anything below it moves down under the
   cursor. A skeleton traced from the real content reserves the right space,
   and the swap is invisible.

   The perceptual one: a spinner carries no information beyond "wait". A
   skeleton tells you what is coming — two figures, then two rows — so the
   page is legible before it has any data in it.

   The sheen runs six times (~8.4s) and then rests flat. PORTAL-STANDARD §3
   bans a shimmer that loops longer than the wait it stands for: past about
   ten seconds the request has failed or stalled, and an animation still
   playing is decoration claiming progress that isn't happening. It also
   stops entirely under prefers-reduced-motion, where the bars are just
   bars. Markup comes from wpc_skeleton() in classes/global-functions.php. */
:root {
  --wpc-skel: #e7ebf2;
  --wpc-skel-lit: #f3f6fa;
}
.wpc-skel-wrap {
  width: 100%;
}
.wpc-skel-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.wpc-skel {
  display: block;
  background-color: var(--wpc-skel);
  background-image: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 0%,
    var(--wpc-skel-lit) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  background-size: 220% 100%;
  background-position: 120% 0;
  background-repeat: no-repeat;
  border-radius: var(--wpc-r-xs);
  animation: wpc-skel-sheen 1.4s ease-in-out 6;
}
@keyframes wpc-skel-sheen {
  from { background-position: 120% 0; }
  to   { background-position: -120% 0; }
}

/* Sizes are the type they stand in for, not arbitrary bars: a label is the
   13px caption's box, a figure is the 22px .count's box. */
.wpc-skel-label  { height: 11px; width: 62%; border-radius: 4px; }
.wpc-skel-figure { height: 20px; width: 78px; margin-top: 9px; }
.wpc-skel-title  { height: 15px; width: 44%; }
.wpc-skel-line   { height: 12px; width: 100%; }
.wpc-skel-line:last-child { width: 72%; }
.wpc-skel-bar    { height: 8px;  width: 100%; border-radius: 999px; }
.wpc-skel-cell   { height: 12px; }

/* Heights are tuned per shape against the real content measured in place:
   a dashboard stat card settles at 183px and the account card at 226px, so
   the placeholder is built to those numbers. That is the whole reason to
   prefer a skeleton — a spinner in a 130px box against 183px of content
   moved everything below it down by 53px the moment the data landed. */
.wpc-skel-body   { padding: 0; }
.wpc-skel-stats-box .wpc-skel-pair   { padding: 12px 20px; }
.wpc-skel-stats-box .wpc-skel-rows   { padding: 12px 20px; gap: 10px; }
.wpc-skel-stats-box .wpc-skel-figure { margin-top: 5px; }
.wpc-skel-pair {
  display: flex;
  gap: 24px;
  padding: 18px 20px;
  border-bottom: 1px solid var(--wpc-line);
}
.wpc-skel-pair .wpc-skel-stat { flex: 1 1 0; min-width: 0; }
.wpc-skel-rows {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px 20px;
}
.wpc-skel-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.wpc-skel-row .wpc-skel-label  { width: 40%; }
.wpc-skel-row .wpc-skel-figure { margin-top: 0; flex: 0 0 auto; }

/* The grid tracks are the site list's real column widths, measured off a
   drawn table (40px then 27 : 13.5 : 13.5 : 13.5 : 13.5 : 13.5), not read off
   the columnDefs — those declare 40px + 30% + 5×15%, which is 145%, and
   DataTables renormalises them at draw time. Row height is the drawn row's
   71px, so five placeholder rows are exactly as tall as five real ones and
   the footer does not move when they swap. */
.wpc-skel-table { padding: 0; }
.wpc-skel-trow {
  display: grid;
  grid-template-columns: 40px 27fr 13.5fr 13.5fr 13.5fr 13.5fr 13.5fr;
  align-items: center;
  height: 71px;
  border-bottom: 1px solid var(--wpc-line);
}
.wpc-skel-trow:last-child { border-bottom: 0; }
/* Cells are inset inside their track and sized to the copy they stand for —
   a domain is long and left-aligned, a figure is short and centred — so the
   block reads as a table waiting for rows, not as seven equal stripes. */
.wpc-skel-trow .wpc-skel-cell { width: 64%; justify-self: center; }
.wpc-skel-trow .wpc-skel-cell:nth-child(1) { width: 18px; height: 18px; border-radius: 999px; }
.wpc-skel-trow .wpc-skel-cell:nth-child(2) { width: calc(100% - 24px); justify-self: start; }
.wpc-skel-trow .wpc-skel-cell:nth-child(3) { width: 74px; }
.wpc-skel-trow .wpc-skel-cell:nth-child(4) { width: 56px; }
.wpc-skel-trow .wpc-skel-cell:nth-child(5) { width: 68px; }
.wpc-skel-trow .wpc-skel-cell:nth-child(6) { width: 78%; height: 8px; border-radius: 999px; }
.wpc-skel-trow .wpc-skel-cell:nth-child(7) { width: 78px; height: 26px; }

/* DataTables inserts .dataTables_processing as a sibling *before* the table,
   so with no DataTables stylesheet loaded — this theme ships none — the
   placeholder rendered above the column headers. It cannot be positioned
   absolutely over the tbody either, because the ajax beforeSend hides the
   tbody and the table collapses to its header, leaving nothing to cover.
   Ordering the wrapper puts the rows where rows go. */
body .dataTables_wrapper {
  display: flex;
  flex-direction: column;
}
body .dataTables_wrapper > table.dataTable { order: 1; }
body .dataTables_wrapper > .dataTables_processing { order: 2; }
body .dataTables_wrapper > .table_footer { order: 3; }

/* The old spinner is not deleted from every one of the forty-odd templates
   that carry it — the ones replaced here are the content placeholders, where
   a skeleton is the right answer. This kills the two properties that made the
   remaining ones worse than a still image: dashboard.css sizes the asset at
   80px, and sites-type.php:846 pulses it forever. */
body .box-content-loading img[src$="loading.svg"] {
  animation: none !important;
  opacity: 1 !important;
}

@media (prefers-reduced-motion: reduce) {
  .wpc-skel {
    animation: none !important;
    background-image: none !important;
  }
}

/* ══ The site list ═══════════════════════════════════════════════════════
   The Account Management table is the densest object in the portal and the
   one thing every customer uses. Four things were wrong with it, and the
   first is the one worth reading.

   1. The row hover was `font-weight: bold` (dashboard.css:880). Not a tint —
      a weight change on the whole row. Measured on a drawn table: hovering
      row 3 takes the site name from 98.8px to 100.6px and the API key from
      71.2px to 72.2px, so every glyph in the row moves under the pointer,
      and the background does not change at all. The only feedback the table
      gave was a reflow. It is a tint now, and the weight is left alone.

   2. Quota text is painted the same hue as the bar it labels, inline, from
      quota_html — so "12% Used" is #22B73A on white at 2.66:1, "63% Used" is
      #FFB94F at 1.71:1, and the credits readout beside it is the same. These
      are the only numbers in the row. Each hue is remapped to a variant
      darkened until it clears 4.75:1 on every ground it is actually drawn
      on — plain white, the #FBFBFB zebra row, and each tinted chip
      composited over both. Deriving them against white alone left three at
      4.36-4.42, because half the rows are the zebra. The bar keeps the
      original saturated colour: a 5px bar is not text and does not have to
      pass. Red already cleared and is left exactly as it is.

   3. .copy-key is 73×25 — the only clickable thing in the table besides the
      row itself, styled as body copy, under both the 34px house minimum and
      WCAG's 24px. It gets the height, a chip ground and a hover, so it looks
      like the control it is.

   4. Quota chips disagreed on width (max-width 150 vs min-width 108) and
      height, so the column read as ragged. One geometry for all of them. */

/* 1 · hover is a tint. The zebra rule below it is !important, so this has to
      be too — equal specificity, later sheet, so it wins. */
body .table-holder .table-body .table-row:hover {
  font-weight: inherit !important;
  font-family: inherit !important;
  background: var(--wpc-accent-soft) !important;
}
body .table-holder .table-body .table-row {
  transition: background-color 120ms ease;
}

/* 2 · quota text, remapped hue by hue. The colours arrive as inline styles
      on server-rendered markup, so an attribute match is the only hook;
      case-insensitive because the API is not consistent about hex case. */
body .progress-text[style*="#22B73A" i],
body .credits-text[style*="#22B73A" i],
body .shared-quota-container.green .shared-quota-text { color: #177e28 !important; }
body .progress-text[style*="#FFB94F" i],
body .credits-text[style*="#FFB94F" i],
body .shared-quota-container.yellow .shared-quota-text { color: #9d5f00 !important; }
body .progress-text[style*="#EC7C28" i],
body .credits-text[style*="#EC7C28" i],
body .shared-quota-container.orange .shared-quota-text { color: #ae530f !important; }
body .progress-text[style*="#7B5EFF" i],
body .credits-text[style*="#7B5EFF" i],
body .shared-quota-container.purple .shared-quota-text { color: #6b4aff !important; }
body .shared-quota-container.blue .shared-quota-text { color: #2266e2 !important; }
/* red is 5.30:1 on white and 4.99:1 on its own chip — already legible */

/* the not-connected chip carries the site's NAME, at 2.13:1. It is the only
   row where a person cannot read which site they are looking at. */
body .badge-expire-in { color: #4a5b70 !important; }

/* 3 · the key is a control */
body .table-holder .table-body .table-row .copy-key,
body .copy-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--wpc-h-sm);
  padding: 0 12px !important;
  line-height: 1 !important;
  border-radius: var(--wpc-r-xs);
  background: var(--wpc-sunk);
  border: 1px solid var(--wpc-line);
  color: var(--wpc-ink-soft) !important;
  /* a truncated hex key is not prose. A mono stack keeps the glyphs on one
     width so two keys are comparable at a glance; tabular-nums was tried
     first and made "1111..." read as spaced-out ones in the page's face. */
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 13px;
  letter-spacing: 0.01em;
  text-decoration: none !important;
}
/* ── The copy confirmation ────────────────────────────────────────────────
   This rule used to say `display: flex !important`, and that one word is why
   the customer saw "Copied!" sitting over their access key forever: jQuery's
   .fadeOut() finishes by writing an inline `display: none`, and an inline
   style loses to !important. The flash faded to opacity 0 and then had its
   display forced back to flex — visible, permanently.

   So visibility is opacity now, and nothing in this block touches `display`.
   That is not a workaround for the old JS; it is the more robust contract in
   both directions — no future !important on display can strand it visible, and
   opacity is what transitions cleanly anyway. The JS (assets/js/functions.js)
   adds and removes .is-in; it no longer animates anything itself.

   Two receipts, two treatments now. Inside the dashboard table the flash is
   one control among many in a dense row — it keeps the flat semantic green
   the row's eye is already tuned to, and still yields its own rim to the
   chip's permanent one below rather than draw two rings in the same 1px.

   Standing alone — the new-site success screen, the multisite popup — the
   flash IS the screen, competing with nothing, so the aurora gets to be the
   receipt rather than just its edge: white ground, green ink, and the rim
   is what moves. Owner call, 17 Aug: the solid green read as one more loud
   block on a screen this pass spent quieting down everywhere else. */
body .copied,
body .copy-key .copied,
body .copy-holder .copied,
body .apikey-holder .copied {
  position: absolute;
  top: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
  left: 0 !important;
  display: flex !important;
  align-items: center;
  justify-content: center;
  gap: 6px;
  line-height: 1 !important;
  padding: 0 !important;
  border-radius: inherit;
  /* the page's face, not the control's. `inherit` would take the key chip's
     mono stack and set the word "Copied" as though it were data. */
  font-family: "Proxima Nova Regular", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 13px;
  font-weight: 600 !important;
  letter-spacing: 0;
  opacity: 0;
  transform: scale(0.97);
  pointer-events: none;
  transition: opacity 200ms ease, transform 200ms ease;
}
/* the dashboard-table receipt: flat semantic green, unchanged */
body .copy-key .copied {
  --wpc-copied-glow: rgba(31, 125, 76, 0.5);
  background-color: var(--wpc-ok) !important;
  background-image: none !important;
  box-shadow: 0 6px 20px -10px var(--wpc-copied-glow);
  color: #fff !important;
}
/* the standing-alone receipt: white ground, the rim carries it. `inherit`
   on the shared rule above resolves to 0 here — .apikey-holder and the
   popup's holder never set a radius of their own, only the key span inside
   them does (5px) — so this states the corner explicitly instead of
   inheriting one that was never really there. */
body .copy-holder .copied,
body .apikey-holder .copied {
  background-color: #fff !important;
  background-image: none !important;
  border-radius: 5px;
  box-shadow: 0 8px 20px -14px rgba(28, 36, 48, 0.35);
  color: var(--wpc-ok) !important;
}
/* ── The chip's own rim ───────────────────────────────────────────────────
   A gradient border, which CSS has no property for: `border-image` cannot
   follow a border-radius, so the ring is a filled pseudo-element with its own
   middle masked out. Two masks, one clipped to the content box and one to the
   border box, composited to keep only the difference — a 1px band that hugs
   any radius exactly.

   It lives on ::after for a second reason. dashboard.css has
   `.copy-key .copied { background: linear-gradient(...) !important }`, and an
   !important SHORTHAND also resets background-position to `initial` at
   !important priority — which beats a CSS animation outright. A sweep
   declared on .copied itself measured 0% 0% for the whole 700ms: a gradient
   that could not move. Nothing in the portal styles these pseudo-elements, so
   the travel happens where no legacy rule reaches it.

   inset: -1px puts the band over the host's own 1px border rather than
   inside it, so the control keeps its exact footprint and nothing reflows.
   This is the chip's ALWAYS-ON edge, not the flash — 150%, not the CTA's
   220%, so a 1px band an inch long still shows blue AND violet at rest with
   half a width left to travel on copy. */
body .copy-key::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1px;
  background-image: linear-gradient(100deg,
      #2f3dbd 0%, #3c4cdf 22%, #1668c4 46%, #6d3ff0 74%, #2f3dbd 100%);
  background-size: 150% 100%;
  background-position: 0% 50%;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
/* ── The standing-alone rim — a conic turn, not a linear one ──────────────
   A linear travel has a start and an end that are different colours, so
   holding `forwards` freezes the ring on whatever the gradient's tail
   happens to be — on a white ground with nowhere to hide, that read as a
   stray blue edge rather than a finished ring. A conic gradient is a full
   turn: animating it exactly 360° lands back on the identical frame it
   started from, so "stop and hold" always reads as one complete, evenly-lit
   ring, never a ring caught mid-sentence. Same masking technique as the
   chip's rim above, same reason it lives on ::after. */
@property --wpc-ghost-orb {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
body .copy-holder .copied::after,
body .apikey-holder .copied::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1px;
  background-image: conic-gradient(from var(--wpc-ghost-orb),
      #5FE3D0, #7DA7FF, #9B7BFF, #5FE3D0);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
/* the chip's own hairline steps aside for the rim — two borders in the same
   1px would read as a smudge, and the rim is the one carrying the meaning */
body .table-holder .table-body .table-row .copy-key,
body .copy-key,
body .copy-key:hover {
  border-color: transparent !important;
}
/* One travel, on the copy — not on hover as well. Two rules naming the same
   keyframes do not restart each other: an animation only restarts when its
   NAME changes, so a hover sweep still applying its `forwards` end state
   swallowed the copy sweep entirely (measured: 100% before the click, 100%
   after). Hover already answers with colour; the sweep belongs to the moment
   something actually happened.

   The JS adds .is-copying to the control it copied from, because CSS cannot
   reach a parent from a child, and :has() is not worth spending on a class
   the JS is already in a position to set. */
body .copy-key.is-copying::after {
  animation: wpc-aurora-sweep 700ms cubic-bezier(0.4, 0, 0.2, 1) 1 forwards;
}
/* Inside a key chip the flash would draw its rim exactly where the chip
   already draws one — two identical rings stacked, which is invisible except
   at the corners, where the antialiasing doubles up and darkens. The chip's
   rim is the one that sweeps, so the flash yields to it. The holder shapes
   (the multisite popup) have no rim of their own and keep theirs. */
body .copy-key .copied::after { content: none; }
/* the check is CSS, not markup, so the announced text stays "Copied" and a
   screen reader is never read a glyph name */
body .copied::before {
  content: "✓";
  font-size: 14px;
  line-height: 1;
}
body .copied.is-in {
  opacity: 1;
  transform: scale(1);
  transition-duration: 120ms;      /* arrives faster than it leaves */
}
/* one travel, then still. The JS restarts it by removing and re-adding
   .is-in, so a second click reads as a second confirmation rather than as
   nothing happening. `forwards` holds the far end instead of snapping back
   while the slab is still on screen. */
body .copied.is-in::after {
  animation: wpc-ghost-orb-spin 700ms cubic-bezier(0.4, 0, 0.2, 1) 1 forwards;
}
@keyframes wpc-aurora-sweep {
  from { background-position: 0% 50%; }
  to   { background-position: 100% 50%; }
}
@keyframes wpc-ghost-orb-spin {
  to { --wpc-ghost-orb: 360deg; }
}
/* the glyph lands a beat after the slab, which is what makes it read as a
   confirmation rather than a colour change */
body .copied.is-in::before {
  animation: wpc-copied-check 260ms cubic-bezier(0.34, 1.56, 0.64, 1) 1;
}
@keyframes wpc-copied-check {
  from { opacity: 0; transform: scale(0.4); }
  to   { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  body .copied { transition: none; transform: none; }
  body .copied.is-in,
  body .copied.is-in::after,
  body .copied.is-in::before,
  body .copy-key.is-copying::after { animation: none; transform: none; }
}
/* the flash is positioned against the control it confirms, and fills its
   padding box — the chip's hairline stays visible as a ring, so the control
   keeps its own footprint while it is covered */
body .copy-key,
body .copy-holder { position: relative; }

/* the live region the JS announces into — visually gone, still read out */
.wpc-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

body .copy-key:hover {
  background: var(--wpc-accent-soft);
  border-color: rgba(60, 76, 223, 0.28);
  color: var(--wpc-accent-deep) !important;
}

/* 4 · one geometry for every quota chip */
body .shared-quota-container,
body .unlimited-badge {
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  min-height: 30px;
  max-height: 30px;
  min-width: 148px !important;
  max-width: 148px !important;
  padding: 4px 12px !important;
  border-radius: var(--wpc-r-xs) !important;
  font-size: 13px !important;
  box-sizing: border-box;
}
body .shared-quota-container .shared-quota-text {
  margin: 0 !important;
  font-size: 13px !important;
}
/* the infinity glyph is set at 20px !important (dashboard.css:36), which made
   the Unlimited chip 39px tall against every other quota chip's 30px — the
   one chip in the column that did not line up, and the reason "one geometry"
   above was not enough on its own. */
body .unlimited-badge span {
  font-size: 15px !important;
  line-height: 1 !important;
  margin-left: 6px !important;
}

/* ══ Aurora — the one gradient control ═══════════════════════════════════
   §1 says no gradient on any control, and that rule earned its place: the
   portal had gradients on Save, on Search, on pagination, on a passive
   "Unlimited" label, so nothing read as more important than anything else
   and the flatten pass took them all off.

   This is the documented exception, and it is exactly one class used in
   exactly one place: the commercial CTA that ends a flow. It works only
   because everything around it is flat — the moment a second control wears
   it, both stop meaning anything and the rule should win instead.

   Three constraints it has to satisfy to be ours:

   · It carries white text, so every stop has to clear 4.5:1 against white.
     The brand's light blue #1c87f1 does not — it is 3.63:1 — so the light
     stop is #1668c4 at 5.51:1. The darkest stop is 8.32:1. That is the only
     reason this palette is not the header gradient.

   · It does not move unprompted. §3 bans looping pulses, and an aurora that
     drifts on its own is a looping pulse with better marketing. At rest it
     is a still gradient; the fill drifts once per hover, over 900ms, and
     stops. The RIM (below) does keep turning while the pointer holds the
     control — brand parity with wpcompress.com/pricing, owner-directed
     16 Aug — but that is motion the hand is actively causing and it ends
     the instant the hand leaves. Idle screens stay still either way.

   · It presses like everything else. The press rule already covers .button
     and .btn; this adds the lift-free hover and the glow.

   The rim is the marketing site's dark-hero button edge, translated: a 2px
   conic band (cyan → soft blue → violet, wpcompress.com's own hex stops)
   masked to the border ring exactly as the copy-confirmation rim above is,
   revealed on hover, spun by animating a registered angle property — the
   gradient itself never repaints, only its start angle moves. Browsers
   without @property render the band static: a rim, just not a turning one. */
@property --wpc-orb {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
@keyframes wpc-orb-spin {
  to { --wpc-orb: 360deg; }
}
.wpc-aurora[class]::before,
body .package-credits-link a::before,
body .step-2-non-js a.button.button-primary-grd.round::before,
body .step-3-non-js a.button.button-primary-grd.round::before,
body .step-2-js-snippet a.button.button-primary-grd.round::before,
body .step-3-js-snippet a.button.button-primary-grd.round::before {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: inherit;
  padding: 2px;
  background: conic-gradient(from var(--wpc-orb, 0deg),
      #5FE3D0, #7DA7FF, #9B7BFF, #7DA7FF, #5FE3D0);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: 0;
  pointer-events: none;
  transition: opacity 250ms ease;
}
.wpc-aurora[class]:hover:not(:active)::before,
body .package-credits-link a:hover:not(:active)::before,
body .step-2-non-js a.button.button-primary-grd.round:hover:not(:active)::before,
body .step-3-non-js a.button.button-primary-grd.round:hover:not(:active)::before,
body .step-2-js-snippet a.button.button-primary-grd.round:hover:not(:active)::before,
body .step-3-js-snippet a.button.button-primary-grd.round:hover:not(:active)::before {
  opacity: 1;
  animation: wpc-orb-spin 2.6s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  /* :not(:active) matches the animating selector's specificity exactly, so
     source order decides — this block must stay below the hover rule */
  .wpc-aurora[class]:hover:not(:active)::before,
  body .package-credits-link a:hover:not(:active)::before,
  body .step-2-non-js a.button.button-primary-grd.round:hover:not(:active)::before,
  body .step-3-non-js a.button.button-primary-grd.round:hover:not(:active)::before,
  body .step-2-js-snippet a.button.button-primary-grd.round:hover:not(:active)::before,
  body .step-3-js-snippet a.button.button-primary-grd.round:hover:not(:active)::before {
    animation: none;
  }
}
.wpc-aurora[class] {
  --wpc-aurora-glow: rgba(47, 61, 189, 0.45);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--wpc-h-md);
  padding: 0 22px;
  border: 0 !important;
  border-radius: var(--wpc-r-md);
  background-color: #2f3dbd;
  background-image: linear-gradient(100deg,
      #2f3dbd 0%, #3c4cdf 26%, #1668c4 50%, #6d3ff0 76%, #2f3dbd 100%);
  background-size: 220% 100%;
  background-position: 0% 50%;
  color: #fff !important;
  font-size: 15px;
  font-weight: 600;
  text-decoration: none !important;
  box-shadow: 0 6px 18px -10px var(--wpc-aurora-glow);
  transition: background-position 900ms cubic-bezier(0.4, 0, 0.2, 1),
              box-shadow 200ms ease,
              transform 120ms cubic-bezier(0.25, 0.8, 0.25, 1);
}
.wpc-aurora[class]:hover:not(:active) {
  background-position: 100% 50%;
  /* the pricing page's dual glow: near blue, far violet — the rim's own
     colours thrown onto the page, so ring and halo read as one light */
  box-shadow: 0 12px 30px -12px var(--wpc-aurora-glow),
              0 0 22px -2px rgba(59, 123, 255, 0.4),
              0 0 46px -10px rgba(155, 123, 255, 0.35);
  transform: none;
}
.wpc-aurora[class]:active { transform: scale(0.97) !important; }
.wpc-aurora[class]:focus-visible {
  outline: 2px solid var(--wpc-accent-deep) !important;
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .wpc-aurora[class] { transition: box-shadow 200ms ease; }
  .wpc-aurora[class]:hover { background-position: 0% 50%; }
}

/* The one place it is used: "Get Unlimited Access" on the dashboard account
   card — the portal's only upgrade CTA, which until now was a bare blue link
   sitting in the corner of a card, indistinguishable from a footnote. */
body .package-credits-link a {
  --wpc-aurora-glow: rgba(47, 61, 189, 0.45);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--wpc-h-sm);
  padding: 0 16px;
  border-radius: var(--wpc-r-md);
  background-color: #2f3dbd;
  background-image: linear-gradient(100deg,
      #2f3dbd 0%, #3c4cdf 26%, #1668c4 50%, #6d3ff0 76%, #2f3dbd 100%);
  background-size: 220% 100%;
  background-position: 0% 50%;
  color: #fff !important;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none !important;
  box-shadow: 0 6px 18px -10px var(--wpc-aurora-glow);
  transition: background-position 900ms cubic-bezier(0.4, 0, 0.2, 1),
              box-shadow 200ms ease,
              transform 120ms cubic-bezier(0.25, 0.8, 0.25, 1);
}
body .package-credits-link a:hover:not(:active) {
  background-position: 100% 50%;
  box-shadow: 0 12px 30px -12px var(--wpc-aurora-glow),
              0 0 22px -2px rgba(59, 123, 255, 0.4),
              0 0 46px -10px rgba(155, 123, 255, 0.35);
  transform: none !important;
}
body .package-credits-link a:active { transform: scale(0.97) !important; }
@media (prefers-reduced-motion: reduce) {
  body .package-credits-link a:hover { background-position: 0% 50%; }
}

/* ── The other place a flow ends: the site-linked success screen ──────────
   "Return to Dashboard" after a key is issued is the same kind of moment as
   the upgrade CTA — the one thing to do next, at the end of a flow, with
   nothing competing (its row-mate is Ghost). Owner-directed 16 Aug, same
   parity request as the rim above. The ink-solid `.button-v3:not(.empty)`
   force at (0,3,0) !important would swallow this, hence the scoped
   (0,4,2) selectors and the matching !important weights. The pill radius is
   the button's own and stays — the pricing page's buttons are pills too. */
body .step-2-non-js a.button.button-primary-grd.round,
body .step-3-non-js a.button.button-primary-grd.round,
body .step-2-js-snippet a.button.button-primary-grd.round,
body .step-3-js-snippet a.button.button-primary-grd.round {
  --wpc-aurora-glow: rgba(47, 61, 189, 0.45);
  position: relative;
  border: 0 !important;
  background-color: #2f3dbd !important;
  background-image: linear-gradient(100deg,
      #2f3dbd 0%, #3c4cdf 26%, #1668c4 50%, #6d3ff0 76%, #2f3dbd 100%) !important;
  background-size: 220% 100% !important;
  background-position: 0% 50% !important;
  color: #fff !important;
  box-shadow: 0 6px 18px -10px var(--wpc-aurora-glow) !important;
  transition: background-position 900ms cubic-bezier(0.4, 0, 0.2, 1),
              box-shadow 200ms ease,
              transform 120ms cubic-bezier(0.25, 0.8, 0.25, 1);
}
body .step-2-non-js a.button.button-primary-grd.round:hover:not(:active),
body .step-3-non-js a.button.button-primary-grd.round:hover:not(:active),
body .step-2-js-snippet a.button.button-primary-grd.round:hover:not(:active),
body .step-3-js-snippet a.button.button-primary-grd.round:hover:not(:active) {
  background-position: 100% 50% !important;
  box-shadow: 0 12px 30px -12px var(--wpc-aurora-glow),
              0 0 22px -2px rgba(59, 123, 255, 0.4),
              0 0 46px -10px rgba(155, 123, 255, 0.35) !important;
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  body .step-2-non-js a.button.button-primary-grd.round:hover:not(:active),
  body .step-3-non-js a.button.button-primary-grd.round:hover:not(:active),
  body .step-2-js-snippet a.button.button-primary-grd.round:hover:not(:active),
  body .step-3-js-snippet a.button.button-primary-grd.round:hover:not(:active) {
    background-position: 0% 50% !important;
  }
}

/* ── The success screen itself — quieter type, choreographed arrival ──────
   The screen read as clipart: illustration jammed against a shouting Title
   Case headline, then a wall of same-weight text. Type first — the heading
   drops to 24px/600 sentence-case ink (the copy edits live in the
   templates), the instruction becomes 14px ink-soft and stops pretending to
   be a heading, and the illustration gets air below it and a ceiling so it
   reads as an accent, not the content.

   The arrival is §169's choreography: each row rises 10px into place, 60ms
   apart, once. The blocks are display:none until the flow succeeds, and a
   box that has never been rendered starts its animations the moment it
   first is — so the sequence plays exactly when the screen appears, no JS.
   (The illustration's own internal drift lives inside the SVG; an <img>'s
   document starts at page load regardless of visibility, which is why the
   entrance cannot live there.) */
body .step-2-non-js > *,
body .step-3-non-js > *,
body .step-2-js-snippet > *,
body .step-3-js-snippet > * {
  animation: wpc-rise 480ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
body .step-2-non-js > *:nth-child(2), body .step-3-non-js > *:nth-child(2),
body .step-2-js-snippet > *:nth-child(2), body .step-3-js-snippet > *:nth-child(2) { animation-delay: 60ms; }
body .step-2-non-js > *:nth-child(3), body .step-3-non-js > *:nth-child(3),
body .step-2-js-snippet > *:nth-child(3), body .step-3-js-snippet > *:nth-child(3) { animation-delay: 120ms; }
body .step-2-non-js > *:nth-child(4), body .step-3-non-js > *:nth-child(4),
body .step-2-js-snippet > *:nth-child(4), body .step-3-js-snippet > *:nth-child(4) { animation-delay: 180ms; }
body .step-2-non-js > *:nth-child(5), body .step-3-non-js > *:nth-child(5),
body .step-2-js-snippet > *:nth-child(5), body .step-3-js-snippet > *:nth-child(5) { animation-delay: 240ms; }
body .step-2-non-js > *:nth-child(n+6), body .step-3-non-js > *:nth-child(n+6),
body .step-2-js-snippet > *:nth-child(n+6), body .step-3-js-snippet > *:nth-child(n+6) { animation-delay: 300ms; }
@keyframes wpc-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
body .step-2-non-js img[src*="add-new"],
body .step-3-non-js img[src*="add-new"],
body .step-2-js-snippet img[src*="js-snippet-logo"],
body .step-3-js-snippet img[src*="js-snippet-logo"] {
  max-width: 320px;
  height: auto;
  margin-bottom: 36px;
}
body .step-2-non-js h2,
body .step-3-non-js h2,
body .step-2-js-snippet h2,
body .step-3-js-snippet h2 {
  font-size: 24px !important;
  font-weight: 600 !important;
  letter-spacing: -0.01em;
  color: var(--wpc-ink) !important;
  margin-bottom: 10px !important;   /* inline style carries 25px */
}
body .step-2-non-js h5,
body .step-3-non-js h5,
body .step-2-js-snippet h5,
body .step-3-js-snippet h5 {
  font-size: 14px !important;
  font-weight: 400 !important;
  line-height: 1.5;
  color: var(--wpc-ink-soft) !important;
  max-width: 52ch;
  margin: 0 auto 28px !important;
}
@media (prefers-reduced-motion: reduce) {
  body .step-2-non-js > *, body .step-3-non-js > *,
  body .step-2-js-snippet > *, body .step-3-js-snippet > * {
    animation: none;
  }
}

/* ── "Download the WordPress Plugin" — a Ghost chip, not a bare link ──────
   On the same success screen the plugin download was a naked text link
   floating between the key and the buttons — an action dressed as a
   footnote. Ghost role, same grammar as every other quiet action, with a
   download glyph drawn as a mask so it takes the text's colour in every
   state. Scoped by href, not container, because two templates hold the link
   in different wrappers and one (new-site-old) points at wordpress.org. */
body a.dl-wp-link[href*="download-plugin"],
body a.dl-wp-link[href*="wp-compress-image-optimizer.zip"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--wpc-h-sm);
  padding: 0 16px;
  border: 1px solid var(--wpc-line-strong);
  border-radius: var(--wpc-r-md);
  background: transparent;
  color: var(--wpc-ink) !important;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none !important;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
body a.dl-wp-link[href*="download-plugin"]::before,
body a.dl-wp-link[href*="wp-compress-image-optimizer.zip"]::before {
  content: "";
  width: 14px;
  height: 14px;
  flex: none;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3v10m0 0l-4-4m4 4l4-4M5 21h14' stroke='%23000' stroke-width='2.2' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3v10m0 0l-4-4m4 4l4-4M5 21h14' stroke='%23000' stroke-width='2.2' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
body a.dl-wp-link[href*="download-plugin"]:hover,
body a.dl-wp-link[href*="wp-compress-image-optimizer.zip"]:hover {
  background: var(--wpc-accent-soft);
  border-color: var(--wpc-accent);
  color: var(--wpc-accent) !important;
}

/* ══ Whitelabel settings — bring it inside the referee ══════════════════
   whitelabel.css runs its own design system: a second accent (#2563eb vs
   this app's --wpc-accent), an unconditional 100px pill on every .wl-btn
   regardless of context, ~30-32px controls under the 34px height floor, a
   12px badge under the 13px chip/badge floor, and a 3-layer shadow + 16px
   radius on .wl-unified-card matching neither the 14px card token nor the
   18px popup token. It loads at default priority, this sheet at 999, and
   still none of it was ever caught — the one screen selling an agency on
   white-labeling their own brand was, itself, off-brand. Same tokens as
   everywhere else; .wpc-wl's own custom properties feed every rule below
   them in that file, so re-pointing --primary here recolors all of it. */
.wpc-wl {
  --primary: var(--wpc-accent) !important;
  --primary-hover: var(--wpc-accent-deep) !important;
  --primary-light: var(--wpc-accent-soft) !important;
  --primary-ring: rgba(60, 76, 223, 0.12) !important;
}
.wl-btn {
  border-radius: var(--wpc-r-md) !important;
  min-height: var(--wpc-h-sm) !important;
  padding: 0 18px !important;
}
.wl-badge {
  font-size: 13px !important;
}
.wl-unified-card {
  border-radius: var(--wpc-r-lg) !important;
  box-shadow: none !important;
}

/* ── "Plan limit reached" — the full-meter screen ─────────────────────────
   The old screen was clipart theatre: the add-new illustration (the same
   one the SUCCESS screen uses), a shouting Title Case headline, and copy
   that always blamed websites even when the gate was credits. The
   heading and copy now match the success family: sentence case, one short
   line, the illustration kept as an accent with a ceiling, and the arrival is the same §169 choreography:
   the block is display:none until a limit code routes here, so the
   animations play once, the moment it first renders. */
body .limit-reached > * {
  animation: wpc-rise 480ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
body .limit-reached > *:nth-child(2) { animation-delay: 60ms; }
body .limit-reached > *:nth-child(3) { animation-delay: 120ms; }
body .limit-reached > *:nth-child(4) { animation-delay: 180ms; }
body .limit-reached > *:nth-child(n+5) { animation-delay: 240ms; }
body .limit-reached h2 {
  font-size: 24px !important;
  font-weight: 600 !important;
  letter-spacing: -0.01em;
  color: var(--wpc-ink) !important;
  margin-bottom: 10px !important;
}
body .limit-reached h5 {
  font-size: 14px !important;
  font-weight: 400 !important;
  line-height: 1.5;
  color: var(--wpc-ink-soft) !important;
  max-width: 52ch;
  margin: 0 auto 28px !important;
}
body .limit-reached img[src*="add-new"] {
  max-width: 320px;
  height: auto;
  margin-bottom: 8px;
}
@media (prefers-reduced-motion: reduce) {
  body .limit-reached > * {
    animation: none;
  }
}
