/* Plus Jakarta Sans is no longer pulled in with @import here. An @import is
   render-blocking, can't be versioned or dequeued, and has to satisfy the page's
   Content-Security-Policy style-src — on a site with a strict CSP that silently
   left the whole design in a fallback face. It is now registered as a proper
   stylesheet in PHP (privacy_score_enqueue_assets), so it loads in parallel and
   the site can dequeue or swap it. The font-family declarations below carry
   system-ui fallbacks either way. */

/* ============================================================================
   Exposure Scanner — Aurora design system (dark mode)
   Ported from the "Exposure Flow" Aurora prototype. Token names and values
   mirror _ds/aurora-design-system tokens/{colors,typography,spacing}.css so
   the two stay comparable. Only the DARK value set is defined: the team scoped
   this to dark-only, and every token below is the [data-theme="dark"] value
   from the Aurora colour file. Adding light later = adding a second value block
   on .ps-wrap, nothing else needs to change.
   Everything is scoped to .ps-wrap so the plugin can't leak styles into the
   host Elementor page (and the page can't be themed from here).
   ========================================================================= */

.ps-wrap {
  /* — Aurora colour tokens (dark) — */
  --color-primary: #7B4FF7;
  --color-on-primary: #FFFFFF;
  --color-primary-container: #6440C8;
  --color-secondary: #CCB1FF;
  --color-on-secondary: #000000;
  --color-secondary-container: #D1B9FF;
  --color-on-secondary-container: #000000;

  --color-surface: #080808;
  --color-surface-container: #1A1C1D;
  --color-surface-container-high: #222426;
  --color-surface-container-higher: #2A2C2E;
  --color-surface-container-highest: #414447;
  --color-on-surface: #FFFFFF;
  --color-on-surface-variant: #A6A6A6;

  /* Raised from #414447. At that value the contrast against the card (#1A1C1D) was 1.73:1,
     failing WCAG 1.4.11 (3:1 for UI component boundaries) — and this token draws the boundary
     of the six OTP boxes, the field group, the state select and the divider between the phone
     and email rows. Because the OTP fill (#222426) is only 1.09:1 against the card, that
     border IS the entire visible edge of the code fields. Solved rather than guessed: #6B6E72 is the first value that clears 3:1 against BOTH the card
     (3.34:1) and surface-container-high (3.04:1). The second matters as much as the first,
     because the OTP boxes sit on the raised surface. A first attempt at #5E6165 measured only
     2.75:1 and was caught by the contrast assertion in the browser test.
     Kept as one token change rather than per-rule overrides so every boundary moves together. */
  --color-outline: #6B6E72;
  --color-outline-variant: #A6A6A6;

  --color-disabled: #4F4F52;
  --color-on-disabled: #000000;

  --color-error: #FF7876;
  --color-on-error: #000000;
  --color-error-container: #FF9391;
  --color-on-error-container: #000000;

  --color-warning: #FFCA83;
  --color-warning-container: #FFD59C;
  --color-on-warning-container: #000000;

  --color-success: #38EFBE;
  --color-success-container: #4CF1C5;
  --color-on-success-container: #000000;

  --color-brand-1: #60CFFF;
  --color-brand-2: #FDB98B;
  --color-brand-3: #E68CD7;
  --color-brand-4: #38EFBE;

  --color-inverse-surface: #FFFFFF;
  --color-on-inverse-surface: #000000;

  --color-transparency-40: rgba(255, 255, 255, 0.40);
  --color-transparency-20: rgba(255, 255, 255, 0.20);
  --color-transparency-5: rgba(255, 255, 255, 0.05);
  /* Button type scale — was referenced by .ps-seg-btn via an inline 14px fallback but
     never defined. Values taken from the Aurora token set. */
  --text-button-medium-size: 14px;
  --text-button-medium-line: 24px;
  --text-button-medium-weight: 600;

  /* — Spacing — */
  --space-2xs: 2px;
  --space-xs: 4px;
  --space-s: 8px;
  --space-sm: 12px;
  --space-m: 16px;
  --space-l: 20px;
  --space-xl: 24px;
  --space-2xl: 28px;
  --space-3xl: 32px;
  --space-4xl: 40px;
  --space-5xl: 48px;

  /* — Radii — */
  --radius-xs: 2px;
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-xl: 20px;
  --radius-card: 12px;
  --radius-pill: 999px;

  /* — Elevation (dark values) — */
  --shadow-elevation-light: 0 0 10px rgba(0, 0, 0, 0.30);
  --shadow-elevation-medium: 0 0 20px rgba(0, 0, 0, 0.30);
  --shadow-elevation-heavy: 0 8px 60px rgba(0, 0, 0, 0.80);

  /* — Typography — */
  --font-sans: 'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-weight-regular: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
  --font-weight-extrabold: 800;

  --text-headline-large-size: 32px;  --text-headline-large-line: 42px;  --text-headline-large-weight: 700;
  --text-headline-medium-size: 26px; --text-headline-medium-line: 36px; --text-headline-medium-weight: 600;
  --text-headline-small-size: 20px;  --text-headline-small-line: 30px;  --text-headline-small-weight: 600;
  --text-body-large-size: 18px;      --text-body-large-line: 26px;
  --text-body-medium-size: 16px;     --text-body-medium-line: 26px;
  --text-body-small-size: 14px;      --text-body-small-line: 24px;
  --text-label-large-size: 14px;     --text-label-large-line: 24px;     --text-label-large-weight: 700;
  --text-label-medium-size: 12px;    --text-label-medium-line: 20px;

  /* Viewport unit the prototype uses for fluid clamps. */
  --vwu: 1vw;

  /* — Legacy aliases — keep older rules and any inline styles working — */
  --surface: var(--color-surface);
  --surface-card: var(--color-surface-container-high);
  --surface-card-alt: var(--color-surface-container-higher);
  --outline: var(--color-outline);
  --text-primary: var(--color-on-surface);
  --text-secondary: var(--color-on-surface-variant);
  --accent: var(--color-secondary);
  --danger: var(--color-error);

  font-family: var(--font-sans);
  color: var(--color-on-surface);
  -webkit-font-smoothing: antialiased;
}

.ps-wrap *,
.ps-wrap *::before,
.ps-wrap *::after { box-sizing: border-box; }

/* Safety net for wpautop: an empty paragraph injected into the markup renders at
   zero height but still carries a bottom margin, adding phantom gaps. The
   shortcode strips blank lines to prevent these, but a theme or another filter can
   still introduce one, so neutralise them here too. */
/* Truly empty paragraphs only.
   
   This deliberately does NOT also match `p:not([class])`. That was tried, on the
   reasoning that every paragraph the plugin emits carries a class so an unclassed one
   can't be ours — which is true, but CSS cannot test content, so the rule hid any
   unclassed <p> regardless of what was inside it. A classless paragraph of real text
   injected into the card by a theme or another plugin would vanish, and it contradicted
   the guarantee the JS layer makes explicitly.

   Whitespace-only paragraphs (`<p> </p>`, which is NOT :empty under Selectors 3, the
   level browsers implement) are handled where content can actually be inspected:
   server-side in privacy_score_strip_empty_paragraphs(), which now hooks Elementor's
   own render pipeline so they never reach the browser, and in the JS sweep, which tests
   textContent. This rule stays as a cheap first-paint guard for the genuinely empty
   case. */
.ps-wrap p:empty { display: none !important; }

/* wpautop turns a single newline between two inline elements into `<br />`. Inside a
   flex container that <br> is blockified into a zero-height flex item, which costs a
   full `gap` on either side — the plans screen's spec tiles were arriving as icon, <br>,
   value, <br>, unit, doubling their 6px gaps.
   
   The shortcode now returns its markup with no whitespace between tags, so there is
   nothing left for wpautop to convert. This rule is the belt-and-braces: it also
   neutralises HTML that was already generated and cached before that fix shipped, which
   matters on a site fronted by an optimiser. Safe as a blanket rule because this plugin
   authors no <br> anywhere — neither in the PHP template nor in the JS-rendered
   screens. */
.ps-wrap br { display: none !important; }

/* Screen headings get tabindex="-1" and are focused by script on every screen
   change so assistive tech announces the new view. They can't be reached by
   tabbing, so hiding the focus ring costs no keyboard affordance — and it stops a
   blue browser outline appearing around the heading of each screen. */
.ps-wrap [tabindex="-1"]:focus,
.ps-wrap [tabindex="-1"]:focus-visible { outline: none !important; box-shadow: none !important; }

/* Host themes commonly apply text-transform: capitalize (or uppercase) to buttons
   and links, which was title-casing every label — "Full Scan", "View All 12",
   "Reclaim My Privacy". The design is sentence case, so opt out explicitly. */
.ps-wrap .ps-btn,
.ps-wrap .ps-seg-btn,
.ps-wrap .ps-link-btn,
.ps-wrap .ps-select-none,
.ps-wrap .ps-accordion-trigger,
.ps-wrap .ps-verify-footer a,
.ps-wrap .ps-h1,
.ps-wrap .ps-h2 { text-transform: none !important; }

/* ── Shell / card ──────────────────────────────────────────────────────────
   The prototype centres a single card in the viewport. Padding matches its
   <main data-scan-main> clamps.
   ------------------------------------------------------------------------- */
.ps-shell {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  width: 100%;
  padding: clamp(16px, calc(3 * var(--vwu)), 40px) clamp(16px, calc(3 * var(--vwu)), 24px) clamp(32px, calc(5 * var(--vwu)), 56px);
}

.ps-card {
  width: 100%;
  max-width: 560px;
  background: var(--color-surface-container);
  border-radius: var(--radius-lg);
  box-shadow: inset 0 0 0 1px var(--color-outline), var(--shadow-elevation-light);
  overflow: hidden; /* fallback: overflow:clip needs Safari 16+ */
  overflow: clip;
  animation: psScreenIn 420ms cubic-bezier(0.2, 0, 0.2, 1) both;
}

/* Report / education / plans get the wide canvas. */
@media (min-width: 1024px) {
  .ps-card[data-wide="true"] { max-width: 940px; }
  .ps-card[data-wide="true"] .ps-card-body { padding: 36px 40px; }
}

.ps-card-hero {
  display: block;
  width: 100%;
  /* REQUIRED, not cosmetic. The markup carries width="1120" height="746" so the browser can
     reserve space and avoid layout shift, but those attributes also act as presentational
     hints that set an explicit height. `aspect-ratio` only supplies a MISSING dimension, so
     with both width and height resolved it is ignored entirely and the hero renders 746px
     tall instead of 315px — 2.4x too tall.
     Measured in a bare page: computed height 746px with aspect-ratio reporting "16 / 9".
     It looks correct on the live site only because the theme happens to ship the usual
     `img { height: auto }` reset, so this was masked by a dependency on the theme rather
     than actually working. The prototype needs no equivalent rule because its <img> carries
     no width/height attributes at all. */
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: 60% 28%;
  margin-bottom: -20px;
}
/* Theme hardening for the hero, reported as "corner rounding on the image on mobile" and
   visible as the image sitting inset from the card edges.
   Themes routinely style bare `img`, and Elementor and block themes ship rules like
   `img { border-radius: 12px }`, `.entry-content img { margin: 0 12px; max-width: 90% }` and
   `.elementor img { box-shadow: ... }`. Reproduced with exactly those: 16px radius, 12px inset
   each side and a leaked box-shadow at every viewport, which is the reported symptom.
   The hero must never carry a radius of its own at ANY width. On desktop the rounded top corners
   come from `.ps-card`'s `overflow: clip`, and on mobile the prototype has square corners —
   `[data-scan-card] { border-radius: 0 !important }` in its own mobile block.
   Scoped `.ps-wrap .ps-card-hero` (0,2,0) to beat theme selectors like `.entry-content img`
   (0,1,1), with `!important` because themes use it freely and this plugin has already lost this
   fight repeatedly on this site. `aspect-ratio` is deliberately NOT listed here so the mobile
   override to 3/2 still applies. */
.ps-wrap .ps-card-hero {
  width: 100% !important;
  max-width: none !important;
  height: auto !important;
  margin: 0 0 -20px !important;
  padding: 0 !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}

.ps-card-body {
  position: relative;
  z-index: 1;
  background: var(--color-surface-container);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
  padding: clamp(20px, calc(4 * var(--vwu)), 32px);
}

/* MySudo lockup at the top of the card. */
.ps-brand {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  color: var(--color-on-surface);
}

/* Pin the mark's box so a missing width/height attribute can't fall back to the
   SVG default of 300x150 and blow out the header. */
.ps-brand svg {
  display: block;
  flex: none;
  width: 38px;
  height: 32px;
}

.ps-brand-name {
  font: var(--font-weight-bold) var(--text-body-large-size)/var(--text-body-large-line) var(--font-sans);
}

/* Each screen is a stacked flex column with the same rhythm. */
.ps-screen {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
  animation: psScreenIn 360ms cubic-bezier(0.2, 0, 0.2, 1) both;
}

/* Blanket guard: the `hidden` attribute must actually hide.
   
   The UA stylesheet's `[hidden] { display: none }` is beaten by ANY author `display`
   declaration, so every element in here that both ships hidden and carries a display rule
   needs an explicit guard. That caught us on the plan cards' upgrade CTAs: the JS sets
   `ctaEl.hidden = true` for organic visitors, but `.ps-wrap .ps-btn` declares
   `display: inline-flex`, so "Upgrade to SudoPro" stayed visible to visitors with nothing
   to upgrade. `.ps-stepper` had the same latent problem.
   
   One rule instead of one per class, so the next element to ship hidden is covered
   automatically. The specific guards below are kept as documentation of what relies on
   this. */
.ps-wrap [hidden] { display: none !important; }

.ps-screen[hidden] { display: none; }

.ps-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
}

/* ── Type helpers ────────────────────────────────────────────────────────── */
.ps-wrap .ps-h1 {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: var(--text-headline-large-weight);
  font-size: clamp(26px, calc(4.4 * var(--vwu)), 32px);
  line-height: 1.28;
  letter-spacing: 0;
  color: var(--color-on-surface);
  text-wrap: balance;
}

/* Secondary screens (verify, more info, select, scanning) run a touch smaller. */
.ps-wrap .ps-h1--sm { font-size: clamp(24px, calc(4.2 * var(--vwu)), 30px); }

.ps-wrap .ps-h2 {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: var(--space-s);
  font-family: var(--font-sans);
  font-weight: var(--text-headline-small-weight);
  font-size: var(--text-headline-small-size);
  line-height: var(--text-headline-small-line);
  color: var(--color-on-surface);
}

.ps-sub {
  margin: 0;
  font: var(--font-weight-medium) var(--text-body-medium-size)/var(--text-body-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
  text-wrap: pretty;
}

.ps-kicker {
  font: var(--font-weight-bold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-on-surface-variant);
}

/* Lock/info note with a leading icon. */
.ps-note {
  display: flex;
  align-items: flex-start;
  gap: var(--space-s);
  color: var(--color-on-surface-variant);
}

.ps-note > .ps-note-icon {
  display: inline-flex;
  flex: none;
  margin-top: 2px;
}

.ps-note > span:last-child {
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  text-wrap: pretty;
}
/* The note now carries Terms of Service and Privacy Policy links. Without these rules they
   inherit the host theme's anchor styling — typically a blue underline, which reads as a
   foreign element inside the card. (These were kept in step with the plans screen's legal
   footer, which no longer exists — this is now the only place the links appear.) `inherit` on
   font keeps them at the note's smaller size rather than
   whatever the theme sets. :focus-visible is explicit because these are the only links inside
   the flow, so they are the one place a keyboard user can tab to and get no ring from the
   button rules. */
.ps-wrap .ps-note a {
  color: var(--color-secondary);
  text-decoration: none;
  font: inherit;
}
@media (hover: hover) {
  .ps-wrap .ps-note a:hover { text-decoration: underline; }
}
.ps-wrap .ps-note a:focus-visible {
  outline: 2px solid var(--color-secondary);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ── Segmented control (scan type + billing period) ─────────────────────── */
/* .ps-wrap prefix for the same reason every .ps-seg-btn rule carries it: host themes
   reset generic containers, and at (0,1,0) this pill lost its background, padding and
   inset outline to whatever the theme declared later in source order. It was the only
   selector in this block without the prefix. */
.ps-wrap .ps-seg {
  display: flex;
  width: 100%;
  padding: var(--space-xs);
  gap: var(--space-xs);
  border-radius: var(--radius-pill);
  background: var(--color-surface-container-high);
  box-shadow: inset 0 0 0 1px var(--color-outline);
}

.ps-wrap .ps-seg-btn {
  flex: 1 1 0;
  min-width: 0;
  min-height: 40px;
  padding: 0 var(--space-sm);
  border: none;
  appearance: none;
  cursor: pointer;
  border-radius: var(--radius-pill);
  /* !important for the same theme-bleed reason as .ps-accordion-trigger — see the long note
     there. This is the OTHER bare <button> with no background of its own, and it has already
     been reported once: 6.1.12 recorded a "crimson-filled Email only scan segment". That was
     closed as a stale pre-cache-fix render, and on the evidence of the Android accordion
     report that conclusion looks wrong. Two independent crimson sightings on precisely the
     two buttons that lacked this guard is not a coincidence.
     The intended fills below (hover wash, selected segment, billing variant) all carry
     !important so they still beat this resting value. */
  background: none !important;
  color: var(--color-on-surface-variant);
  font: var(--font-weight-semibold) var(--text-button-medium-size, 14px)/24px var(--font-sans);
  transition: background 140ms linear, color 140ms linear;
}

/* Hover, focus and disabled states ported from the Aurora bundle's segmented
   control, which the earlier port missed. Hover shows as a background wash on
   inactive segments only — the prototype pins their text colour with
   `color: var(--color-on-surface-variant) !important`, so it never brightens on
   hover, and the pressed segment is excluded (`:not([aria-pressed="true"])`). The
   plugin marks the selection with both the attribute and `.is-active`, so both are
   excluded here. Replaces a colour-shift-on-hover rule that applied to every
   segment, including the active one, which the prototype does not do. Both tokens
   already existed in the token layer. */
/* :not(:disabled) belongs on THIS rule, not on a later override. :not() contributes the
   specificity of its argument, so this selector is (0,5,0) and the `:disabled:hover`
   reset below is only (0,4,0) — the reset could never win, and a hovered disabled
   segment got the wash anyway. */
@media (hover: hover) {
  .ps-wrap .ps-seg-btn:hover:not(:disabled):not([aria-pressed="true"]):not(.is-active) {
    background: var(--color-transparency-5) !important;
  }
}

.ps-wrap .ps-seg-btn:focus-visible {
  outline: 2px solid var(--color-secondary);
  outline-offset: -2px;
}

.ps-wrap .ps-seg-btn:disabled {
  color: var(--color-disabled);
  cursor: default;
}

.ps-wrap .ps-seg-btn[aria-pressed="true"],
.ps-wrap .ps-seg-btn.is-active {
  background: var(--color-secondary) !important;
  color: var(--color-on-secondary);
}

/* "Yearly • Save 16%" — the saving rides the accent while the label stays on the
   segment's own colour, matching the prototype. Kept lavender in both the selected and
   unselected states: it is a promotional accent rather than a state indicator, and
   dimming it when Monthly is active would hide the reason to switch back.
   
   The segmented control itself is deliberately NOT changed to the prototype's treatment
   — the current fill was preferred. */
.ps-wrap .ps-seg-save {
  color: var(--color-secondary);
  /* The label is now long enough to wrap on a narrow phone, which would make the two
     segments different heights. Keep it on one line and let the flex basis shrink. */
  white-space: nowrap;
}

/* Billing toggle inverts: selected segment reads as a raised surface. */
.ps-wrap .ps-seg--billing .ps-seg-btn[aria-pressed="true"],
.ps-wrap .ps-seg--billing .ps-seg-btn.is-active {
  background: var(--color-surface) !important;
  color: var(--color-on-surface);
  box-shadow: var(--shadow-elevation-light);
}

/* ── Field group ─────────────────────────────────────────────────────────── */
/* Aurora's textFieldGroup (Figma 4237:11817). Its own comment in the design-system
   bundle is explicit: "the border, the 8px radius and the row dividers belong to the
   group; the rows inside it are borderless. Use with bordered={false}." That is exactly
   what the prototype does — every TextField it renders passes `bordered: false` — and it
   is where the rounded outlined box comes from. The earlier port had neither the border
   nor the radius, which is what was reported as "the boxes are rounded on the prototype". */
.ps-field-group {
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  border: 1px solid var(--color-outline);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-surface-container-high);
}

/* The group's border carries the interaction state, not the individual row. */
.ps-field-group.is-engaged,
.ps-field-group.is-active { border-width: 1.5px; border-color: var(--color-secondary); }
.ps-field-group.is-error  { border-width: 1.5px; border-color: var(--color-error-container); }

/* Ported from Aurora's TextField (.aurora-field__box in the design-system bundle):
   a filled 64px row, column layout, vertically centred, 8px/16px padding, on
   surface-container-high. The earlier version had no background and no horizontal
   padding, so it read as a bare underlined input rather than a contained field. */
.ps-field {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0; /* Aurora's Details column is gap 0 on the Default type */
  min-height: 64px;
  padding: var(--space-s) var(--space-m);
  background: var(--color-surface-container-high);
  cursor: text; /* the whole box is a text target, per Aurora */
  animation: psFieldIn 260ms cubic-bezier(0.2, 0, 0.2, 1) both;
}

/* Themes border the field WRAPPER as well as the input, putting a line under every
   field on top of the group divider. Hardening the input alone wasn't enough. These
   two rules are deliberately high-specificity (0,3,0 and 0,4,0) so they beat a
   theme's `.ps-field`/`input` rules whichever stylesheet loads last: reset every
   edge, then draw the divider BETWEEN fields only — never below the last one. */
.ps-wrap .ps-field-group > .ps-field {
  border: 0 !important;
}

.ps-wrap .ps-field-group > .ps-field + .ps-field {
  border-top: 1px solid var(--color-outline) !important;
}

/* ── Select (state dropdown) ───────────────────────────────────────────────
   Port of Aurora's Select, the "Drop down (New Proposal)". A trigger row that expands
   IN FLOW into option rows inside one bordered container — not a floating pop-over, and
   with no elevation, exactly as the bundle describes it. That is why there is no
   positioning logic here: the list is a sibling, and the container simply grows.
   The 2px border is drawn with ::after so it sits over the trigger's bottom hairline
   rather than being covered by it, which is how the bundle does it too.
   ------------------------------------------------------------------------- */
.ps-select {
  position: relative;
  box-sizing: border-box;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  font-family: var(--font-sans);
  background: var(--color-surface-container-high);
  border-radius: var(--radius-sm);
  overflow: hidden;
  /* The prototype's own page CSS caps the open list and sticks the trigger, rather than
     letting 50 states run off the screen. Both belong to this instance, not to Aurora. */
  max-height: 320px;
  overflow-y: auto;
}
.ps-select::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border: 2px solid var(--color-outline);
  border-radius: var(--radius-sm);
}
.ps-wrap .ps-select-trigger {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  box-sizing: border-box;
  width: 100%;
  /* min-height rather than height: Figma pins both, and min-height lets a wrapped
     value grow instead of clipping. */
  min-height: 64px;
  padding: var(--space-s) var(--space-m);
  margin: 0;
  border: 0;
  text-align: left;
  cursor: pointer;
  background: var(--color-surface-container-high);
  color: var(--color-on-surface);
  font-family: inherit;
  box-shadow: inset 0 -1px 0 var(--color-outline);
  /* Sticky so the label stays put while the list scrolls. Needs its own background
     or the rows show through. */
  position: sticky;
  top: 0;
  z-index: 1;
  flex: none; /* same reason as .ps-select-menu; min-height alone already floors it */
  text-transform: none;
  letter-spacing: normal;
}
.ps-select-details {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex: 1 1 0;
  min-width: 0;
}
.ps-select-sublabel {
  width: 100%;
  font-size: var(--text-label-medium-size);
  line-height: var(--text-label-medium-line);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0;
  color: var(--color-on-surface-variant);
}
.ps-select-value {
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-body-small-size);
  line-height: var(--text-body-small-line);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0;
  color: var(--color-on-surface);
}
.ps-select-value--placeholder { color: var(--color-on-surface-variant); }
.ps-select-chev {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 4px;
  color: var(--color-on-surface-variant);
}
/* NOT .ps-select-list — that class already belongs to the person-selection list on the
   "Select your record" screen, which sets `display: flex; max-height: 340px; overflow-y: auto`.
   Reusing the name silently inherited all three: the state list capped itself at 340px and
   scrolled internally, so the outer container's scrollHeight stayed at 404px against 2300px of
   rows and arrowing to the end left the cursor off-screen. Measured, not guessed.
   flex: none is load-bearing. `.ps-select` is a flex column with max-height: 320px, and a
   flex item shrinks by default — so this wrapper collapsed to the leftover 256px, the rows
   overflowed it invisibly, and the container's scrollHeight stayed equal to its clientHeight,
   making the list unscrollable. Measured: scrollHeight 320 against 2364px of rows.
   The prototype does not hit this because it has no wrapper: each row is its own flex item
   with min-height: 46px, which floors the shrink. Our role="listbox" element is the single
   shrinkable item, so it has to opt out. */
.ps-select-menu {
  flex: none;
}
.ps-select-menu[hidden] { display: none; }
/* 11 + 24 + 11 = 46, so the hairline is an inside stroke. Aurora hangs it off every row
   including the last, where the container's 2px border hides it; suppressing the last
   renders identically and avoids a double line. */
.ps-wrap .ps-select-opt {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  box-sizing: border-box;
  width: 100%;
  min-height: 46px;
  padding: 11px var(--space-m);
  margin: 0;
  border: 0;
  text-align: left;
  cursor: pointer;
  background: var(--color-surface-container-high);
  font-family: inherit;
  box-shadow: inset 0 -1px 0 var(--color-outline);
  text-transform: none;
  letter-spacing: normal;
}
.ps-wrap .ps-select-opt:last-child { box-shadow: none; }
.ps-select-opt-label {
  flex: 1 1 0;
  min-width: 0;
  word-break: break-word;
  font-size: var(--text-label-large-size);
  line-height: var(--text-label-large-line);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0;
  color: var(--color-on-surface);
}
.ps-select-opt-check {
  display: flex;
  flex: none;
  color: var(--color-secondary);
}
/* Aurora draws no hover or pressed fill on an option row, so the only affordance for
   keyboard users is this focus ring. Without it, arrowing through 50 states gives no
   indication of position — the design's omission is fine for pointer use and not for
   keyboard use. */
.ps-wrap .ps-select-opt:focus-visible,
.ps-wrap .ps-select-opt.is-cursor {
  outline: 2px solid var(--color-secondary);
  outline-offset: -2px;
}
.ps-select.is-open::after { border-color: var(--color-secondary); }

/* ── Field internals: the floating-label state machine ─────────────────────
   Ported from Aurora's TextField. resolveState() in the bundle:
       disabled                        -> disabled
       an explicitly passed state      -> that state
       focused                         -> hasValue ? active : engaged
       otherwise                       -> hasValue ? done   : none
   and COLLAPSED = { none, add }, i.e. only the empty-and-unfocused field collapses.

   Collapsed means the label has not floated: it IS the prompt line, rendered at Body
   Small on full on-surface brightness, and the value row is height 0. Once the field is
   focused or holds a value, the label shrinks to Label Medium and the value row opens.
   That is the behaviour reported missing as "when you enter email or phone the text does
   not change like in the screenshots".
   ------------------------------------------------------------------------- */
.ps-field-details {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* Vertically centred. `flex: 1 0 0` stretches this column to the full 64px row, so
     without an explicit justify-content the contents sit hard against the TOP — which is
     what was reported as the label needing to be centred in an unfilled field. The
     `justify-content: center` on `.ps-field` cannot do it, because this stretched child is
     what actually holds the label. Centring here covers both states: the lone label when
     collapsed, and the label+value pair once the field is engaged. */
  justify-content: center;
  flex: 1 0 0;
  min-width: 0;
  gap: 0; /* Aurora: "Details column is gap 0 on Default / Done / ... 0 wins." */
}

.ps-field-valuerow {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  width: 100%;
  min-width: 0;
}

/* Empty and unfocused: label becomes the prompt, value row folds away. */
.ps-field.is-collapsed .ps-field-valuerow { height: 0; overflow: hidden; }

.ps-wrap .ps-field.is-collapsed label {
  font-size: var(--text-body-small-size);
  line-height: var(--text-body-small-line);
  color: var(--color-on-surface);
}

/* Focused: label rides secondary. Error: label rides error-container. */
.ps-wrap .ps-field.is-engaged label,
.ps-wrap .ps-field.is-active label { color: var(--color-secondary); }
.ps-wrap .ps-field.is-error label { color: var(--color-error-container); }

/* Aurora grows the row from 64px when a message is present:
   "Figma 100px = 12 + 20 + 24 + 8 + 24 + 12". */
.ps-field.has-message { padding: var(--space-sm) var(--space-m); }

/* The error message is a filled token in Figma, not caption text —
   error-container fill, 8px radius, 2/6 padding, Label Medium on-error-container. */
.ps-field-message {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  margin: 0;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  background: var(--color-error-container);
  color: var(--color-on-error-container);
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
}

.ps-field-message[hidden] { display: none; }

.ps-wrap .ps-field label {
  display: block;
  margin: 0;
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
  /* Aurora clips a long label rather than wrapping it and growing the 64px row. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The field group draws the only dividers, so the inputs themselves must be bare.
   !important because host themes style bare `input` selectors that outspecify
   this and were adding a second border-bottom under each field. */
.ps-wrap .ps-field input[type="tel"],
.ps-wrap .ps-field input[type="email"],
.ps-wrap .ps-field input[type="text"] {
  width: 100%;
  padding: 0;
  border: 0 !important;
  border-radius: 0 !important;
  outline: none;
  background: transparent !important;
  color: var(--color-on-surface);
  /* Aurora's .aurora-field__input is Body Small — 14/24, SemiBold 600.
     
     font-size carries !important for the same reason height/background/border in the
     OTP rule do: the host theme sets a size on `input` and wins. Measured on staging,
     the computed value was 10.0625px — exactly 1rem against the theme's ~10px root —
     on a rule that declares 16px. Below 16px iOS Safari zooms the page on focus and
     never zooms back out, which is the "stays zoomed on the next screen" report. The
     mobile override below raises this to the 16px zoom threshold. */
  font-family: var(--font-sans) !important;
  font-size: var(--text-body-small-size) !important;
  line-height: var(--text-body-small-line) !important;
  font-weight: var(--font-weight-semibold) !important;
  caret-color: var(--color-secondary);
  /* Themes draw input underlines with box-shadow as well as border, which was
     leaving a stray grey line under each field on top of the group divider. */
  box-shadow: none !important;
  transition: box-shadow 140ms linear;
}

/* Last-resort reset for the theme-drawn line under each input, reported as "the line under
   phone and email removed". The prototype has no line beneath the value — only the group
   divider BETWEEN the two fields, which is correct and stays.
   The class-based reset above already sets `border: 0 !important` and `box-shadow: none
   !important` and was still being beaten, so a theme rule is winning on specificity. These
   inputs all carry ids, and every one of them sits inside `.ps-field-group > .ps-field`, so
   naming that real ancestor chain lands at (1,3,0).
   That number is not arbitrary. A first attempt used `.ps-wrap :is(#id, ...)` at (1,1,0) on the
   assumption it would beat anything realistic, and a test against
   `#content .entry-content input[type="tel"] { border-bottom: 1px !important }` at (1,2,1)
   showed the theme still winning — equal ids, then 2 classes beating 1. (1,3,0) wins that
   comparison on class count while still using only selectors that exist in the markup.
   Applies in every state, focus included, because an id already outranks a pseudo-class rule.
   `background-image` is in the list because themes also fake underlines with a gradient, and
   `text-decoration` because a couple draw them that way too. */
.ps-wrap .ps-field-group .ps-field :is(#ps-phone, #ps-email, #ps-first-name, #ps-last-name, #ps-state) {
  border: 0 !important;
  border-bottom: 0 !important;
  box-shadow: none !important;
  outline: none !important;
  background-image: none !important;
  text-decoration: none !important;
}
/* Same line drawn as a pseudo-element on the row instead. We use neither here, so
   neutralising both is safe. */
.ps-wrap .ps-field-valuerow::before,
.ps-wrap .ps-field-valuerow::after {
  content: none !important;
  border: 0 !important;
  background: none !important;
}

/* opacity: 1 because Firefox and some Android browsers dim placeholders by default,
   which would take this below the contrast the token was chosen for. Aurora sets it
   explicitly for the same reason. */
.ps-wrap .ps-field input::placeholder {
  color: var(--color-on-surface-variant);
  opacity: 1;
}

/* Aurora focus treatment: a lilac underline. Previously this removed the outline
   without replacing it, which left keyboard users with whatever the host theme
   happened to leak (a blue browser default) and failed WCAG 2.4.7 Focus Visible. */
/* MUST match the (1,3,0) of the theme-hardening reset above, or it loses to it.
   
   Both rules are !important, so specificity decides — and the reset
   `.ps-wrap .ps-field-group .ps-field :is(#ps-phone, …)` is (1,3,0) while this used to be
   `.ps-wrap .ps-field input[type="tel"]:focus` at (0,4,0). One id beats four classes, so
   `box-shadow: none` won and the focus underline never rendered at all. The comment on that
   reset says it "applies in every state, focus included" — true, and it took the focus
   indicator with it. Net effect: tabbing to Phone, Email, First name or Last name gave no
   indicator except a 12px label turning lilac, which is a colour-only cue and a WCAG 2.4.7
   failure on the form's primary fields.
   
   Naming the same ancestor chain plus the ids lands at (1,3,0) with the pseudo-class on top,
   so this now wins. Keep the two selectors in step: if the reset's shape changes, this has to
   change with it. */
.ps-wrap .ps-field-group .ps-field :is(#ps-phone, #ps-email, #ps-first-name, #ps-last-name, #ps-state):focus,
.ps-wrap .ps-field input[type="tel"]:focus,
.ps-wrap .ps-field input[type="email"]:focus,
.ps-wrap .ps-field input[type="text"]:focus {
  outline: none;
  box-shadow: 0 2px 0 0 var(--color-secondary) !important;
}

/* Aurora's --engaged/--active state tints the label to secondary. Its other focus
   signal is a 1.5px secondary border, which only exists on the --bordered variant —
   and the prototype uses the grouped variant here, which has no border to tint. So the
   label is the design's actual focus affordance in this context. The underline above is
   kept as well: it is a deliberate addition for WCAG 2.4.7, since a label colour change
   alone is a weak indicator and fails on colour-vision grounds. */
.ps-wrap .ps-field:focus-within label {
  color: var(--color-secondary);
}







/* ── Buttons ─────────────────────────────────────────────────────────────── */
.ps-wrap .ps-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-s);
  width: 100%;
  min-height: 48px;
  padding: 0 var(--space-m);
  border: none;
  appearance: none;
  cursor: pointer;
  border-radius: var(--radius-pill);
  /* Aurora Button, Type=Primary with Colour=False:
         .aurora-btn--primary { background: var(--color-inverse-surface);
                                color: var(--color-on-inverse-surface); }
     which is #FFFFFF on #000000 in the dark theme. The accent fill is a separate
     variant (Colour=True) and the prototype only uses it on the plans upgrade CTA —
     see .ps-btn--colour below. Every other CTA in the flow (Start scan, Continue,
     Reclaim my privacy, See MySudo plans, Back to protecting your privacy) is passed
     `variant: "primary"` with no colour flag, so all of them are white. This port had
     them all on the accent, which is what was reported as "the buttons are different". */
  background: var(--color-inverse-surface);
  color: var(--color-on-inverse-surface);
  font-family: var(--font-sans);
  font-weight: var(--font-weight-bold);
  font-size: 16px;
  line-height: 24px;
  text-align: center;
  text-decoration: none;
  transition: background 140ms linear, filter 140ms linear;
}

/* Type=Primary, Colour=True — the accent fill. Only the plans upgrade CTA uses it. */
.ps-wrap .ps-btn--colour {
  background: var(--color-secondary);
  color: var(--color-on-secondary);
}

/* .aurora-btn--primary:disabled { background: var(--color-disabled);
                                   color: var(--color-on-disabled); }
   The prototype gates Start scan on `!(eValid && (!full || pValid))` and Continue on
   `!this.moreInfoComplete()`, so both sit disabled until their fields validate. Ours
   were always enabled. */
.ps-wrap .ps-btn:disabled,
.ps-wrap .ps-btn[aria-disabled="true"] {
  background: var(--color-disabled);
  color: var(--color-on-disabled);
  cursor: default;
  filter: none;
}

/* :hover and :focus-visible were one rule. They must be separated, because :hover has to be
   gated on the device actually having a pointer and :focus-visible must not be.

   REPORTED AS: "the CTA button starts out as purple, but randomly changes to white when I
   scroll down." Cause: on mobile .ps-cta-bar is position:fixed at bottom:0, so it sits exactly
   where a thumb rests. A touch applies :hover, which paints --color-secondary-container
   (#D1B9FF, purple); releasing or dragging away drops :hover and the button returns to its
   resting --color-inverse-surface (#FFFFFF, white). Nothing random about it — it tracked
   where the thumb was. Verified on an emulated iPhone 13, which reports BOTH `hover: none`
   and `pointer: coarse` and still applied the rule, because a media feature describes the
   device, it does not stop :hover from matching.

   The resting colour was always white; the purple was the transient state. */
@media (hover: hover) {
  .ps-wrap .ps-btn:hover {
    background: var(--color-secondary-container);
    color: var(--color-on-secondary);
    text-decoration: none;
  }
}

/* Keyboard focus is not a pointer capability, so this stays unconditional. Removing it would
   regress WCAG 2.4.7 Focus Visible, which the comment above the focus treatment calls out. */
.ps-wrap .ps-btn:focus-visible {
  background: var(--color-secondary-container);
  color: var(--color-on-secondary);
  text-decoration: none;
}

/* Touch devices lose hover feedback entirely with the gate above, which would leave a tap with
   no acknowledgement at all. :active covers the press on every input type and matches the
   intent that a button is white until it is actually being chosen. */
.ps-wrap .ps-btn:active:not(:disabled):not([aria-disabled="true"]) {
  background: var(--color-secondary-container);
  color: var(--color-on-secondary);
}

.ps-wrap .ps-btn:visited { color: var(--color-on-secondary); }

.ps-wrap .ps-btn:disabled,
.ps-wrap .ps-btn[aria-disabled="true"] {
  background: var(--color-disabled);
  color: var(--color-on-disabled);
  cursor: not-allowed;
}

.ps-wrap .ps-btn--secondary {
  background: transparent;
  color: var(--color-on-surface);
  box-shadow: inset 0 0 0 1px var(--color-outline);
}

@media (hover: hover) {
  .ps-wrap .ps-btn--secondary:hover {
    background: var(--color-surface-container-higher);
    color: var(--color-on-surface);
  }
}

.ps-wrap .ps-btn--small { min-height: 40px; font-size: 14px; }

/* Underlined text button (toggles, "check my details again"). */
.ps-wrap .ps-link-btn {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 0 0;
  border: none;
  appearance: none;
  background: none;
  cursor: pointer;
  font: var(--text-label-large-weight) var(--text-label-large-size)/var(--text-label-large-line) var(--font-sans);
  color: var(--color-on-surface);
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 3px;
}

@media (hover: hover) {
  .ps-wrap .ps-link-btn:hover { color: var(--color-on-surface-variant); }
}

/* Controls that previously relied on the browser's own focus ring. A UA outline on a dark
   surface is often a thin dark-blue line with very little contrast, so these are the design's
   ring instead: the state-select trigger (the ONLY keyboard entry point to 50 options), the
   accordion triggers, the resend links, and the two text-style buttons. */
.ps-wrap .ps-select-trigger:focus-visible,
.ps-wrap .ps-accordion-trigger:focus-visible,
.ps-wrap .ps-verify-footer a:focus-visible,
.ps-wrap .ps-verify-footer button:focus-visible,
.ps-wrap .ps-link-btn:focus-visible,
.ps-wrap .ps-select-none:focus-visible {
  outline: 2px solid var(--color-secondary);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* ── Stepper ─────────────────────────────────────────────────────────────── */
.ps-stepper {
  display: flex;
  gap: var(--space-xs);
  width: 100%;
}

.ps-stepper-seg {
  flex: 1 1 0;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-container-highest);
}

.ps-stepper-seg.is-done { background: var(--color-secondary); }

.ps-step-label {
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
}

/* ── OTP code cells ──────────────────────────────────────────────────────── */
.ps-code-wrap {
  display: flex;
  gap: clamp(6px, calc(1.6 * var(--vwu)), 10px);
}

/* The code cells are the one place the design relies on a visible input border,
   and host themes (Elementor's form styles in particular) reset `input` borders
   with enough weight to erase it. The Aurora prototype marks its own OTP rules
   !important for the same reason, so this follows suit. */
.ps-wrap .ps-code-digit {
  flex: 1 1 0;
  min-width: 0;
  width: auto;
  /* Themes that set `height: auto` on inputs collapsed these to 28px. */
  height: 56px !important;
  padding: 0;
  text-align: center;
  font-family: var(--font-sans) !important;
  font-weight: var(--font-weight-bold) !important;
  /* !important for the same reason as every other declaration in this rule. Measured
     at 10.0625px on staging — the theme's 1rem — against this 22px. Under 16px iOS
     Safari zooms on focus and stays zoomed, which is what was reported on the code
     screen. */
  font-size: 22px !important;
  color: var(--color-on-surface);
  background: var(--color-surface-container-high) !important;
  border: 1px solid var(--color-outline) !important;
  border-radius: var(--radius-sm) !important;
  outline: none;
  box-shadow: none !important;
  caret-color: var(--color-secondary);
  transition: border-color 140ms linear, background 140ms linear;
}

.ps-wrap .ps-code-digit:focus {
  border-width: 1.5px !important;
  border-color: var(--color-secondary) !important;
  background: var(--color-surface-container-higher) !important;
}

/* The boxes are disabled while a code is being sent — the email send goes over SMTP and can take
   tens of seconds, and typing into boxes whose code does not exist yet auto-submits and spends
   one of the server's three attempts. Without a visible difference the disabled state would be
   invisible and the field would just feel broken. Deliberately NOT using --color-disabled: at
   2.07:1 on the card that token is the contrast problem 6.12.0 fixed elsewhere, and these still
   have to read as fields. Dimmed with opacity instead, which keeps the border legible. */
.ps-wrap .ps-code-digit:disabled {
  opacity: 0.55;
  cursor: default;
}

.ps-code-wrap[data-otp-error="1"] .ps-code-digit {
  border-width: 1.5px !important;
  border-color: var(--color-error-container) !important;
}

/* Error chip shared by verify screens. */
.ps-verify-error {
  display: none;
  align-items: center;
  gap: var(--space-s);
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: var(--color-error-container);
  color: var(--color-on-error-container);
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  animation: psFieldIn 220ms ease both;
}

.ps-verify-error.is-visible { display: flex; }

.ps-verify-target {
  font-weight: var(--font-weight-bold);
  color: var(--color-on-surface);
}

.ps-spam-note {
  margin: 0;
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
}

.ps-verify-footer {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-s);
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
}

/* `button` as well as `a`: the resend control is an anchor with role="button" (it predates
   this rule), while the new "Change it" controls are real buttons — they act on the page
   rather than navigating, so a button is the correct element and gets Enter/Space for free.
   Both need the same text-link treatment. */
.ps-wrap .ps-verify-footer a,
.ps-wrap .ps-verify-footer button {
  border: none;
  padding: 0;
  appearance: none;
  background: none;
  font: inherit;
  color: var(--color-on-surface);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 3px;
}

@media (hover: hover) {
  .ps-wrap .ps-verify-footer a:hover,
  .ps-wrap .ps-verify-footer button:hover { color: var(--color-on-surface-variant); }
}

.ps-wrap .ps-verify-footer a.is-disabled {
  color: var(--color-disabled);
  cursor: default;
  text-decoration: none;
}

@media (hover: hover) {
  .ps-wrap .ps-verify-footer a.is-disabled:hover { color: var(--color-disabled); }
}

.ps-resend-status {
  opacity: 0;
  transition: opacity 160ms linear;
  color: var(--color-on-surface-variant);
}

.ps-resend-status.is-visible { opacity: 1; }

/* ── Scanning screen ─────────────────────────────────────────────────────── */
.ps-ring-wrap {
  display: flex;
  justify-content: center;
  padding: var(--space-s) 0;
}

.ps-ring {
  position: relative;
  width: 180px;
  height: 180px;
}

.ps-ring-pulse {
  position: absolute;
  inset: 10px;
  border-radius: 50%;
  background: var(--color-secondary);
  opacity: 0.10;
  animation: psPulse 2.4s ease-in-out infinite;
}

.ps-ring svg {
  position: relative;
  display: block;
}

.ps-ring-track {
  fill: none;
  stroke: var(--color-outline);
  stroke-width: 10;
}

.ps-ring-progress {
  fill: none;
  stroke: var(--color-secondary);
  stroke-width: 10;
  stroke-linecap: round;
  stroke-dasharray: 490;
  transition: stroke-dashoffset 240ms linear;
}

.ps-ring-center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
}

.ps-ring-pct {
  font-family: var(--font-sans);
  font-weight: var(--text-headline-large-weight);
  font-size: 34px;
  line-height: 1;
  color: var(--color-on-surface);
}

.ps-ring-label {
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
}

.ps-stages {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.ps-stage {
  display: flex;
  align-items: center;
  gap: var(--space-m);
  min-height: 44px;
  padding: 0 var(--space-m);
  border-radius: var(--radius-sm);
}

.ps-stage-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 20px;
  height: 20px;
}

.ps-stage-dot {
  display: block;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1.5px var(--color-outline-variant);
}

.ps-stage-label {
  flex: 1 1 auto;
  font: var(--font-weight-semibold) var(--text-body-small-size)/var(--text-body-small-line) var(--font-sans);
  color: var(--color-on-surface-variant);
}

.ps-stage.is-done .ps-stage-label,
.ps-stage.is-active .ps-stage-label { color: var(--color-on-surface); }

.ps-stage.is-done .ps-stage-icon { color: var(--color-success-container); }

.ps-stage-note {
  flex: none;
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
}

/* Inline spinner used by the active stage and legacy #ps-loading. */
.ps-spinner {
  width: 18px;
  height: 18px;
  border: 2px solid var(--color-outline);
  border-top-color: var(--color-secondary);
  border-radius: 50%;
  animation: psSpin 0.8s linear infinite;
}

/* ── Report ──────────────────────────────────────────────────────────────── */
.ps-report {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

/* Level accents. Text colour uses the brighter dark-mode token per Aurora. */
.ps-report[data-level="high"]   { --level-accent: var(--color-error-container);   --level-text: var(--color-error); }
.ps-report[data-level="medium"] { --level-accent: var(--color-warning-container); --level-text: var(--color-warning); }
.ps-report[data-level="low"]    { --level-accent: var(--color-brand-1);           --level-text: var(--color-brand-1); }

.ps-level-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-m);
  padding: var(--space-xl);
  border-radius: var(--radius-xl) var(--radius-sm) var(--radius-xl) var(--radius-sm);
  /* Plain surface first: color-mix() needs Safari 16.2+, and without a fallback an
     older in-app webview would drop the whole declaration and leave the level card
     with no background at all. */
  background: var(--color-surface-container-high);
  background:
    radial-gradient(120% 100% at 82% 0%,
      color-mix(in oklab, var(--level-accent) 26%, transparent) 0%,
      color-mix(in oklab, var(--level-accent) 6%, transparent) 45%,
      transparent 72%),
    var(--color-surface-container-high);
  box-shadow: inset 0 0 0 1px var(--color-outline);
  overflow: hidden;
}

.ps-level-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.ps-level-row {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  color: var(--level-text);
}

.ps-level-label {
  font-family: var(--font-sans);
  font-weight: var(--text-headline-large-weight);
  font-size: clamp(26px, calc(4.4 * var(--vwu)), 32px);
  line-height: 1.2;
}

.ps-level-blurb {
  margin: 0;
  font: var(--font-weight-medium) var(--text-body-medium-size)/var(--text-body-medium-line) var(--font-sans);
  color: var(--color-on-surface);
  text-wrap: pretty;
}

.ps-metrics {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
}

.ps-metric {
  flex: 1 1 132px;
  min-width: 132px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-s) var(--space-m) 10px;
  border-radius: var(--radius-sm);
  background: var(--color-transparency-5);
  box-shadow: inset 0 0 0 1px var(--color-outline);
}

.ps-metric-value {
  font-family: var(--font-sans);
  font-weight: var(--text-headline-medium-weight);
  font-size: var(--text-headline-medium-size);
  line-height: 1.15;
  color: var(--level-text);
}

.ps-metric-label {
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
}

/* Report sections */
.ps-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-m);
  padding: var(--space-m);
  border-radius: var(--radius-sm);
  background: var(--color-surface-container-high);
  box-shadow: inset 0 0 0 1px var(--color-outline);
}

.ps-section-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ps-section-count {
  font-size: 26px;
  line-height: 1.15;
  color: var(--level-text);
}

.ps-section-desc {
  margin: 0;
  font: var(--font-weight-semibold) var(--text-body-small-size)/var(--text-body-small-line) var(--font-sans);
  color: var(--color-on-surface-variant);
  text-wrap: pretty;
}

.ps-rows {
  display: flex;
  flex-direction: column;
}

.ps-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-m);
  padding: 10px 0;
  box-shadow: inset 0 -1px 0 var(--color-outline);
}

.ps-row-label {
  font: var(--font-weight-semibold) var(--text-body-small-size)/var(--text-body-small-line) var(--font-sans);
  color: var(--color-on-surface-variant);
}

/* Same min-width: auto trap as .ps-breach-name above. .ps-row is structurally identical
   — baseline flex, space-between — and carries values that can be long single tokens: a
   profile URL, a domain, an email local part. It hasn't been seen to overflow only
   because the LinkedIn URL in the test data contains hyphens, which browsers will break
   at anyway. A value without them would escape the card exactly as the breach name did.
   Both sides get min-width: 0 so flexbox can distribute, rather than pinning the label
   with flex: none, which would just move the overflow to a long label. */
.ps-row-label { min-width: 0; }

.ps-row-value {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  text-align: right;
  font: var(--text-label-large-weight) var(--text-label-large-size)/var(--text-label-large-line) var(--font-sans);
  color: var(--color-on-surface);
  min-width: 0;
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
}

/* Breach rows stack company/year then the exposed-data line. */
.ps-breaches {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
}

.ps-breach-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 0;
  box-shadow: inset 0 -1px 0 var(--color-outline);
}

.ps-breach-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-m);
}

/* A flex item defaults to min-width: auto, so it cannot shrink below the width of its
   longest unbreakable token. Real HIBP names are single words with no spaces or hyphens
   to break on — "SynthientCredentialStuffingThreatData" is 37 characters — so the name
   held the row open and pushed the year outside the card. Measured: it overflows by 42px
   at a 320px viewport and by 2px at 360px, and clears only from about 380px up, leaving
   28px of slack at 390px. That is data-dependent rather than fixed, and 360px is a
   common Android width.

   min-width: 0 lets the item shrink; overflow-wrap allows the break inside the token.
   `anywhere` also reduces the item's min-content contribution, which is what actually
   frees the layout; `break-word` is listed first as a fallback for Safari below 15.4,
   which doesn't support `anywhere`.

   Note this is an extension beyond the prototype, not a correction of the port: the
   prototype's breach row is the same three properties with no wrap handling, and only
   avoids the problem because its sample names ("Northbridge Retail", "Tempo Fitness")
   are short and contain spaces. */
.ps-breach-name {
  font: var(--text-label-large-weight) var(--text-label-large-size)/var(--text-label-large-line) var(--font-sans);
  color: var(--color-on-surface);
  min-width: 0;
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
}

/* The year is always four digits and must never wrap or be compressed. */
.ps-breach-date { flex: none; }

.ps-breach-date,
.ps-breach-exposed {
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
  text-wrap: pretty;
}

/* Shown when the browser has no native share sheet to open. */
.ps-share-note {
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
  text-wrap: pretty;
}

/* Benefit / check list */
.ps-checklist {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
}

.ps-check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-s);
}

.ps-check-icon {
  display: inline-flex;
  flex: none;
  margin-top: 3px;
  color: var(--level-text, var(--color-on-surface));
}

.ps-check span:last-child {
  font: var(--font-weight-semibold) var(--text-body-small-size)/var(--text-body-small-line) var(--font-sans);
  text-wrap: pretty;
}

/* Sticky CTA bar, bleeding to the card edges. */
.ps-cta-bar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  margin: 0 calc(-1 * clamp(20px, calc(4 * var(--vwu)), 32px)) calc(-1 * clamp(20px, calc(4 * var(--vwu)), 32px));
  padding: var(--space-m) clamp(20px, calc(4 * var(--vwu)), 32px) clamp(20px, calc(4 * var(--vwu)), 32px);
  background: var(--color-surface-container);
  box-shadow: inset 0 1px 0 var(--color-outline);
}

/* Two-column report grid on desktop. */
@media (min-width: 1024px) {
  .ps-card[data-wide="true"] .ps-report {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: start;
    column-gap: var(--space-xl);
  }
  .ps-card[data-wide="true"] .ps-report > * { min-width: 0; }
  .ps-card[data-wide="true"] .ps-report > :first-child,
  .ps-card[data-wide="true"] .ps-report > :last-child { grid-column: 1 / -1; }
  .ps-card[data-wide="true"] .ps-report > .ps-cta-bar {
    margin: 0 -40px -36px;
    padding: var(--space-m) 40px 36px;
  }
}

/* ── Accordion (education lessons, "What's in a Sudo?") ─────────────────── */
.ps-accordion {
  border-radius: var(--radius-sm);
  background: var(--color-surface-container-high);
  box-shadow: inset 0 0 0 1px var(--color-outline);
  overflow: hidden;
}

.ps-wrap .ps-accordion-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-m);
  width: 100%;
  padding: var(--space-m);
  border: none;
  appearance: none;
  /* !important is theme-bleed defence, not laziness. REPORTED ON ANDROID: tapping
     "What's in a Sudo?" left the trigger filled magenta — a colour that exists nowhere in
     Aurora (the only crimson in the whole design system is #BF3B3F, the LIGHT-mode
     --color-error-container, and it is never applied to anything). The accordion works; only
     the fill was wrong.

     Cause: this is a bare <button> whose design has NO background, and the plain rule was
     (0,2,0) with no !important. A host theme rule such as `.elementor button:focus` is
     (0,2,1) and beat it. Android is where it shows because a tapped button KEEPS FOCUS, so
     the theme's focus fill sticks after the tap. That also explains why the CTA in the same
     screenshot looked right: only one element can hold focus at a time.

     Marking the resting state !important defeats any non-important theme rule regardless of
     its specificity, which covers the common case. The intended states below carry
     !important too, or this base rule would win over them. Same treatment the text inputs
     and OTP boxes already get. A theme using !important AND higher specificity would still
     win; that would need a specificity bump, not more !important. */
  background: none !important;
  cursor: pointer;
  text-align: left;
  font: var(--text-label-large-weight) var(--text-label-large-size)/var(--text-label-large-line) var(--font-sans);
  color: var(--color-on-surface);
  transition: background 140ms linear;
}

@media (hover: hover) {
  .ps-wrap .ps-accordion-trigger:hover { background: var(--color-surface-container-higher) !important; }
}

.ps-wrap .ps-accordion-trigger--lg {
  font-family: var(--font-sans);
  font-weight: var(--font-weight-bold);
  font-size: 18px;
  line-height: 26px;
}

.ps-accordion-chevron {
  display: inline-flex;
  flex: none;
  color: var(--color-on-surface-variant);
  transition: transform 160ms ease;
}

.ps-accordion.is-open .ps-accordion-chevron { transform: rotate(180deg); }

/* The prototype styles its TWO accordions differently, and this port had collapsed them into
   one class carrying the education-lesson values. Checked against
   "Exposure Flow standalone-src.dc.html":

     education lesson (line 551/554) — border-radius: var(--radius-sm, 8px)
                                       chevron: var(--color-on-surface-variant)
     "What's in a Sudo?"  (line 499/504) — border-radius: 12px
                                       chevron: var(--color-on-surface)

   So the shared values above are RIGHT for the lessons and wrong for the plans accordion.
   Scoped by [data-accordion="sudo-info"] rather than changing .ps-accordion, which would
   have "fixed" one screen by breaking the other. --radius-md is already 12px.

   Noticed while checking a report that the pill "doesn't fit the shape of the borders" — the
   shape complaint was a separate bug from the magenta fill, and this is the shape half. */
.ps-accordion[data-accordion="sudo-info"] { border-radius: var(--radius-md); }
.ps-accordion[data-accordion="sudo-info"] .ps-accordion-chevron { color: var(--color-on-surface); }

.ps-accordion-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  padding: 0 var(--space-m) var(--space-m);
}

.ps-accordion-panel[hidden] { display: none; }

.ps-accordion-body {
  margin: 0;
  font: var(--font-weight-medium) var(--text-body-medium-size)/var(--text-body-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
  text-wrap: pretty;
}

/* Bulleted list used by "What's in a Sudo?" */
.ps-bullets {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.ps-bullet {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font: var(--font-weight-medium) var(--text-body-medium-size)/var(--text-body-medium-line) var(--font-sans);
  color: var(--color-on-surface);
}

.ps-bullet-dot { color: var(--color-on-surface-variant); }

.ps-footnote {
  margin-top: 6px;
  font: var(--font-weight-semibold) var(--text-body-small-size)/var(--text-body-small-line) var(--font-sans);
  color: var(--color-on-surface-variant);
  text-wrap: pretty;
}

/* ── Education video ────────────────────────────────────────────────────── */
.ps-video {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-surface-container-highest);
}

.ps-video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: none;
}

/* ── Plans ───────────────────────────────────────────────────────────────── */
.ps-plans {
  display: flex;
  flex-direction: column;
  gap: var(--space-m);
}

.ps-plan-card {
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--color-surface-container-high);
  box-shadow: inset 0 0 0 1px var(--color-outline);
}

.ps-plan-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  padding: var(--space-s) var(--space-m);
  background: var(--color-surface-container-highest);
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface);
}

.ps-plan-badge {
  display: inline-flex;
  flex: none;
  align-items: center;
  padding: 2px 10px 3px;
  border-radius: var(--radius-pill);
  background: var(--color-inverse-surface);
  color: var(--color-on-inverse-surface);
  font: var(--font-weight-bold) 11px/16px var(--font-sans);
}

.ps-plan-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: var(--space-m);
}

.ps-plan-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-m);
}

.ps-plan-id {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  min-width: 0;
}

.ps-plan-name {
  font-family: var(--font-sans);
  font-weight: var(--font-weight-bold);
  font-size: 28px;
  line-height: 1.15;
  color: var(--color-on-surface);
}

.ps-plan-tagline {
  font: var(--font-weight-medium) var(--text-body-medium-size)/var(--text-body-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
  text-wrap: pretty;
}

.ps-plan-pricing {
  display: flex;
  flex: none;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  text-align: right;
}

.ps-plan-price {
  font-family: var(--font-sans);
  font-weight: var(--font-weight-bold);
  font-size: 22px;
  line-height: 1.25;
  color: var(--color-on-surface);
}

.ps-plan-term,


.ps-specs {
  display: flex;
  gap: 10px;
}

.ps-spec {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: var(--space-sm) 6px;
  border-radius: 10px;
  background: var(--color-surface-container);
  box-shadow: inset 0 0 0 1px var(--color-outline);
}

.ps-spec-icon,
.ps-value-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--color-surface-container-highest);
  color: var(--color-on-surface-variant);
}

.ps-spec-value,
.ps-spec-unit {
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  text-align: center;
}

.ps-spec-value { color: var(--color-on-surface); }
.ps-spec-unit { color: var(--color-on-surface-variant); }

/* Organic-web download pair. Reuses the Aurora primary button so it reads as part of the
   same system as the rest of the flow; the plan cards' own accent CTAs are hidden in this
   mode, so these are the only actions on the screen and carry no competing emphasis. */
.ps-plan-downloads {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
}

.ps-plan-downloads[hidden] { display: none; }

.ps-plan-downloads-title {
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
}

/* Current-plan / SudoMax highlight cards */
.ps-plan-current,
.ps-plan-owned {
  border-radius: var(--radius-card);
  background: var(--color-surface-container-high);
  box-shadow: inset 0 0 0 1.5px var(--color-secondary);
}

.ps-plan-current {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 14px var(--space-m);
}

.ps-plan-current-icon {
  display: inline-flex;
  flex: none;
  color: var(--color-secondary);
}

.ps-plan-current-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.ps-plan-current-name {
  font: var(--font-weight-bold) var(--text-label-large-size)/var(--text-label-large-line) var(--font-sans);
  color: var(--color-on-surface);
}

.ps-plan-current-meta {
  font: var(--font-weight-medium) var(--text-body-small-size)/var(--text-body-small-line) var(--font-sans);
  color: var(--color-on-surface-variant);
  text-wrap: pretty;
}

.ps-plan-owned {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: clamp(20px, calc(4 * var(--vwu)), 28px);
}

.ps-plan-pill {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  padding: 3px 12px 4px;
  border-radius: var(--radius-pill);
  background: var(--color-secondary);
  color: var(--color-on-secondary);
  font: var(--font-weight-bold) 11px/16px var(--font-sans);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.ps-value-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: 14px var(--space-m);
  border-radius: var(--radius-card);
  background: var(--color-surface-container-high);
  box-shadow: inset 0 0 0 1px var(--color-outline);
}

.ps-value-icon { color: var(--color-secondary); }

.ps-value-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.ps-value-title {
  font: var(--font-weight-bold) var(--text-label-large-size)/var(--text-label-large-line) var(--font-sans);
  color: var(--color-on-surface);
  text-wrap: pretty;
}

.ps-value-body {
  font: var(--font-weight-medium) var(--text-body-small-size)/var(--text-body-small-line) var(--font-sans);
  color: var(--color-on-surface-variant);
  text-wrap: pretty;
}

/* Info / legal blocks */
.ps-info-box {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: var(--space-sm) var(--space-m);
  border-radius: var(--radius-card);
  background: var(--color-surface-container-high);
  box-shadow: inset 0 0 0 1px var(--color-outline);
}

.ps-info-box > span:first-child {
  display: inline-flex;
  flex: none;
  margin-top: 1px;
  color: var(--color-on-surface-variant);
}

.ps-info-box > span:last-child,
.ps-legal {
  font: var(--font-weight-medium) var(--text-body-small-size)/var(--text-body-small-line) var(--font-sans);
  color: var(--color-on-surface-variant);
  text-wrap: pretty;
}

.ps-legal { margin: 0; }

/* The .ps-legal-links rules that were here are gone with the markup they styled: the plans
   screen's Privacy policy / Terms of service footer was removed, so nothing emits that class
   any more. Recorded rather than silently dropped, because the class name still appears in
   one comment in privacy-score.php and someone may otherwise go looking for these rules.
   `.ps-bullet-dot` on its own is NOT dead — the plan feature bullets still use it. */

/* ── Full plan details (privacy_score_plans_full_details) ─────────────────
   Content lifted from the standalone Elementor pricing page and restyled on Aurora
   tokens. Every rule here is additive: with the setting off none of this markup is
   rendered, so the plans screen is byte-identical to 6.6.x.
   ------------------------------------------------------------------------- */
.ps-plans-intro {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
}
.ps-plans-intro-title {
  margin: 0;
}
/* Eyebrow above the plan name — "For basic use" / "Recommended" / "For advanced use".
   The source page puts Recommended in mint #38EFBE, which does not exist in this palette;
   it uses the secondary lavender instead, which is the accent the rest of the flow already
   uses for emphasis. The other two are quieter: outlined rather than filled, so
   Recommended stays the only card that draws the eye. */
.ps-plan-eyebrow {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  padding: 3px 10px 4px;
  border-radius: var(--radius-pill);
  box-shadow: inset 0 0 0 1px var(--color-outline);
  color: var(--color-on-surface-variant);
  font: var(--font-weight-bold) 11px/16px var(--font-sans);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.ps-plan-eyebrow--rec {
  background: var(--color-secondary);
  box-shadow: none;
  color: var(--color-on-secondary);
}
/* The "What's included" expander sits inside a card that already has a surface, so it
   drops the accordion's own fill and border and reads as a divider instead. Otherwise it
   looks like a panel floating inside a panel. */
.ps-accordion--includes {
  background: none;
  box-shadow: none;
  border-radius: 0;
  border-top: 1px solid var(--color-outline);
}
.ps-wrap .ps-accordion--includes .ps-accordion-trigger {
  padding: var(--space-m) 0 var(--space-s);
}
@media (hover: hover) {
  /* !important is required, not cosmetic: the general trigger hover above is now !important,
     and !important beats specificity — so without it this (0,4,0) rule would LOSE to that
     (0,3,0) one and the in-card expander would grow a background wash it is not meant to
     have. */
  .ps-wrap .ps-accordion--includes .ps-accordion-trigger:hover {
    background: none !important;
    color: var(--color-secondary);
  }
}
.ps-includes {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  margin: 0;
  padding: 0 0 var(--space-m);
  list-style: none;
}
.ps-includes[hidden] { display: none; }
.ps-includes li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-s);
  margin: 0;
  font: var(--font-weight-medium) var(--text-label-large-size)/var(--text-label-large-line) var(--font-sans);
  color: var(--color-on-surface-variant);
}
.ps-includes-tick {
  display: inline-flex;
  flex: none;
  /* Optical alignment: the tick reads high against a 20px line, so nudge it down rather
     than centring the row, which would drop the text off the baseline on wrapped items. */
  margin-top: 2px;
  color: var(--color-secondary);
}
/* Legal footnotes, verbatim from the source page. Smallest type in the flow because they
   are reference text, not reading matter, but still on the variant colour rather than
   anything dimmer — this is pricing and fee information, so it has to stay legible. */
.ps-plan-footnotes {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  padding-top: var(--space-s);
}
.ps-plan-footnotes p {
  margin: 0;
  font: var(--font-weight-medium) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
  text-wrap: pretty;
}

/* ── Person selection list ──────────────────────────────────────────────── */
.ps-select-list {
  display: flex;
  flex-direction: column;
  max-height: 340px;
  overflow-y: auto;
  border-radius: var(--radius-sm);
  box-shadow: inset 0 0 0 1px var(--color-outline);
}

.ps-select-item {
  padding: var(--space-m);
  min-height: 72px;
  display: flex;
  align-items: center;
  cursor: pointer;
  background: var(--color-surface-container-high);
  color: var(--color-on-surface);
  font: var(--font-weight-semibold) var(--text-body-small-size)/var(--text-body-small-line) var(--font-sans);
  transition: background 140ms linear;
}

.ps-select-item + .ps-select-item { border-top: 1px solid var(--color-outline); }

@media (hover: hover) {
  .ps-select-item:hover { background: var(--color-surface-container-higher); }
}

/* The record-picker rows are the only way to choose a person, and they are keyboard-reachable
   (tabindex="0" in showSelectionPrompt). This removed the outline and replaced it with the same
   background hover uses — #2A2C2E against a resting #222426 is a 1.09:1 change, i.e. no visible
   indicator at all for a keyboard user. The inset ring was already here and is what actually
   shows focus, so :focus-visible now carries a real outline as well. */
.ps-select-item:focus {
  outline: none;
  background: var(--color-surface-container-higher);
  box-shadow: inset 0 0 0 1.5px var(--color-secondary);
}
.ps-select-item:focus-visible {
  outline: 2px solid var(--color-secondary);
  outline-offset: -2px;
}

.ps-wrap .ps-select-none {
  align-self: center;
  border: none;
  appearance: none;
  background: none;
  cursor: pointer;
  padding: 0;
  font: var(--text-label-large-weight) var(--text-label-large-size)/var(--text-label-large-line) var(--font-sans);
  color: var(--color-on-surface);
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 3px;
}

@media (hover: hover) {
  .ps-wrap .ps-select-none:hover { color: var(--color-on-surface-variant); }
}

/* Refine note on the "more info" screen. */
.ps-refine-note {
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: var(--color-surface-container-high);
  box-shadow: inset 0 0 0 1px var(--color-outline);
  font: var(--font-weight-semibold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  color: var(--color-on-surface-variant);
}

/* ── Status / misc ──────────────────────────────────────────────────────── */
#ps-error {
  display: none;
  padding: var(--space-m);
  border-radius: var(--radius-sm);
  background: var(--color-error-container);
  color: var(--color-on-error-container);
  font: var(--font-weight-semibold) var(--text-body-small-size)/var(--text-body-small-line) var(--font-sans);
}

.ps-api-error {
  margin: 0;
  font: var(--font-weight-semibold) var(--text-body-small-size)/var(--text-body-small-line) var(--font-sans);
  color: var(--color-error);
}















/* Identity panel (shown on a 0-result scan). */
.ps-identity-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  padding: var(--space-m);
  border-radius: var(--radius-sm);
  background: var(--color-surface-container-high);
  box-shadow: inset 0 0 0 1px var(--color-outline);
}

.ps-identity-kicker {
  margin: 0;
  font: var(--font-weight-bold) var(--text-label-medium-size)/var(--text-label-medium-line) var(--font-sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-on-surface-variant);
}

.ps-identity-rows {
  display: flex;
  flex-direction: column;
}

.ps-identity-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-m);
  padding: 10px 0;
  box-shadow: inset 0 -1px 0 var(--color-outline);
}

.ps-identity-item:last-child { box-shadow: none; padding-bottom: 0; }

.ps-identity-label {
  font: var(--font-weight-semibold) var(--text-body-small-size)/var(--text-body-small-line) var(--font-sans);
  color: var(--color-on-surface-variant);
}

.ps-identity-value {
  font: var(--text-label-large-weight) var(--text-label-large-size)/var(--text-label-large-line) var(--font-sans);
  color: var(--color-on-surface);
  text-align: right;
}















/* ── Animations ─────────────────────────────────────────────────────────── */
@keyframes psScreenIn {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

@keyframes psFieldIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

@keyframes psPulse {
  0%, 100% { transform: scale(0.92); opacity: 0.08; }
  50%      { transform: scale(1);    opacity: 0.18; }
}

@keyframes psSpin { to { transform: rotate(360deg); } }


/* ── Mobile: card treatment drops away, CTA bar pins to the viewport ─────── */
@media (max-width: 767px) {
  .ps-shell {
    align-items: stretch;
    padding: 0;
  }

  .ps-card {
    max-width: none;
    border-radius: 0;
    box-shadow: none;
    background: var(--color-surface);
    overflow: visible;
  }

  .ps-card-body {
    padding: var(--space-xl) var(--space-m) var(--space-3xl);
    background: var(--color-surface);
    border-radius: 0;
  }

  .ps-card-hero {
    aspect-ratio: 3 / 2;
    max-height: 38vh;
  }

  /* A fixed bar must have NO transformed ancestor, or that ancestor becomes its
     containing block and the bar pins to it instead of the viewport.
     
     `.ps-card` matters as much as `.ps-screen` here and was missed originally. Both use
     psScreenIn, which animates transform, and `animation-fill-mode: both` keeps that
     property applied indefinitely — so even though the `to` keyframe says
     `transform: none`, the computed value stays `matrix(1, 0, 0, 1, 0, 0)`. An identity
     matrix is still a transform, and still establishes a containing block. Measured on
     an iPhone: `.ps-cta-bar` reported `position: fixed` while sitting 209px above the
     bottom of the screen, because it was pinned to the card.
     
     The prototype sidesteps this with `[style*="auroraScreenIn"]`, an attribute selector
     that catches every element using the animation. This is the class-based equivalent,
     and it has to list each one.
     
     NOTE for future edits: any new animation that touches `transform` on an ancestor of
     .ps-cta-bar must be neutralised here too, or the bar silently stops pinning. */
  .ps-card,
  .ps-screen { animation-name: psFadeOnly; }

  /* iOS Safari zooms the page whenever a focused input computes to LESS than 16px, and
     it does not zoom back out on blur — so one tap on a small field leaves the rest of
     the flow magnified. That is the reported "enters the code, zooms in, next screen
     stays zoomed".

     Aurora specifies 14px (Body Small) for the field input, which is correct for a
     native app and unusable on the mobile web for exactly this reason. So: 14px on
     desktop to match the design, 16px here. The OTP cells are already well clear at
     22px and are restated only so a theme cannot pull them under the threshold.

     The alternative — maximum-scale=1 or user-scalable=no on the viewport meta — is
     deliberately NOT used. It fails WCAG 1.4.4 by blocking pinch-zoom for low-vision
     users, iOS Safari has ignored it since iOS 10, and the meta tag belongs to the
     theme, not this plugin. */
  .ps-wrap .ps-field input[type="tel"],
  .ps-wrap .ps-field input[type="email"],
  .ps-wrap .ps-field input[type="text"] {
    font-size: 16px !important;
    line-height: var(--text-body-medium-line) !important;
  }
  .ps-wrap .ps-code-digit { font-size: 22px !important; }

  .ps-cta-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    margin: 0;
    padding: var(--space-m) var(--space-m) calc(var(--space-m) + env(safe-area-inset-bottom, 0px));
  }

  /* Reserve room for the pinned bar. --cta-h is measured by JS onto #ps-card-body and
     set to 0px on screens without a bar, so this needs no :has() — which matters
     because on an engine without :has() support the padding would be skipped entirely
     and the fixed bar would sit on top of the content.

     The fallback is 132px, matching the prototype, not 0px. It only applies before the
     measurement runs or if the script never executes — and 0px in that window means the
     last of the content sits underneath the bar with no way to scroll it clear. An
     over-reserve is a bit of empty space; an under-reserve hides the content. */
  .ps-card-body {
    padding-bottom: calc(var(--cta-h, 132px) + var(--space-3xl));
  }
}

@keyframes psFadeOnly {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ── Reduced motion ───────────────────────────────────────────────────────
   LAST IN THE FILE ON PURPOSE. This block used to sit above the mobile media query, which
   sets `.ps-card, .ps-screen { animation-name: psFadeOnly }` at equal specificity — so on a
   phone the `animation: none` here was reinstated by source order. Harmless in that instance
   (an opacity fade is not motion) but it meant this block was not actually authoritative,
   which is the kind of thing that stops being harmless the next time a rule is added.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .ps-card,
  .ps-screen,
  .ps-field,
  .ps-verify-error { animation: none !important; }
  .ps-ring-pulse { animation: none !important; }
  .ps-ring-progress { transition: none !important; }
  /* The per-stage spinner was the one PERPETUAL animation left running under reduce —
     psSpin rotates indefinitely. Replaced with a static ring rather than hidden, so the
     "searching" stage still has a visible marker. */
  .ps-spinner { animation: none !important; }
  /* Chevron rotation on the accordions. */
  .ps-accordion.is-open .ps-accordion-chevron { transition: none !important; }
  /* Anything else in the card that animates or transitions. Broad on purpose: this is the
     one place where a rule added later should be covered without needing to be listed. */
  .ps-wrap *,
  .ps-wrap *::before,
  .ps-wrap *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
