/* ==========================================================================
   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 [banners] auto [steps] auto [content] 1fr;
}

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

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

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

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

/* Bank pickers: 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); the picker stretches into the content row
   (min-height: 0 lets it shrink to it), then flexes its children inside. */
body:has(provider-selector),
body:has(bank-branch-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,
body:has(bank-branch-selector) > main {
  min-height: 0;
}

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

header {
  position: sticky;
  top: 0;
  z-index: 1;
  background-color: var(--color-canvas);
  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-canvas);
  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-text-heading);
  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;
}

/* Mirrors .header-back-arrow on the opposite edge: the logo stays centred by
   the container's flex centring, so both affordances sit outside the flow. */
.header-close {
  position: absolute;
  right: var(--grid-gap);
  color: var(--color-text-heading);
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

.search-relative {
  position: relative;
  width: 100%;
}

/* Figma: 15px inset, a 20px icon and a 10px gap — so the text starts 45px in.
   Height, border, radius, font and background come from the shared input rule
   in elements.css. */
.search-relative > input[type="search"] {
  width: 100%;
  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(--color-input-placeholder);
  opacity: 1;
}

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

.search-icon {
  position: absolute;
  left: 15px;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  color: var(--color-input-placeholder);
  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,
bank-branch-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-text-muted);
}

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

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

/* Provider card: native <button> with role="checkbox" aria-checked that
   toggles, in both single- and multi-select (single-select is the same
   interaction capped at one bank). 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,
label.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-text-heading);
  border: var(--provider-card-border-thickness) solid transparent;
  border-radius: var(--input-border-radius);
  transition: all 0.3s;
  /* The one border a card draws once it is hovered or chosen. Hover and the
     chosen state share it so a card keeps the same weight as it is picked and
     moves into the selected group, which draws the same border permanently. */
  --provider-card-active-border: color-mix(in srgb,
    var(--color-provider-card-selected-border), transparent 80%);
}

button.item--card[aria-checked="true"],
label.item--card:has(input:checked) {
  color: var(--color-text-heading);
  border-color: var(--provider-card-active-border);
}

@media (hover: hover) {
  button.item--card:hover,
  label.item--card:hover {
    color: var(--color-text-heading);
    border-color: var(--provider-card-active-border);
  }
}

button.item--card:focus-visible,
label.item--card:has(input:focus-visible) {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

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

/* The avatar box is what gives a card its height, so the radio sits in one
   too — centred by the box, with no account-list baseline nudge. */
label.item--card > .logo-avatar > input[type="radio"] {
  margin: 0;
}

button.item--card > span:not(.deselect-provider),
label.item--card > span:not(.logo-avatar) {
  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-provider-card-deselect-icon);
  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-text-heading);
    background: color-mix(in srgb, var(--color-text-heading), transparent 90%);
    color: var(--color-provider-card-deselect-icon);
  }
}

button.item--card.loading {
  pointer-events: none;
  opacity: var(--button-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-input-background-muted) url("/static/icons/padlock.svg") no-repeat
    right var(--space-s) center;
  background-size: 1rem auto;
  padding-right: 2.5rem;
}

.psu-id-input-container label {
  line-height: normal;
  color: var(--color-input-label);
}

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

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

/* A rejected field is marked on the input itself, not only in the message below
   it. Keyed on the aria-invalid the validation already sets, so the colour and
   the state a screen reader announces cannot disagree — and so a server-rendered
   invalid field would be marked without any JS running. */
.psu-id-input-container input[aria-invalid="true"] {
  border-color: var(--color-error);
}

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

.main-container {
  --page-bottom-space: var(--space-s);
  width: 100%;
  max-width: var(--page-max-width);
  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-border);
    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-border);
    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-border);
    color: var(--color-text-muted);
  }

  .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-surface);
  }

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

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

  .active > .step-label {
    color: var(--color-text-heading);
  }

  .completed > .step-label {
    color: var(--color-text-secondary);
  }
}

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

.welcome-hero {
  font-size: var(--font-size-s);
  color: var(--color-text-secondary);
}

/* The intro is markdown, so its paragraphs are generated and carry no class of
   their own. The rule on p above would win over inheriting from .welcome-hero. */
.welcome-hero p {
  color: inherit;
}

.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-text-secondary);
}

.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-border);
  border-radius: 50%;
  background: var(--color-canvas);
  color: var(--color-text-muted);
  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-border);
  /* 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, mark centered in what's left
   -------------------------------------------------------------------------- */

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

redirect-auth-form .button-group,
redirect-auth-form .nav-button-group {
  margin-top: auto;
}

/* The mark absorbs the space left between the copy and the buttons, centering
   itself in it — so the gaps above and below it are whatever the viewport has
   to spare, and they close as the window gets shorter. Same on every width;
   desktop only bounds how far the gaps may open. */
redirect-auth-form .provider-mark {
  /* The drawn mark's own pixel size. The gap bounds derive from it, so scaling
     the mark up keeps them measured from its edge. */
  --provider-mark-size: 100px;

  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: var(--provider-mark-size);
  height: var(--provider-mark-size);
}

@media (min-width: 48rem) {
  /* Block layout on desktop, so the cases without a mark — the waiting state,
     the SEB variant, which has no mark at all — keep their margin rhythm. */
  redirect-auth-form {
    display: block;
  }

  /* With the mark on screen the flex column comes back, so the mark keeps
     absorbing the free height between the copy and the buttons the way it does
     on mobile. */
  redirect-auth-form:has(> .provider-mark:not(.hidden)) {
    display: flex;
  }

  /* Bounding the mark's box bounds the gaps, since it stays centered in
     whatever height the box ends up with: a floor so a short window keeps the
     mark from touching the copy and the buttons, and a ceiling so a tall one
     doesn't strand it mid-page. Between the two the gaps track the free height.
     The floor is deliberately tight — a short window has scrolling to lose. */
  redirect-auth-form:has(> .provider-mark:not(.hidden)) .provider-mark {
    min-block-size: calc(var(--provider-mark-size) + 2 * var(--space-2xs));
    max-block-size: calc(var(--provider-mark-size) + 2.35 * var(--space-5xl));
  }

  /* Past the ceiling the leftover height has to collect somewhere, and below
     the buttons is the only place that keeps both gaps at the maximum — so the
     buttons stop riding the bottom, and the mark's box is left as the only
     thing setting the gap above them. */
  redirect-auth-form:has(> .provider-mark:not(.hidden)) .nav-button-group {
    margin-top: 0;
  }
}

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

.provider-list-subtitle {
  color: var(--color-text-heading);
}

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"],
label.item--card input[type="radio"] {
  width: var(--space-m);
  height: var(--space-m);
  accent-color: var(--color-primary-button-background);
  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-text-muted);
}

.account-balance {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}

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

.rename-label {
  color: var(--color-input-label);
}

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

.rename-input {
  width: 100%;
}

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

/* 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-text-body);
}

.decoupled-steps li::marker {
  color: var(--color-text-body);
}

/* Painted from currentColor through a mask rather than drawn as an image, so
   the mark always matches the label sitting next to it — whatever colour that
   button's text is in a given theme. Hidden by default; contexts that want it
   opt in by setting display. */
.bankid-logo {
  display: none;
  background-color: currentColor;
  mask: url("/static/icons/bank-id-logo.svg") no-repeat center / contain;
  -webkit-mask: url("/static/icons/bank-id-logo.svg") no-repeat center / contain;
}

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

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

/* 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(--color-primary-button-background);
  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-canvas);
  z-index: 2;
}

/* The bank pickers lock 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,
bank-branch-selector .nav-button-group,
bank-branch-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-text-secondary);
}

.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-text-heading);
}

/* --------------------------------------------------------------------------
   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-border);
}

.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-text-muted);
  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-text-muted);
  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-text-secondary);
}

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

a.dynamic-page-back:hover {
  color: var(--color-text-heading);
}

/* --------------------------------------------------------------------------
   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)
}

/* Bye page. The h1/h2 pair already carries the display type the design asks
   for, so this only handles the centring. */
.farewell {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-s);
}

.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-text-secondary);
  font-size: var(--font-size-s);
}

.spinner::before {
  content: "";
  width: 1.25em;
  height: 1.25em;
  border: 2px solid var(--color-border);
  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(--color-primary-button-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(--color-secondary-button-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;
}

/* The mark is an empty span, so it has no intrinsic size to fall back on —
   --icon-size is only defined by the themes that set it, hence the 24px
   default, which is the artwork's own size. */
.auth-actions .bankid-logo {
  display: inline-block;
  width: var(--icon-size, 24px);
  height: var(--icon-size, 24px);
  margin-block: -0.75em;
}

.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;
}

/* Shorter than the filled controls — see --text-button-height in space.css.
   Both box-sizing and display have to be restated: `all: unset` resets them to
   content-box and inline, so without them the height would sit outside the
   padding and have nothing to apply to. (`all` leaves custom properties alone,
   so the token itself still resolves here.)

   The height owns the click target now, so there is no block padding to add on
   top of it. */
.btn-text {
  all: unset;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--text-button-height);
  max-height: var(--text-button-height);
  cursor: pointer;
  color: var(--color-primary-link);
  font-size: var(--font-size-s);
  font-weight: var(--font-weight-button);
}

/* `all: unset` drops the UA focus ring, so put one back for keyboard users. */
.btn-text:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.decoupled-or-divider {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  width: 100%;
  color: var(--color-text-muted);
  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-border);
}

/* --------------------------------------------------------------------------
   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-text-body);
  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-text-body);
  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-surface-sunken);
  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
 *
 * A card surfaced to the user at the top of the page. Toasts stack in a fixed
 * region; each carries a kind (error, info or success) that scopes its palette
 * to local custom properties so the colours stay in one place. SEB keeps its
 * own look in seb.css.
 */
/* Spans the page grid's content column: .main-container's width less the
   grid gutter on each side. */
.toast-region {
  position: fixed;
  top: 46px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  width: calc(min(var(--page-max-width), 100%) - 2 * var(--grid-gap));
  z-index: 1000;
  pointer-events: none;
}

/* Banners: toasts in the page flow below the header. While one shows, it takes
   over the header's gap to the content and sits closer to the header. */
.banners {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin-block-end: var(--space-xl-6xl);
}

.banners:empty {
  display: none;
}

.main-container:has(> .banners:not(:empty)) > .header-container {
  margin-block-end: var(--space-m);
}

.toast {
  --toast-bg: rgba(254, 247, 246, 1);
  --toast-text: rgba(185, 47, 19, 1);
  --toast-border-radius: 5px;

  display: flex;
  align-items: flex-start;
  gap: var(--space-2xs);
  width: 100%;
  /* The border counts towards the 16px inset the design gives the content. */
  padding: calc(var(--space-s) - var(--input-border-thickness));
  background: var(--toast-bg);
  border: var(--input-border-thickness) solid var(--toast-text);
  border-radius: var(--toast-border-radius);
  color: var(--toast-text);
  font-size: var(--font-size-m);
  line-height: 1.17;
  letter-spacing: -0.011em;
  text-align: left;
  opacity: 0;
  transform: translateY(-1rem);
  transition:
    opacity 0.2s,
    transform 0.2s;
  pointer-events: auto;
}

.toast--info {
  --toast-bg: rgba(240, 246, 255, 1);
  --toast-text: rgba(31, 78, 160, 1);
}

.toast--success {
  --toast-bg: #e5f9ee;
  --toast-text: #00562a;
}

/* A message-only info banner, such as the one on a provider the user has
   already identified with, follows the design's info alert: no icon and the
   info surface colours, which no token carries yet. */
.banners .toast--info:not(:has(.toast__title)) {
  --toast-bg: #f5f9fe;
  --toast-text: #116b93;
}

.banners .toast--info:not(:has(.toast__title)) .toast__icon {
  display: none;
}

.toast.visible {
  opacity: 1;
  transform: translateY(0);
}

/* The toast is focused programmatically so assistive tech lands on it, but it
   is tabindex="-1" and not keyboard operable, so the global :focus-visible ring
   in elements.css is noise on it. It only ever showed on the page-load path —
   the toasts queued for the page — because a pointer click
   suppresses :focus-visible everywhere else, which made the same toast look
   different depending on how it was raised. The close button keeps its ring. */
.toast:focus-visible {
  outline: none;
}

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

/* An error carries no icon: its colour already says what it is. SEB keeps
   its warning sign (seb.css). */
.toast--error .toast__icon {
  display: none;
}

.toast__content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  min-width: 0;
  overflow-wrap: break-word;
}

/* Inherit so the spans follow the toast, not the global span size. */
.toast__title,
.toast__description,
.toast__message {
  font-size: inherit;
}

.toast__title {
  font-weight: var(--font-weight-bold);
}

/* pre-line so a description listing one rejected field per bullet renders as
   those lines. Runs of spaces still collapse exactly as normal does, so a
   single-line description — which is every other toast in the flow — is
   unaffected; only the explicit newlines between bullets now survive. The text
   is still set as textContent, never innerHTML: the bullets are characters in
   a string, not markup. */
.toast__description,
.toast__message {
  white-space: pre-line;
}

.toast__close {
  flex-shrink: 0;
  width: 1em;
  height: 1em;
  /* Centres the glyph on the first line of text. */
  margin-top: calc((1lh - 1em) / 2);
  padding: 0;
  background: none;
  border: 0;
  color: var(--toast-text);
  cursor: pointer;
}

/* The glyph is a pseudo-element because a mask on the button itself would
   clip its focus ring. */
.toast__close::before {
  content: "";
  display: block;
  width: 100%;
  height: 100%;
  background-color: currentColor;
  mask: url("/static/icons/close.svg") no-repeat center / contain;
  -webkit-mask: url("/static/icons/close.svg") no-repeat center / contain;
}

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

.callback-link {
  font-size: var(--font-size-xs);
  color: var(--color-input-border);
  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-text-heading);
}

.provider-list-no-matches {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-base);
  color: var(--color-text-body);
}

/* The message sits outside the gap-m stack of groups, so it keeps its own
   distance from the selected banks above it. */
.provider-list-scroll:has(#selected-providers:not([hidden])) .provider-list-no-matches {
  margin-top: var(--space-l);
}

/* Banks already identified with: a plain row of logo and name, nothing to
   press. Wraps when the names outgrow the page. */
.accepted-provider-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
  list-style: none;
}

.accepted-provider {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
}

.accepted-provider > span {
  font-size: var(--font-size-s);
}

/* 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: var(--provider-card-active-border);
}

.organization-logo {
  height: 44px;
  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-text-heading), transparent 40%);
}

.inactivity-dialog {
  background: var(--color-canvas);
  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-text-heading), transparent 70%);
}

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

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

inactivity-timeout {
  display: block;
}

/* ==========================================================================
   Modal dialogs
   ========================================================================== */

.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-canvas);
  color: var(--color-text-body);
  box-shadow: 0 0 20px
    color-mix(in srgb, var(--color-text-heading), 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. */
.modal[open] {
  display: flex;
  flex-direction: column;
}

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

/* Sits inside the dialog's top padding, clear of the content below. */
.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-canvas);
  color: var(--color-text-secondary);
  cursor: pointer;
}

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

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

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

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

/* 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);
}

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

/* ==========================================================================
   Duplicate connection modal
   ========================================================================== */

/* The close button's extent is 48px — its inset, its own padding twice over
   and the 24px icon — which is exactly .modal's own block-start padding, so
   the heading would otherwise start flush against it. --space-6xl clears the
   button by --space-l. */
.duplicate-connection-modal {
  max-width: 30rem;
  padding-block-start: var(--space-6xl);
  text-align: center;
}

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

.duplicate-connection-modal-body p {
  font-size: var(--font-size-s);
}

/* The two answers carry equal weight, so they share the width evenly. They
   stack below the breakpoint instead: --control-height clips a label that
   wraps, and "Choose another bank" wraps in most locales on a phone. */
.modal-actions {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  gap: var(--space-xs);
}

.modal-actions > button {
  width: 100%;
}

@media (min-width: 30rem) {
  .modal-actions {
    flex-direction: row;
  }

  .modal-actions > button {
    flex: 1 1 0;
  }
}

/* ==========================================================================
   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-text-heading), 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-text-heading);
  font-size: var(--font-size-m);
}

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

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