/* ==========================================================================
   Utility Classes
   ========================================================================== */

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

.skip-link {
  position: absolute;
  left: -9999px;
  top: 4px;
  z-index: 1000;
  padding: 0.5rem 1rem;
  background: #fff;
  color: #010f3e;
  text-decoration: none;
  font-weight: 600;
  border-radius: var(--container-border-radius);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

.skip-link:focus {
  left: 50%;
  transform: translateX(-50%);
}

.hidden {
  display: none;
}

/* ==========================================================================
   Logo & Avatar
   ========================================================================== */

.logo-avatar {
  --size: var(--space-xl);
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--size);
  height: var(--size);
  flex-shrink: 0;
  overflow: hidden;
}

/* ==========================================================================
   Page Layout
   ========================================================================== */

/* Pages whose content should fill the viewport below the header mark their
   wrapper with .page-fill. The container then gets explicit rows — header,
   steps, content — and the 1fr content row absorbs the remaining height, so
   nothing needs to know the header's rendered size. Inside the SEB flex frame
   the same class fills via flex instead (grid-row is inert there). */
.main-container:has(> .page-fill) {
  grid-template-rows: [header] auto [steps] auto [content] 1fr;
}

.main-container:has(> .page-fill) > .header-container {
  grid-row: header;
}

.main-container:has(> .page-fill) > .steps {
  grid-row: steps;
}

.page-fill {
  grid-row: content;
  flex: 1 1 auto;
}

/* Provider list page: lock the page to the viewport so #provider-list is the
   only scrollable area. main-container keeps its default grid layout (and the
   gutters that come with it); provider-selector stretches into the content
   row (min-height: 0 lets it shrink to it), then flexes its children inside. */
body:has(provider-selector) {
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
}

/* main is a flex item: without this its content-based automatic minimum
   (min-height: auto) stops it shrinking to the locked body height, pushing
   the bottom of the grid past the hidden overflow. */
body:has(provider-selector) > main {
  min-height: 0;
}

provider-selector {
  width: 100%;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

header {
  position: sticky;
  top: 0;
  z-index: 1;
  background-color: var(--color-background);
  margin-block-start: 0;
  padding: 0 var(--space-l);
}

.header-container {
  grid-column: full;
  display: flex;
  align-items: center;
  justify-content: center;
  position: sticky;
  top: 0;
  z-index: 3;
  gap: var(--space-s);
  background-color: var(--color-background);
  padding: var(--space-m) var(--grid-gap) 0 var(--space-m);
  margin-block-end: var(--space-xl-6xl);
}

.header-back-arrow {
  position: absolute;
  left: var(--grid-gap);
  background: none;
  border: none;
  cursor: pointer;
  color: var(--color-neutral-1);
  padding: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.header-back-arrow.loading {
  pointer-events: none;
}

.header-back-arrow.loading > svg {
  visibility: hidden;
}

.header-back-arrow.loading::before {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 1em;
  height: 1em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}

.search-relative {
  position: relative;
  width: 100%;
  /* One source for the icon and the placeholder so they can't drift apart. */
  --search-hint-color: var(--color-neutral-4);
}

/* Figma: 50px tall, 15px inset, a 20px icon and a 10px gap — so the text
   starts 45px in. Border, radius, font and background come from the shared
   input rule in elements.css. */
.search-relative > input[type="search"] {
  width: 100%;
  height: 50px;
  padding: 0 15px 0 45px;
}

/* Figma places the placeholder on Neutrals/400 (#94A3B8), which is 2.6:1 on
   white — below the 4.5:1 SC 1.4.3 wants for what is, with only an sr-only
   label, the field's visible label. Uses the nearest passing neutral. */
.search-relative > input[type="search"]::placeholder {
  color: var(--search-hint-color);
  opacity: 1;
}

.search-relative > input[type="search"]:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-color: transparent;
}

.search-icon {
  position: absolute;
  left: 15px;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  color: var(--search-hint-color);
  z-index: 2;
  pointer-events: none;
}

.nav-button-group,
.button-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  width: 100%;
  box-sizing: border-box;
}

provider-selector > .stack,
#provider-form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* Container hugs its items so it doesn't stretch to the page bottom when the
   list is short; shrinks when the list overflows so #provider-list scrolls. */
.provider-selector-container {
  flex: 0 1 auto;
  min-height: 0;
}

/* Keeps the list clear of the buttons once it grows tall enough to reach
   them; the auto margin below still pins them to the bottom when it doesn't. */
#provider-form {
  gap: var(--space-s);
}

#provider-form > .nav-button-group,
#provider-form > .button-group {
  margin-block-start: auto;
  flex-shrink: 0;
}

/* Text buttons keep their intrinsic width so the click target hugs the label. */
.nav-button-group > button:not(.btn-text),
.nav-button-group > a,
.button-group > button:not(.btn-text),
.button-group > a {
  width: 100%;
  justify-content: center;
  text-align: center;
}

.provider-selector-back-button {
  display: none;
}

/* ==========================================================================
   Typography
   ========================================================================== */

p {
  color: var(--color-neutral-4);
}

p.description {
  font-size: var(--font-size-xs);
  max-width: none;
  color: var(--color-neutral-4);
}

/* ==========================================================================
   Form Elements
   ========================================================================== */

/* Provider card: native <button> used for both single-select (plain button
   that auto-submits on click, no persistent checked state) and multi-select
   (button with role="checkbox" aria-checked that toggles). Using a real
   button satisfies SC 1.4.11 via the user-agent exemption — no hidden input
   with appearance:none, which is what automated scanners were flagging. */
button.item--card {
  /* Reset native button chrome */
  font: inherit;
  text-align: left;
  width: 100%;
  background: transparent;
  /* Layout */
  display: flex;
  flex-direction: row;
  align-items: center;
  cursor: pointer;
  gap: var(--space-s);
  padding: var(--space-2xs) var(--space-s) ;
  /* Card styling */
  color: var(--color-neutral-1);
  border: var(--input-border-thickness) solid transparent;
  border-radius: var(--input-border-radius);
  transition: all 0.3s;
}

button.item--card[aria-checked="true"] {
  color: var(--color-neutral-1);
  border-color: var(--color-neutral-1);
  box-shadow: 0 0 var(--space-2xs)
    color-mix(in srgb, var(--color-neutral-1), transparent 93%);
}

@media (hover: hover) {
  button.item--card:hover {
    color: var(--color-neutral-1);
    border-color: var(--color-neutral-1);
    box-shadow: 0 0 var(--space-2xs)
      color-mix(in srgb, var(--color-neutral-1), transparent 93%);
  }
}

button.item--card:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

button.item--card > .logo-avatar {
  --size: 40px;
}

button.item--card > span:not(.deselect-provider) {
  font-size: var(--font-size-s);
}

/* deselect affordance on checked multi-select cards */
button.item--card > .deselect-provider {
  margin-left: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--space-m);
  height: var(--space-m);
  flex-shrink: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--color-neutral-3);
  opacity: 0;
  transform: scale(0);
  transition: all 0.2s ease-in-out;
  padding: 0;
}

button.item--card[aria-checked="true"] > .deselect-provider {
  opacity: 1;
  transform: scale(1);
}

@media (hover: hover) {
  button.item--card > .deselect-provider:hover {
    box-shadow: 0 0 10px -2px var(--color-neutral-1);
    background: color-mix(in srgb, var(--color-neutral-1), transparent 90%);
    color: var(--color-neutral-5);
  }
}

button.item--card.loading {
  pointer-events: none;
  opacity: var(--primary-color-disabled-opacity);
}

button.item--card.loading::after {
  content: "";
  margin-left: auto;
  width: 1em;
  height: 1em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
  flex-shrink: 0;
}

.psu-id-input-container input[readonly] {
  background: var(--color-neutral-9) url("/static/icons/padlock.svg") no-repeat
    right var(--space-s) center;
  background-size: 1rem auto;
  padding-right: 2.5rem;
}

.psu-id-input-container small {
  font-weight: normal;
  color: var(--color-neutral-5);
}

.psu-id-input-container .psu-id-error {
  color: var(--color-error, #d32f2f);
}

/* --------------------------------------------------------------------------
   Main container — single column, centered
   -------------------------------------------------------------------------- */

.main-container {
  --page-bottom-space: var(--space-s);
  width: 100%;
  max-width: 48rem;
  margin: 0 auto;
  padding-block-end: var(--page-bottom-space);
}

.main-container.embedded {
  padding-block-start: var(--space-m);
}

/* --------------------------------------------------------------------------
   Steps bar (desktop only)
   -------------------------------------------------------------------------- */

.steps {
  display: none;
}

@media (min-width: 48rem) {
  .steps {
    border: 1px solid var(--color-neutral-7);
    border-radius: var(--container-border-radius);
    padding: var(--space-s) var(--space-s);
    margin-bottom: var(--space-l);
  }

  .steps ol {
    display: flex;
    align-items: center;
    justify-content: space-between;
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .steps li {
    display: flex;
    align-items: center;
    gap: var(--space-2xs);
    white-space: nowrap;
  }

  .step-circle,
  .step-label {
    flex-shrink: 0;
  }

  .steps li:not(:last-child)::after {
    content: "";
    display: block;
    flex: 1 1 0;
    min-width: var(--space-s);
    height: 2px;
    background: var(--color-neutral-7);
    margin: 0 var(--space-xs);
  }

  .steps li.completed:not(:last-child)::after {
    background: var(--color-success);
  }

  .step-circle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    font-size: var(--font-size-xs);
    font-weight: 600;
    flex-shrink: 0;
    background: var(--color-neutral-7);
    color: var(--color-neutral-4);
  }

  .active > .step-circle {
    background: var(--color-accent);
    /* White on brand orange is 3.0:1 — below WCAG AA's 4.5:1 for normal
       text, but accepted here: the digit is aria-hidden (decorative only)
       and the adjacent .step-label carries the semantic meaning at
       AAA contrast. BrowserStack flagged this as "needs review" rather
       than a confirmed violation. Preserving the SBAB brand look. */
    color: var(--color-neutral-10);
  }

  .completed > .step-circle,
  .step-check {
    background: var(--color-success);
    color: var(--color-neutral-10);
  }

  .step-label {
    font-size: var(--font-size-xs);
    font-weight: 500;
    color: var(--color-neutral-4);
  }

  .active > .step-label {
    color: var(--color-neutral-1);
  }

  .completed > .step-label {
    color: var(--color-neutral-3);
  }
}

/* --------------------------------------------------------------------------
   Welcome page
   -------------------------------------------------------------------------- */

.welcome-hero {
  font-size: var(--font-size-s);
  color: var(--color-neutral-3);
}

.welcome-steps ol {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
}

.welcome-steps li {
  display: grid;
  grid-template-columns: 2rem 1fr;
  align-items: center;
  column-gap: var(--space-s);
  font-size: var(--font-size-m);
  line-height: 21px;
  color: var(--color-neutral-3);
}

.welcome-steps li::before {
  counter-increment: step;
  content: counter(step);
  grid-row: 1;
  grid-column: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--color-neutral-7);
  border-radius: 50%;
  background: var(--color-background);
  color: var(--color-neutral-4);
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-s);
}

.welcome-steps li:not(:last-child)::after {
  content: "";
  grid-row: 2;
  grid-column: 1;
  width: 1px;
  height: var(--space-s);
  background: var(--color-neutral-7);
  /* The circle column is an even 32px, so its centre falls on a pixel
     boundary. Offsetting by a transform keeps the hairline straddling that
     boundary instead of letting layout snap it a half pixel to one side. */
  justify-self: start;
  margin-block: var(--space-3xs);
  margin-inline-start: 1rem;
  transform: translateX(-50%);
}

.welcome-steps ol {
  counter-reset: step;
}

/* Welcome page — push button to bottom on mobile
   The auto margin collapses to zero when the body fills the row, so the
   padding keeps a minimum gap between body and actions. */
.welcome-page > form {
  margin-block-start: auto;
  padding-block-start: var(--space-s);
}

@media (min-width: 48rem) {
  .welcome-page > form {
    margin-block-start: var(--space-5xl);
    padding-block-start: 0;
  }
}

/* --------------------------------------------------------------------------
   Redirect auth — push buttons to bottom on mobile
   -------------------------------------------------------------------------- */

redirect-auth-form {
  display: flex;
  flex-direction: column;
}

redirect-auth-form .button-group,
redirect-auth-form .nav-button-group {
  margin-top: auto;
  padding-block-start: var(--space-m);
}

/* On mobile the mark absorbs the space left between the copy and the buttons,
   centering itself in it. */
redirect-auth-form .provider-mark {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Explicit, so the spinner state still hides the mark despite the display above. */
.provider-mark.hidden {
  display: none;
}

.provider-mark > svg {
  display: block;
  width: 100px;
  height: 100px;
}

@media (min-width: 48rem) {
  redirect-auth-form {
    display: block;
  }

  /* Block layout on desktop, so the rhythm comes from margins here. */
  redirect-auth-form .provider-mark {
    margin-block-start: var(--space-5xl);
    margin-block-end: var(--space-5xl);
  }
}

/* ==========================================================================
   Account List
   ========================================================================== */

.provider-list-subtitle {
  color: var(--color-neutral-1);
}

label.account-item {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: var(--space-s);
  cursor: pointer;
}

label.account-item input[type="checkbox"],
label.account-item input[type="radio"] {
  width: var(--space-m);
  height: var(--space-m);
  accent-color: var(--primary-color-bg);
  flex-shrink: 0;
  margin-top: var(--space-3xs);
}

.account-details {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
}

.account-number {
  font-size: var(--font-size-xs);
  color: var(--color-neutral-4);
}

.account-balance {
  font-size: var(--font-size-xs);
  color: var(--color-neutral-4);
}

/* ==========================================================================
   Account Rename Page
   ========================================================================== */

.rename-label .account-number {
  font-size: var(--font-size-xs);
  color: var(--color-neutral-4);
}

.rename-input {
  width: 100%;
}

.rename-error {
  color: var(--color-error, #d32f2f);
}

/* Fill the viewport so the (already sticky) action button pins to the bottom
   even when content is short, then revert to natural flow on larger screens.
   Mirrors the decoupled-auth page. */
account-renamer {
  display: flex;
  flex-direction: column;
}

account-renamer > .stack,
#account-rename-form {
  flex: 1;
}

#account-rename-form .button-group {
  margin-top: auto;
}

@media (min-width: 48rem) {
  account-renamer {
    display: block;
  }

  #account-rename-form .button-group {
    margin-top: 0;
  }
}

/* ==========================================================================
   Decoupled Auth Page
   ========================================================================== */

decoupled-auth-form {
  display: flex;
  flex-direction: column;
}

decoupled-auth-form > .stack {
  flex: 1;
}

decoupled-auth-form .button-group {
  margin-top: auto;
}

@media (min-width: 48rem) {
  decoupled-auth-form {
    display: block;
  }

  decoupled-auth-form > .stack {
    flex: initial;
  }

  decoupled-auth-form .button-group {
    margin-top: 0;
  }
}

.decoupled-steps {
  list-style-position: inside;
  font-size: var(--font-size-s);
  color: var(--color-neutral-2);
}

.decoupled-steps li::marker {
  color: var(--color-neutral-2);
}

.bankid-logo {
  display: none;
}

/* ==========================================================================
   Enter PSU ID Page
   ========================================================================== */

/* Kreditz logo beside the provider list title. Hidden by default; themes opt in via {theme}.css. */
.provider-list-kreditz-logo {
  display: none;
}

.provider-title-row {
  align-items: center;
  color: var(--color-neutral-1);
}

/* Class selector needed: the global `span` rule in elements.css sets
   font-size directly on the element, which beats inheritance from the row. */
.provider-title-row > span {
  font-size: var(--font-size-m);
}

enter-psu-id p,
enter-psu-id small,
enter-psu-id details {
  font-size: var(--font-size-xs);
}

.consent-body h2 {
  font-size: var(--font-size-s);
  margin-block-start: var(--space-xs);
}

.consent-body p {
  margin-block-end: var(--space-2xs);
}

.consent-body strong {
  font-weight: var(--font-weight-bold);
}

.consent-body a,
.consent-body a:visited {
  color: var(--primary-color-bg);
  text-decoration: underline;
}

/* --------------------------------------------------------------------------
   Focus indicators — WCAG 2.4.7
   Visible, high-contrast outline for all interactive elements.
   -------------------------------------------------------------------------- */

.main-container input:focus-visible,
.main-container button:focus-visible,
.main-container a:focus-visible,
.main-container summary:focus-visible,
.main-container button.primary:focus-visible {
  outline-color: var(--color-accent);
}

/* --------------------------------------------------------------------------
   Button group — sticky on mobile
   -------------------------------------------------------------------------- */

.nav-button-group,
.button-group {
  position: sticky;
  bottom: 0;
  background: var(--color-background);
  z-index: 2;
}

/* Provider list page locks the viewport, so nav buttons are pinned at the
   bottom of #provider-form via flex auto-margin — sticky would detach them
   and let the list visually overlap. */
provider-selector .nav-button-group,
provider-selector .button-group {
  position: static;
}

@media (min-width: 48rem) {
  .nav-button-group,
  .button-group {
    position: static;
  }
}

/* --------------------------------------------------------------------------
   Enter PSU ID — consent info card
   -------------------------------------------------------------------------- */

.consent-card {
  display: flex;
  gap: var(--space-s);
  padding-top: var(--space-3xs);
  padding-bottom: var(--space-3xs);
}

.consent-icon {
  flex-shrink: 0;
  margin-top: var(--space-3xs);
}

.consent-text {
  font-size: var(--font-size-xs);
  color: var(--color-neutral-3);
}

.consent-text p {
  font-size: var(--font-size-xs);
  margin: 0;
  /* Inherit the container colour (neutral-3 = 9:1 on the #f5f5f5 consent
     card) instead of the global p rule's neutral-4, which would fall just
     below the 7:1 AAA threshold on this background. */
  color: inherit;
}

.consent-text a {
  /* The global link blue (#0D6EFD) is 4.31:1 on the card's neutral-9
     background — just under the 4.5:1 AA threshold. Darken it here. */
  color: #0B5ED7;
}

.consent-text strong {
  color: var(--color-neutral-1);
}

/* --------------------------------------------------------------------------
   Enter PSU ID — legal sections card
   -------------------------------------------------------------------------- */

.legal-sections {
  padding: 0 var(--space-m);
}

.legal-sections details {
  border: none;
  border-radius: 0;
  border-bottom: 1px solid var(--color-neutral-7);
}

.legal-sections summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-s) 0;
}

.legal-sections summary::after {
  content: "+";
  font-size: var(--font-size-m);
  color: var(--color-neutral-4);
  flex-shrink: 0;
}

.legal-sections details[open] summary::after {
  content: "−";
}

.legal-sections details[open] {
  background: transparent;
}

.faq-sections {
  display: flex;
  flex-direction: column;
  gap: var(--space-m);
}

.faq-sections details {
  border: none;
  border-radius: 0;
  background: transparent;
}

.faq-sections summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-s);
  padding: var(--space-xs) 0;
}

.faq-sections summary::after {
  content: "+";
  font-size: var(--font-size-m);
  color: var(--color-neutral-4);
  flex-shrink: 0;
}

.faq-sections details[open] summary::after {
  content: "−";
}

.dynamic-page {
  margin-bottom: var(--space-s);
}

.dynamic-page-body > * + * {
  margin-block-start: var(--space-xs);
}

.dynamic-page-body h1,
.dynamic-page-body h2,
.dynamic-page-body h3,
.dynamic-page-body h4 {
  margin-block-start: var(--space-m);
}

.dynamic-page-body h1:first-child,
.dynamic-page-body h2:first-child,
.dynamic-page-body h3:first-child,
.dynamic-page-body h4:first-child {
  margin-block-start: 0;
}

.dynamic-page-body ul,
.dynamic-page-body ol {
  padding-inline-start: var(--space-m);
}

.dynamic-page-body li + li {
  margin-block-start: var(--space-3xs);
}

.dynamic-page-body hr {
  margin-block: var(--space-m);
  border: 0;
  border-top: 1px solid var(--color-neutral-3);
}

a.dynamic-page-back,
a.dynamic-page-back:visited {
  color: var(--color-neutral-1);
}

a.dynamic-page-back:hover {
  color: var(--color-neutral-1);
}

/* --------------------------------------------------------------------------
   Done page
   -------------------------------------------------------------------------- */

.done {
  display: flex;
  flex-direction: column;
}

.completion {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--space-4xl-6xl)
}

.done-checkmark {
  width: 170px;
}

.completion .done-checkmark {
  margin-block-start: var(--space-4xl-6xl);
}

.done-footer {
  margin-top: auto;
  display: flex;
  justify-content: center;
}

.done-footer-img {
  display: block;
}

/* ==========================================================================
   Misc Components
   ========================================================================== */

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

@media (prefers-reduced-motion: reduce) {
  .spinner::before,
  .primary.loading::before,
  .secondary.loading::after,
  .header-back-arrow.loading::before,
  button.item--card.loading::after {
    animation: none;
  }
}

.spinner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  padding: var(--space-m) 0;
  color: var(--color-neutral-3);
  font-size: var(--font-size-s);
}

.spinner::before {
  content: "";
  width: 1.25em;
  height: 1.25em;
  border: 2px solid var(--color-neutral-7);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
  flex-shrink: 0;
}

/* Replace the button's text with a centered spinner while loading. The button
   keeps its natural width because the text is just made transparent (it still
   occupies layout space). The spinner is absolutely positioned, so it doesn't
   push other content. Border colors are explicit since currentColor would
   also be transparent. */
.primary.loading,
.secondary.loading {
  pointer-events: none;
  position: relative;
  color: transparent;
}

.primary.loading::before {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 1em;
  height: 1em;
  border: 2px solid var(--primary-color-text);
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}

.secondary.loading::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 1em;
  height: 1em;
  border: 2px solid var(--secondary-color-text);
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}

#qr-code-container {
  display: flex;
  width: 100%;
  justify-content: center;
  align-items: center;
  min-height: 156px;
  padding-top: var(--space-m);
  padding-bottom: var(--space-m);
}

#qr-code-container.hidden {
  display: none;
}

/* Auth actions */
.auth-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-s);
  width: 100%;
}

.auth-actions > button:not(.btn-text),
.auth-actions > a:not(.btn-text),
.auth-actions > a {
  width: 100%;
  justify-content: center;
  align-items: center;
  gap: var(--space-2xs);
  text-align: center;
}

/* The QR button hugs its label. Themes that want it full width override this. */
.auth-actions > button.show-qr-btn {
  width: fit-content;
}

.auth-actions .bankid-logo {
  display: inline-block;
  width: var(--icon-size);
  height: var(--icon-size);
  margin-block: -0.75em;
}

.primary .bankid-logo {
  filter: brightness(0) invert(1);
}

.bankid-mark > img {
  width: 159px;
  height: auto;
}

/* Display area: overlays spinner/QR so swapping them causes no layout shift */
.auth-center {
  display: grid;
  place-items: center;
  width: 100%;
  min-height: 300px;
}

.auth-center > * {
  grid-area: 1 / 1;
}

.auth-center > .hidden {
  display: block;
  visibility: hidden;
}

.auth-center--selector {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
}

.auth-center--selector > .bankid-mark {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
}

#btn-qr.hidden,
#btn-ast.hidden,
.decoupled-or-divider.hidden {
  display: none;
}

.btn-text {
  all: unset;
  cursor: pointer;
  color: var(--color-primary-link);
  font-size: var(--font-size-s);
  font-weight: var(--font-weight-button);
  padding: var(--space-2xs) 0;
}

.decoupled-or-divider {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  width: 100%;
  color: var(--color-neutral-4);
  font-size: var(--font-size-xs);
  margin: var(--space-2xs) 0;
}

.decoupled-or-divider::before,
.decoupled-or-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--color-neutral-7);
}

/* --------------------------------------------------------------------------
   Mobile QR disclosure

   The "show QR code" row and the panel it reveals. Only the mobile views
   render this markup, so the rules need no viewport guard.
   -------------------------------------------------------------------------- */

.qr-disclosure {
  width: 100%;
}

.qr-disclosure__toggle {
  all: unset;
  /* box-sizing: border-box; */
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  width: 100%;
  padding: var(--space-xs) 0;
  cursor: pointer;
  color: var(--color-neutral-2);
  font-size: var(--font-size-s);
  font-weight: var(--font-weight-button);
  line-height: 0.9;
  text-align: left;
}

/* Class selector needed: the global `span` rule in elements.css sets font-size
   directly on the element, which beats inheritance from the row. */
.qr-disclosure__toggle > span {
  font-size: var(--font-size-s);
}

.qr-disclosure__toggle:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Points right while collapsed, down once the panel is open. */
.qr-disclosure__chevron {
  flex-shrink: 0;
  width: var(--space-s);
  height: var(--space-s);
  transform: rotate(-90deg);
  transition: transform 0.2s ease-in-out;
}

.qr-disclosure__toggle[aria-expanded="true"] .qr-disclosure__chevron {
  transform: rotate(0deg);
}

.qr-disclosure__panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-l);
  width: 100%;
}

.qr-disclosure__panel[hidden] {
  display: none;
}

/* Indented to the label so the copy reads as the row's own content. The code
   below it stays centred on the full content width. */
.qr-disclosure__hint {
  align-self: stretch;
  padding-inline-start: var(--space-m);
  color: var(--color-neutral-2);
  font-size: var(--font-size-s);
}

/* Scanner framing: a light plate holding the code, with four brackets sitting
   just outside it. Sizes derive from --qr-code-size, so scaling the code up
   scales the whole assembly. */
.qr-panel {
  /* The code's own pixel size — the one dimension the scale can't express,
     since it has to stay large enough to scan. */
  --qr-code-size: 180px;

  position: relative;
  padding: 2px;
}

.qr-plate {
  display: grid;
  place-items: center;
  width: calc(var(--qr-code-size) + var(--space-2xl));
  height: calc(var(--qr-code-size) + var(--space-2xl));
  background: var(--color-neutral-8);
  border-radius: 5px;
}

/* White sheet the code sits on, inset in the plate so the plate reads as a
   border around it. Radius matches the plate rather than the themed token so
   the two nested corners stay concentric. */
.qr-sheet {
  display: grid;
  place-items: center;
  width: calc(var(--qr-code-size) + var(--space-s));
  height: calc(var(--qr-code-size) + var(--space-s));
  background: white;
}

/* The shared QR display sizes itself for the desktop view; inside the plate it
   only needs to fill the code area. */
.qr-plate .auth-center {
  width: auto;
  min-height: 0;
}

.qr-plate .qr-frame {
  padding: 0;
}

/* Loading state: the ring sits above its label rather than beside it, so the
   plate reads as "waiting for the code" while the first poll is in flight. */
.spinner--stacked {
  flex-direction: column;
  gap: var(--space-m);
  /* Inline padding only: the longest translations wrap, and this keeps them
     off the edges of the plate. */
  padding: 0 var(--space-2xs);
  text-align: center;
}

.spinner--stacked::before {
  width: var(--space-2xl);
  height: var(--space-2xl);
}

/* While the first poll is in flight the frame is not drawn — the spinner and
   its label stand alone. The plate keeps its box, so the code drops into place
   without moving the page when it arrives. */
.qr-panel:has(#auth-spinner:not(.hidden)) .qr-panel__corner {
  display: none;
}

.qr-panel:has(#auth-spinner:not(.hidden)) .qr-plate,
.qr-panel:has(#auth-spinner:not(.hidden)) .qr-sheet {
  background: transparent;
}

.qr-plate .qr-frame > img {
  width: var(--qr-code-size);
  height: var(--qr-code-size);
}

/* An SVG rather than two borders: only a stroke can round the open ends of the
   arms as well as the corner itself. One shape, rotated into each corner. */
.qr-panel__corner {
  position: absolute;
  width: var(--space-m);
  height: var(--space-m);
  color: var(--color-primary);
}

.qr-panel__corner > svg {
  display: block;
  width: 100%;
  height: 100%;
}

.qr-panel__corner:nth-child(1) {
  top: 0;
  left: 0;
}

.qr-panel__corner:nth-child(2) {
  top: 0;
  right: 0;
  transform: rotate(90deg);
}

.qr-panel__corner:nth-child(3) {
  bottom: 0;
  right: 0;
  transform: rotate(180deg);
}

.qr-panel__corner:nth-child(4) {
  bottom: 0;
  left: 0;
  transform: rotate(-90deg);
}

/* Toast notification
 *
 * Single design used across all themes and layouts: a warm red/cream card
 * surfaced to the user for errors. Per-toast colours are scoped to local
 * custom properties so the palette stays in one place.
 */
.toast {
  --toast-bg: rgba(254, 247, 246, 1);
  --toast-border: rgba(185, 47, 19, 0.4);
  --toast-text: rgba(185, 47, 19, 1);
  --toast-close-bg: rgba(253, 235, 232, 1);

  position: fixed;
  top: var(--space-m);
  left: 50%;
  transform: translateX(-50%) translateY(-1rem);
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  width: calc(100% - var(--space-l));
  max-width: 32rem;
  padding: var(--space-xs) var(--space-s);
  background: var(--toast-bg);
  border: 1px solid var(--toast-border);
  border-radius: 1rem;
  color: var(--toast-text);
  text-align: left;
  box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
  opacity: 0;
  transition:
    opacity 0.2s,
    transform 0.2s;
  z-index: 1000;
}

.toast.visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.toast__icon {
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  margin-top: 0.125rem;
}

.toast__content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}

.toast__title {
  font-weight: 450;
  font-size: var(--font-size-s);
  color: var(--toast-text);
}

.toast__description,
.toast__message {
  font-size: var(--font-size-s);
  color: var(--toast-text);
}

.toast__close {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  background: var(--toast-close-bg);
  border: 0;
  border-radius: 50%;
  color: var(--toast-text);
  cursor: pointer;
  line-height: 0;
}

.toast__close:focus-visible {
  outline: 2px solid var(--toast-text);
  outline-offset: 2px;
}

.callback-link {
  font-size: var(--font-size-xs);
  color: var(--color-neutral-5);
  margin-top: 1rem;
  display: inline-block;
}

#qr-code-container > img,
.qr-frame > img {
  width: 200px;
  height: 200px;
  display: block;
}

.qr-frame {
  position: relative;
  padding: 16px;
}

th {
  text-align: left;
}

/* The scroll region spans both provider groups ("selected banks" and the rest)
   so they share one scrollbar. */
.provider-list-scroll {
  flex: 1 1 auto;
  min-height: 0;
}

#provider-list,
#selected-provider-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

#provider-list > li,
#selected-provider-list > li {
  display: block;
}

/* Search hides filtered-out rows with [hidden]; this has to out-specify the
   display rule above, which would otherwise keep them in the flex layout
   contributing their row gap. */
#provider-list > li[hidden],
#selected-provider-list > li[hidden] {
  display: none;
}

.provider-list-group[hidden] {
  display: none;
}

.provider-list-group-heading {
  font-size: var(--font-size-m);
  font-weight: var(--font-weight-bold);
  color: var(--color-neutral-1);
}

/* Selected banks read as cards: a permanent border and deselect affordance,
   unlike the borderless rows of the main list. Sizing and spacing stay
   inherited from .item--card so both groups line up on the page. */
#selected-provider-list button.item--card,
#selected-provider-list button.item--card[aria-checked="true"] {
  border-color: color-mix(in srgb, var(--color-neutral-1), transparent 80%);
  box-shadow: none;
}

.organization-logo {
  height: 2rem;
  width: auto;
}

/* ==========================================================================
   Inactivity Timeout
   ========================================================================== */

.inactivity-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--color-neutral-1), transparent 40%);
}

.inactivity-dialog {
  background: var(--color-background);
  border-radius: var(--container-border-radius);
  padding: var(--space-m);
  max-width: 24rem;
  width: calc(100% - var(--space-l));
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  box-shadow: 0 4px 24px
    color-mix(in srgb, var(--color-neutral-1), transparent 70%);
}

.inactivity-dialog p {
  font-size: var(--font-size-s);
  color: var(--color-neutral-1);
}

.inactivity-dialog > .primary {
  width: 100%;
  justify-content: center;
  padding: var(--space-s);
  margin-top: var(--space-xs);
  background-color: var(--color-primary-dark);
  border-color: var(--color-primary-dark);
}

.inactivity-dialog > .primary:hover,
.inactivity-dialog > .primary:active {
  background-color: var(--color-primary-dark);
  border-color: var(--color-primary-dark);
}

inactivity-timeout {
  display: block;
}

/* ==========================================================================
   Accessibility statement modal
   ========================================================================== */

/* display:contents keeps the closed dialog from consuming a `.stack` gap in
   the welcome page's layout. */
accessibility-modal {
  display: contents;
}

.accessibility-modal {
  box-sizing: border-box;
  width: calc(100% - var(--space-l));
  max-width: 45rem;
  max-height: calc(100dvh - var(--space-l));
  padding: var(--space-2xl) var(--space-s) var(--space-m);
  border: 0;
  border-radius: var(--container-border-radius);
  background: var(--color-background);
  color: var(--color-neutral-2);
  box-shadow: 0 0 20px
    color-mix(in srgb, var(--color-neutral-1), transparent 95%);
}

/* The UA hides a closed dialog with display:none, so the layout only applies
   while it is open. Column flex is what lets the body take the remaining
   height and scroll inside it. */
.accessibility-modal[open] {
  display: flex;
  flex-direction: column;
}

.accessibility-modal::backdrop {
  background: rgb(0 0 0 / 45%);
}

/* The scroll region holds both the statement and the close action, so the
   button trails the text instead of being pinned above the dialog edge.
   min-height:0 lets it shrink below its content height so it — and not the
   dialog — is what scrolls. tabindex="0" in the markup keeps the scroll region
   reachable by keyboard (SC 2.1.1). */
.accessibility-modal-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-l);
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.accessibility-modal-body h2 {
  font-size: var(--font-size-m);
}

/* Sits inside the dialog's top padding, clear of the content below. */
.accessibility-modal-close {
  position: absolute;
  inset-block-start: var(--space-2xs);
  inset-inline-end: var(--space-2xs);
  display: inline-flex;
  padding: var(--space-2xs);
  border: 0;
  border-radius: 1000px;
  background: var(--color-background);
  color: var(--color-neutral-3);
  cursor: pointer;
}

.accessibility-modal-close:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.accessibility-modal-actions {
  display: flex;
  justify-content: center;
  flex-shrink: 0;
  padding-block: var(--space-s);
}

@media (min-width: 48rem) {
  .accessibility-modal {
    padding-inline: var(--space-xl);
  }
}

/* ==========================================================================
   How it works page
   ========================================================================== */

.how-it-works-cards {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.how-it-works-card {
  background: color-mix(in srgb, var(--color-neutral-1), transparent 96%);
  border-radius: var(--container-border-radius);
  padding: var(--space-s);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.how-it-works-card > p {
  color: var(--color-neutral-1);
  font-size: var(--font-size-m);
}

.how-it-works-card-note {
  color: var(--color-neutral-2) !important;
  font-size: var(--font-size-s) !important;
}

.how-it-works-card-icon {
  width: 1.5rem;
  height: 1.5rem;
}
