/* ==========================================================================
   OpenDhi auth screens -- layout, components, states, breakpoints
   (OD-SPEC-AUTH-001 v1.2 sections 2.3, 5.2, 5.5, 6.2, 7.2, 9, 14.2; WEB-UI-BRIEF sections 3, 4, 6)

   Mobile-first. Breakpoints: XS < 600 (base), SM >= 600, two-up >= 720, profile columns >= 900,
   LG >= 1024, XL >= 1440. Only var(--od-*) values from tokens.css are used for colour, size,
   radius, elevation and motion. Icons are drawn with borders / currentColor, never raw colours.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Base
   -------------------------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  background: var(--od-canvas);
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--od-canvas);
  color: var(--od-text-primary);
  font-family: var(--od-font);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, h4, p, ul, ol, dl, dt, dd, figure {
  margin: 0;
}

img, svg {
  display: block;
  max-width: 100%;
}

button, input, textarea, select, a {
  font-family: inherit;
}

/* 14.2 focus-visible: 2 px brand ring with 2 px offset, everywhere. */
:focus {
  outline: none;
}

:focus-visible {
  outline: var(--od-focus-ring);
  outline-offset: var(--od-focus-offset);
}

/* Utilities */
.od-visually-hidden,
.od-visually-hidden-focusable:not(:focus):not(:focus-within) {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

.od-skip {
  position: fixed;
  top: var(--od-space-xs);
  left: var(--od-space-xs);
  z-index: 100;
  padding: var(--od-space-sm) var(--od-space-md);
  background: var(--od-surface);
  color: var(--od-brand-primary);
  border-radius: var(--od-radius-badge);
  box-shadow: var(--od-elevation-2);
  text-decoration: none;
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   1. Page shell and the two-panel block
   -------------------------------------------------------------------------- */
.od-shell {
  width: 100%;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

.od-block {
  width: 100%;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  background: var(--od-surface);
}

/* Brand panel.

   XS follows the mobile mockup, which does not shrink the desktop panel but replaces it: a green
   card inset from the edges (radius 14) carrying the logo lock-up, the screen title and its sub-copy.
   The 'light' variant drops the green entirely for the long forms (sign up, edit profile) and puts
   the logo chip on the canvas above a dark title. From SM up both revert to the web mockup's banner. */
.od-brand {
  background: var(--od-brand-primary);
  color: var(--od-brand-on-primary);
  border-radius: var(--od-radius-card);
  margin: var(--od-space-md) var(--od-panel-gutter) 0;
  padding: var(--od-space-lg);
  display: flex;
  align-items: center;
}

.od-brand--mobile-light {
  background: transparent;
  color: var(--od-text-primary);
  border-radius: 0;
  margin: var(--od-space-lg) var(--od-panel-gutter) 0;
  padding: 0;
}

/* The mobile block and the desktop panel content are mutually exclusive; display:none also keeps
   the unused <h1> out of the accessibility tree, so exactly one heading is ever exposed. */
.od-brand__mobile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--od-space-xs);
  width: 100%;
}

.od-brand__mobile-title {
  margin-top: var(--od-space-xxs);
  color: inherit;
}

.od-brand__mobile-copy {
  color: inherit;
  opacity: 0.92;
}

.od-brand--mobile-light .od-brand__mobile-copy {
  color: var(--od-text-secondary);
  opacity: 1;
}

.od-brand__inner {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--od-space-xs);
  min-width: 0;
}

/* XS: the desktop panel's own lock-up and headline stand down for the mobile block above. The
   sub-copy, label and benefit list are already hidden at this width by their base rules. */
@media (max-width: 599.98px) {
  .od-brand__inner {
    gap: var(--od-space-xxs);
  }

  .od-brand__inner > .od-logo,
  .od-brand__headline {
    display: none;
  }

  /* The mobile header already carries the screen title and sub-copy, and the mockup goes straight
     from it to the first field. These are hidden visually rather than with display:none because the
     form's aria-labelledby / aria-describedby point at them and must keep resolving. */
  .od-main__form > .od-form__title,
  .od-main__form > .od-form__subtitle {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
  }
}

/* Spec 3.2 assigns the brand headline the display style (24 / 32, growing to 32 / 38 at LG);
   20 / 26 is not on the scale. The XS strip stays within 120 px through the tighter inner gap below. */
.od-brand__headline {
  color: var(--od-brand-on-primary);
  font-size: 1.5rem;
  line-height: 2rem;
  overflow-wrap: anywhere;
}

.od-brand__headline br {
  display: none;
}

.od-brand__copy {
  color: var(--od-brand-on-primary);
  opacity: 0.92;
  display: none;
}

/* Same rule as the headline: the authored line break applies only where the copy is shown (SM+). */
.od-brand__copy br {
  display: none;
}

.od-brand__label {
  color: var(--od-brand-on-primary);
  opacity: 0.85;
  margin-top: var(--od-space-xs);
  display: none;
}

.od-brand__list {
  list-style: none;
  padding: 0;
  display: none;
  flex-direction: column;
  gap: var(--od-space-xs);
}

.od-brand__item {
  position: relative;
  padding-left: var(--od-space-xl);
  color: var(--od-brand-on-primary);
}

/* Check mark drawn from borders (no raw colours). */
.od-brand__item::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 2px;
  width: 6px;
  height: 11px;
  border: solid currentColor;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
  z-index: 1;                          /* above the disc drawn by ::after in the chip variant */
}

/* Login carries each check inside a translucent disc; sign up uses the bare check above. The two
   frames of the mockup genuinely differ here, so the difference is a modifier rather than a bug. */
.od-brand__list--chips .od-brand__item {
  display: flex;
  align-items: center;
  min-height: 24px;
  padding-left: 36px;
}

.od-brand__list--chips .od-brand__item::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 24px;
  height: 24px;
  margin-top: -12px;
  border-radius: var(--od-radius-pill);
  background: var(--od-brand-chip);
}

.od-brand__list--chips .od-brand__item::before {
  left: 9px;
  top: 50%;
  margin-top: -6px;
  width: 5px;
  height: 9px;
}

/* White panel */
.od-panel {
  background: var(--od-surface);
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--od-space-md) var(--od-panel-gutter) var(--od-space-xl);
}

/* Utility nav: DETAILS · SIGN UP · LOG IN, top-right */
.od-nav {
  display: flex;
  justify-content: flex-end;
}

.od-nav__list {
  list-style: none;
  padding: 0;
  display: flex;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 0 var(--od-space-xs);
}

.od-nav__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--od-target-min);
  padding: 0 var(--od-space-xs);
  color: var(--od-text-secondary);
  text-decoration: none;
  border-radius: var(--od-radius-badge);
  transition: color var(--od-motion-fast);
}

.od-nav__link:hover,
.od-nav__link[aria-current="page"] {
  color: var(--od-brand-primary);
}

/* The mockup draws the utility nav on the LOGIN frame only: on sign up the "Account setup" card
   occupies that corner and on the profile frame the card starts at the top of the white area. Keeping
   it added a 44 px band above both forms that the artboard does not have. Each screen still carries
   its own route in the footer or rail ("Already registered? Log in", "Profile - Privacy - Help"). */
.od-page--signup .od-nav,
.od-page--profile .od-nav,
.od-page--edit .od-nav {
  display: none;
}

/* The mobile frames carry no utility nav: they go from the header straight to the first field and
   rely on the footer for the route that matters per screen ("Create an account" / "Log in" /
   "Profile - Privacy - Help"). Keeping the row would add a block above the form that the mockup does
   not have. This sits after the base rule on purpose -- the XS block higher up is declared before
   `.od-nav` exists, so the same-specificity base would win the cascade. */
@media (max-width: 599.98px) {
  .od-nav {
    display: none;
  }
}

/* Main region. Below LG the content starts directly under the utility nav (the login title must
   not sink below the fold on short phones); the LG split block centres it vertically. */
.od-main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  width: 100%;
  min-width: 0;
  padding: var(--od-space-md) 0 0;
}

.od-main__form {
  width: 100%;
  max-width: var(--od-content-max-sm);
  margin: 0 auto;
  min-width: 0;
}

/* Right rail (sign-up): below LG it flows under the form. */
.od-rail {
  width: 100%;
  max-width: var(--od-content-max-sm);
  margin: var(--od-space-xl) auto 0;
  min-width: 0;
}

/* Footer inside the white panel */
.od-footer {
  width: 100%;
  max-width: var(--od-content-max-sm);
  margin: 0 auto;
  text-align: center;
}

.od-footer--filled {
  padding-top: var(--od-space-xl);
}

.od-footer__text {
  color: var(--od-text-secondary);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0 var(--od-space-xxs);
}

.od-footer__links {
  list-style: none;
  padding: 0;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  align-items: center;
  color: var(--od-text-secondary);
}

.od-footer__links li {
  display: inline-flex;
  align-items: center;
}

/* Chrome exposes generated content to the accessibility tree, so the separator counts as text and
   needs 4.5:1 (14.2): text-secondary is 6.34:1 on white and matches the links it separates. The
   alternative-text syntax additionally drops it from the tree where supported (plain fallback first). */
.od-footer__links li + li::before {
  content: "\00b7";
  content: "\00b7" / "";
  color: var(--od-text-secondary);
  padding: 0 var(--od-space-xxs);
}

.od-footer__links .od-btn--link {
  color: var(--od-text-secondary);
}

/* --------------------------------------------------------------------------
   2. Form structure
   -------------------------------------------------------------------------- */
.od-form {
  display: flex;
  flex-direction: column;
  gap: var(--od-space-md);          /* field -> next label 16 */
  width: 100%;
  min-width: 0;
}

.od-form__title {
  color: var(--od-text-primary);
  margin-bottom: var(--od-space-xxs);
}

.od-form__subtitle {
  color: var(--od-text-secondary);
  margin-bottom: var(--od-space-xl);
}

.od-form__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--od-space-md);
}

/* Pairs that stay side by side on the phone, because the mobile mockup draws them that way:
   First name | Surname and Password | Confirm are two-up there. Email | Phone is NOT -- it collapses
   to one column below 720 px like an ordinary row, which is also what the mockup shows.

   The floor is 360 px, not 0. The mobile frame is drawn at 390 px, where each half is 165 px and
   holds "Minimum 8 characters" and the "CONFIRM PASSWORD" label on one line. At 320 px each half
   would be about 130 px: the placeholders clip and the label wraps to two lines, which is what
   "breaking" looked like. Below 360 px they stack like every other row. */
@media (min-width: 360px) {
  .od-form__row--pair {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--od-space-sm);
  }
}

@media (min-width: 600px) {
  .od-form__row--pair {
    gap: var(--od-space-md);
  }
}

.od-form__options {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--od-space-xxs) var(--od-space-sm);
}

.od-form__actions {
  display: flex;
  flex-direction: column;
  gap: var(--od-space-sm);
}

/* Primary + text link side by side once there is room (SCR-AUTH-07 "SAVE CHANGES" / "Cancel"). */
.od-form__actions--split {
  align-items: stretch;
}

/* Secondary text actions under the primary button ("Back to log in", "Resend code"). */
.od-form__secondary {
  color: var(--od-text-secondary);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0 var(--od-space-xxs);
  margin: 0;
}

.od-form__divider {
  display: flex;
  align-items: center;
  gap: var(--od-space-sm);
  color: var(--od-text-secondary);
}

.od-form__divider::before,
.od-form__divider::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--od-border-default);
}

/* --------------------------------------------------------------------------
   3. od-input (9.1, 5.5)
   -------------------------------------------------------------------------- */
.od-input {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.od-input__label {
  color: var(--od-text-primary);
  margin-bottom: 6px;                /* label -> field 6 */
}

.od-input__control {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 0;
}

.od-input__field {
  width: 100%;
  min-width: 0;
  height: var(--od-size-field);
  padding: 0 var(--od-space-sm);
  border: 1px solid var(--od-border-control);   /* 14.2: control boundary >= 3:1 */
  border-radius: var(--od-radius-field);
  background: var(--od-field-fill);
  color: var(--od-text-primary);
  caret-color: var(--od-brand-primary);
  appearance: none;
  -webkit-appearance: none;
  box-shadow: none;
  transition: border-color var(--od-motion-standard), background-color var(--od-motion-standard), box-shadow var(--od-motion-standard);
}

.od-input__field::placeholder {
  color: var(--od-text-placeholder-web);   /* 14.2: placeholder text >= 4.5:1 on the field fill */
  opacity: 1;
}

/* Focus: 2 px brand border (1 px border + 1 px inset shadow keeps the box size), fill white. */
.od-input__field:focus {
  border-color: var(--od-brand-primary);
  box-shadow: inset 0 0 0 1px var(--od-brand-primary);
  background: var(--od-surface);
}

/* Error: 1.5 px error border. */
.od-input--error .od-input__field,
.od-input__field[aria-invalid="true"] {
  border-color: var(--od-error);
  box-shadow: inset 0 0 0 0.5px var(--od-error);
}

.od-input--error .od-input__field:focus,
.od-input__field[aria-invalid="true"]:focus {
  box-shadow: inset 0 0 0 1px var(--od-error);
}

.od-input__field:disabled {
  background: var(--od-canvas);
  opacity: var(--od-opacity-disabled);
  cursor: not-allowed;
}

.od-input__field[readonly] {
  cursor: default;
}

/* The immutable email on SCR-AUTH-07 is there to be READ, so it keeps full contrast: the 0.38
   disabled opacity above would drop it to about 2.4:1. The muted fill and the helper text carry
   "you cannot change this" instead, and the field is still `disabled` so it is never submitted. */
.od-input--locked .od-input__field:disabled {
  opacity: 1;
  background: var(--od-surface-muted);
  color: var(--od-text-primary);
  -webkit-text-fill-color: var(--od-text-primary);   /* Safari overrides colour on :disabled */
}

.od-input--locked .od-input__label {
  color: var(--od-text-secondary);
}

.od-input--with-trailing .od-input__field {
  padding-right: 72px;
}

.od-input__trailing {
  position: absolute;
  top: 0;
  right: var(--od-space-xxs);
  height: var(--od-size-field);
  display: flex;
  align-items: center;
}

/* The address stays a <textarea> so a genuinely multi-line address can be typed and can grow, but its
   RESTING height follows the mockup, which draws it differently on the two frames:

     phone   two rows -- the placeholder "House number, street, city and postal code" wraps at that
             width, and the mobile frame shows it on two lines.
     desktop one row -- the web frame draws it the same height as the Email and Phone fields beside
             it, with the placeholder on a single line.

   Both keep the same max-height, so dragging the resize handle or typing a long address behaves
   identically either way. */
textarea.od-input__field {
  height: auto;
  min-height: 64px;                   /* 2 lines = 2 x 20 + 2 x 11 + 2 x 1 */
  max-height: 112px;
  padding: 11px var(--od-space-sm);
  resize: vertical;
  line-height: 1.25rem;
}

@media (min-width: 600px) {
  textarea.od-input__field {
    /* `height` and not just `min-height`: the element carries rows="2", which gives it an intrinsic
       height of ~68 px that a min-height cannot shrink. Dragging the resize handle still works, and
       max-height still caps the growth. */
    height: var(--od-size-field);
    min-height: var(--od-size-field);  /* one line, matching the inputs on the same row */
    padding: 13px var(--od-space-sm);  /* 20 line box + 2 x 13 + 2 x 1 = 48 */
  }
}

.od-input--textarea.od-input--grow textarea.od-input__field {
  min-height: 112px;
}

/* The hint is the aria-describedby target and must stay in the DOM while empty: reserve its caption
   line box (12 / 16) so the password-strength helper appearing causes no layout shift (RUL / 6.5). */
.od-input__hint {
  min-height: 1rem;
  margin-top: var(--od-space-xxs);
  color: var(--od-text-secondary);
}

.od-input__hint--weak {
  color: var(--od-warning-text);   /* 12 px text: 6.0:1 (feedback.warning itself is 4.2:1) */
}

.od-input__hint--ok,
.od-input__hint--strong {
  color: var(--od-success);
}

/* Error text: icon + text, 12 / error colour, 4 px below the field. */
.od-input__error {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin-top: var(--od-space-xxs);
  color: var(--od-error);
}

.od-input__error[hidden] {
  display: none;
}

/* The "!" is decorative (the text carries the meaning): the alternative-text syntax keeps it out of
   the accessibility tree where supported; older engines fall back to the plain declaration. */
.od-input__error::before {
  content: "!";
  content: "!" / "";
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
  margin-top: 1px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  font-size: 10px;
  line-height: 11px;
  font-weight: 700;
  text-align: center;
}

/* SHOW / HIDE text toggle (11 / 700 brand) inside the password field. Specificity (0,2,0) so the
   brand colour wins over the later `.od-btn { color: inherit }` base rule. */
.od-btn.od-btn--toggle,
.od-btn.od-input__toggle {
  min-width: var(--od-target-min);
  min-height: var(--od-target-min);
  padding: 0 var(--od-space-xs);
  border: 0;
  background: transparent;
  color: var(--od-brand-primary);
  font-size: 0.6875rem;
  line-height: 0.875rem;
  font-weight: 700;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  border-radius: var(--od-radius-badge);
  cursor: pointer;
}

/* --------------------------------------------------------------------------
   4. Buttons (9.2)
   -------------------------------------------------------------------------- */
.od-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--od-space-xs);
  min-height: var(--od-target-min);
  padding: 0;
  border: 0;
  border-radius: var(--od-radius-field);
  background: transparent;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--od-motion-fast), color var(--od-motion-fast), opacity var(--od-motion-fast);
}

.od-btn--full {
  width: 100%;
}

.od-btn__label {
  display: inline-block;
}

/* A primary action never wraps: its label is a fixed phrase and a two-line button changes the
   control height, which is what "CREATE MY ACCOUNT" did when the rail was too narrow for it. If a
   label ever does outgrow its button this makes the overflow visible rather than silently reflowing
   the layout. */
.od-btn--primary .od-btn__label {
  white-space: nowrap;
}

/* The ticking number inside a holdButton label ("Resend code in 12 seconds."). aria-live="off" in
   the markup keeps it out of the announcement; tabular figures stop the label jittering as it counts. */
.od-btn__count {
  font-variant-numeric: tabular-nums;
}

/* A control waiting out a resend window is genuinely disabled, so it needs the muted treatment
   whatever its variant. */
.od-btn[data-od-hold] {
  opacity: var(--od-opacity-disabled-label);
  cursor: default;
}

.od-btn__icon {
  width: var(--od-size-icon);
  height: var(--od-size-icon);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}

/* Primary. min-height (not height) + vertical padding so the label still fits its box when the
   browser's default font size is raised instead of zoomed; 48 px at the 16 px root (14.2). */
.od-btn--primary {
  min-height: var(--od-size-button);
  padding: var(--od-space-sm) var(--od-space-md);
  background: var(--od-brand-primary);
  color: var(--od-brand-on-primary);
}

.od-btn--primary:hover,
.od-btn--primary:active {
  background: var(--od-brand-pressed);
}

.od-btn--primary:disabled {
  opacity: var(--od-opacity-disabled);
  cursor: default;
  pointer-events: none;
}

/* aria-disabled is the "inert but still reachable" state (SCR-AUTH-07 SAVE CHANGES, RUL-07-01):
   it reads as disabled and keeps its place in the tab order, so it must stay clickable and
   focusable -- pointer-events: none here would swallow the click that explains why nothing
   happened. The handler is what refuses the submit. */
.od-btn--primary[aria-disabled="true"] {
  opacity: var(--od-opacity-disabled);
  cursor: default;
}

.od-btn--primary.od-btn--loading,
.od-btn--primary.od-btn--loading:disabled {
  opacity: 1;
  pointer-events: none;
  background: var(--od-brand-primary);
}

.od-btn--loading .od-btn__label,
.od-btn--loading .od-btn__icon {
  visibility: hidden;
}

.od-btn__spinner {
  display: none;
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--od-size-icon);
  height: var(--od-size-icon);
  margin: -10px 0 0 -10px;
  border: 2px solid var(--od-brand-on-primary);
  border-right-color: transparent;
  border-radius: 50%;
  animation: od-spin var(--od-motion-loading) infinite;
}

.od-btn--loading .od-btn__spinner {
  display: block;
}

/* Social: white, 1 px border, text-primary, leading 20 px mark, never uppercase. */
.od-btn--social {
  height: var(--od-size-button);
  padding: 0 var(--od-space-md);
  background: var(--od-surface);
  border: 1px solid var(--od-border-control);   /* 14.2: control boundary >= 3:1 (white on white) */
  color: var(--od-text-primary);
  text-transform: none;
}

.od-btn--social:hover,
.od-btn--social:active {
  background: var(--od-canvas);
}

/* Google Identity Services overlay (E-02).

   GIS opens its account chooser only from the button it renders itself, so that button is laid over
   ours at zero opacity and takes the press. Ours stays the one the person sees. The overlay is only
   ever populated when GIS loaded and a client id is configured -- otherwise it is an empty box with
   no pointer target, and the visible button keeps its own click handler and explains the failure. */
.od-social {
  position: relative;
  width: 100%;
}

.od-social__gis {
  position: absolute;
  inset: 0;
  opacity: 0;
  overflow: hidden;
  /* The rendered button is centred over ours so its hit area covers the same rectangle. */
  display: flex;
  align-items: center;
  justify-content: center;
}

.od-social__gis:empty {
  pointer-events: none;
}

/* The visible button carries the focus ring for the invisible one that actually holds focus. */
.od-social:focus-within .od-btn--social {
  outline: var(--od-focus-ring);
  outline-offset: var(--od-focus-offset);
}

/* While the token is being exchanged the overlay must not accept a second press. */
.od-social--busy .od-social__gis {
  pointer-events: none;
}

/* Text link: body-strong brand, underline on hover / focus-visible, 44 px target. */
.od-btn--link {
  color: var(--od-brand-primary);
  padding: 0 var(--od-space-xxs);
  min-width: var(--od-target-min);    /* short links ("Help") still meet 44 x 44 */
  min-height: var(--od-target-min);
  border-radius: var(--od-radius-badge);
}

.od-btn--link:hover,
.od-btn--link:focus-visible {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Inline link inside running text (consent sentence). Hit area is extended without moving the layout.
   An inline box adds the padding to the font's content area (~17 px at 14 px), not to the 20 px
   line-height, so 16 px each side is what actually reaches the 44 px target (16 + 17 + 16 = 49). */
.od-link {
  color: var(--od-brand-primary);
  font-weight: 600;
  text-decoration: none;
  padding: var(--od-space-md) 0;
  margin: calc(-1 * var(--od-space-md)) 0;
  border-radius: var(--od-radius-badge);
}

/* Inside a checkbox label the enlarged box would be painted over the row's own padding strips and
   steal the checkbox's pointer target (WCAG 2.5.8 exempts a link inside a sentence of text), so the
   consent links keep their inline box and the whole row stays a checkbox target. */
.od-checkbox__label .od-link {
  padding: 0;
  margin: 0;
}

.od-link:hover,
.od-link:focus-visible {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Icon button: 44 x 44 target, 20 px icon in brand. */
.od-btn--icon {
  width: var(--od-target-min);
  height: var(--od-target-min);
  min-height: var(--od-target-min);
  flex: 0 0 var(--od-target-min);
  color: var(--od-brand-primary);
}

.od-btn--icon:hover,
.od-btn--icon:active {
  background: var(--od-surface-muted);
}

.od-btn--icon svg {
  width: var(--od-size-icon);
  height: var(--od-size-icon);
}

/* --------------------------------------------------------------------------
   5. od-checkbox (9.3)
   -------------------------------------------------------------------------- */
.od-checkbox {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.od-checkbox__row {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-height: var(--od-target-min);
  padding: var(--od-space-sm) 0;
  cursor: pointer;
}

.od-checkbox__input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
  pointer-events: none;
}

.od-checkbox__box {
  position: relative;
  flex: 0 0 16px;
  width: 16px;
  height: 16px;
  margin-top: 2px;
  border: 1px solid var(--od-border-control);   /* the box is the only affordance: >= 3:1 */
  border-radius: 4px;
  background: var(--od-surface);
  transition: background-color var(--od-motion-fast), border-color var(--od-motion-fast);
}

.od-checkbox__input:checked + .od-checkbox__box {
  background: var(--od-brand-primary);
  border-color: var(--od-brand-primary);
}

.od-checkbox__input:checked + .od-checkbox__box::after {
  content: "";
  position: absolute;
  left: 4.5px;
  top: 1px;
  width: 4px;
  height: 8px;
  border: solid var(--od-brand-on-primary);
  border-width: 0 1.6px 1.6px 0;
  transform: rotate(45deg);
}

.od-checkbox__input:focus-visible + .od-checkbox__box {
  outline: var(--od-focus-ring);
  outline-offset: var(--od-focus-offset);
}

.od-checkbox__input:disabled + .od-checkbox__box {
  opacity: var(--od-opacity-disabled);
}

.od-checkbox--error .od-checkbox__box {
  border: 1.5px solid var(--od-error);
}

.od-checkbox__label {
  color: var(--od-text-primary);
  min-width: 0;
}

.od-checkbox .od-input__error {
  margin-top: 0;
}

/* --------------------------------------------------------------------------
   6. od-captcha (9.4)
   -------------------------------------------------------------------------- */
.od-captcha__row {
  display: flex;
  align-items: center;
  gap: var(--od-space-xs);
  min-width: 0;
}

.od-captcha__chip {
  flex: 0 0 44%;
  max-width: 44%;
  height: var(--od-size-field);
  padding: 0 var(--od-space-xs);
  background: var(--od-surface-muted);
  border-radius: var(--od-radius-field);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  user-select: none;
  -webkit-user-select: none;
  color: var(--od-text-primary);
  font-size: 1rem;                   /* 9.4: challenge characters 16 / 700, tracking +2.6 */
  font-weight: 700;
  letter-spacing: 2.6px;
}

.od-captcha__img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 40px;
  transition: opacity var(--od-motion-emphasised);
}

/* No challenge loaded (first paint, or the fetch failed): hide the empty <img> so the browser does
   not draw a broken-image glyph; signup.js reveals the text fallback after a failure. */
.od-captcha__img:not([src]) {
  display: none;
}

/* The fallback is body copy, not a challenge glyph: it carries od-caption (12 / 16 / 400) from the
   type scale and only resets the chip's tracking. */
.od-captcha__fallback {
  letter-spacing: 0;
  color: var(--od-text-secondary);
  text-align: center;
}

.od-captcha__fallback[hidden] {
  display: none;
}

.od-captcha__control {
  flex: 1 1 auto;
  min-width: 0;
}

.od-captcha__input {
  text-transform: uppercase;
  letter-spacing: 1.5px;
}

/* Only the typed code is uppercased / spaced; the placeholder keeps the mockup's "Enter code". */
.od-captcha__input::placeholder {
  text-transform: none;
  letter-spacing: 0;
}

.od-captcha__refresh {
  flex: 0 0 var(--od-target-min);
}

.od-captcha__refresh-icon {
  transition: transform var(--od-motion-emphasised);
}

.od-captcha__refresh--spin .od-captcha__refresh-icon {
  animation: od-rotate-once 300ms ease-in-out;
}

/* XS: at 320 px the row is 272 px wide, so a 44 % chip plus the 44 px refresh button left the input
   92 px -- too narrow for the mockup placeholder "Enter code". Give the chip less and the input
   tighter gutters; the challenge SVG still scales inside the chip. */
@media (max-width: 599.98px) {
  .od-captcha__chip {
    flex: 0 0 38%;
    max-width: 38%;
  }

  .od-captcha__input {
    padding-left: var(--od-space-xs);
    padding-right: var(--od-space-xs);
  }
}

/* --------------------------------------------------------------------------
   7. od-stepper (9.5) -- vertical rail; XS becomes the 3-dot indicator
   -------------------------------------------------------------------------- */
.od-stepper {
  list-style: none;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.od-stepper__step {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 20px;
}

.od-stepper__step + .od-stepper__step {
  margin-top: var(--od-space-md);    /* 16 px connector */
}

/* 9.5: the 16 px connector sits in the 16 px gap between the rows, but the 15 px circle is centred in
   a 20 px row, so it must also bridge the 2.5 px half-gap at each end to actually touch both circles. */
.od-stepper__step + .od-stepper__step::before {
  content: "";
  position: absolute;
  left: 7px;
  top: calc(-1 * var(--od-space-md) - 2.5px);
  width: 1px;
  height: calc(var(--od-space-md) + 5px);
  background: var(--od-border-default);
}

.od-stepper__num {
  flex: 0 0 15px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: var(--od-surface-muted);
  color: var(--od-text-secondary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;                    /* size / tracking come from od-micro on the element (type scale) */
  font-weight: 700;
}

.od-stepper__label {
  color: var(--od-text-secondary);
}

.od-stepper__step--active .od-stepper__num {
  background: var(--od-brand-primary);
  color: var(--od-brand-on-primary);
}

.od-stepper__step--active .od-stepper__label {
  color: var(--od-text-primary);
}

/* XS: horizontal 3-dot indicator (labels stay in the accessibility tree). */
@media (max-width: 599.98px) {
  .od-stepper {
    flex-direction: row;
    justify-content: center;
    gap: var(--od-space-xs);
  }

  .od-stepper__step + .od-stepper__step {
    margin-top: 0;
  }

  .od-stepper__step + .od-stepper__step::before {
    display: none;
  }

  .od-stepper__num {
    flex-basis: 10px;
    width: 10px;
    height: 10px;
    font-size: 0;
  }

  .od-stepper__label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }
}

/* --------------------------------------------------------------------------
   8. od-banner (9.5)
   -------------------------------------------------------------------------- */
.od-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--od-space-xs);
  width: 100%;
  padding: 10px var(--od-space-sm);
  border-radius: var(--od-radius-block);
  margin-bottom: var(--od-space-md);
  color: var(--od-text-primary);
}

.od-banner[hidden] {
  display: none;
}

.od-banner--error {
  background: var(--od-banner-error-bg);
}

.od-banner--error .od-banner__icon {
  color: var(--od-error);
}

.od-banner--success {
  background: var(--od-banner-success-bg);
}

.od-banner--success .od-banner__icon {
  color: var(--od-success);
}

.od-banner__icon {
  flex: 0 0 var(--od-size-icon);
  width: var(--od-size-icon);
  height: var(--od-size-icon);
  border-radius: 50%;
  border: 1.5px solid currentColor;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
}

.od-banner--error .od-banner__icon::before {
  content: "!";
}

.od-banner--success .od-banner__icon::before {
  content: "";
  width: 4px;
  height: 8px;
  margin-top: -2px;
  border: solid currentColor;
  border-width: 0 1.6px 1.6px 0;
  transform: rotate(45deg);
}

.od-banner__text {
  flex: 1 1 auto;
  min-width: 0;
  padding-top: 2px;
  color: var(--od-text-primary);
  overflow-wrap: anywhere;
}

.od-banner__dismiss {
  flex: 0 0 auto;
  width: var(--od-target-min);
  height: var(--od-target-min);
  margin: -12px -12px -12px 0;
  border: 0;
  background: transparent;
  color: var(--od-text-secondary);
  border-radius: var(--od-radius-badge);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

/* Inline error with Retry (profile fetch failure, RUL-03-04). */
.od-inline-error {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--od-space-sm);
  padding: var(--od-space-md) 0;
}

.od-inline-error__text {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  color: var(--od-error);
}

.od-inline-error__text::before {
  content: "!";
  content: "!" / "";
  flex: 0 0 16px;
  width: 16px;
  height: 16px;
  margin-top: 2px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  font-size: 10px;
  line-height: 13px;
  font-weight: 700;
  text-align: center;
}

/* --------------------------------------------------------------------------
   9. od-card (9.5), rail card
   -------------------------------------------------------------------------- */
.od-card {
  background: var(--od-surface);
  border-radius: var(--od-radius-card);
  box-shadow: var(--od-elevation-3);
  padding: var(--od-space-md);
  min-width: 0;
}

.od-card__title {
  color: var(--od-text-primary);
  margin-bottom: var(--od-space-md);
}

.od-card__actions {
  margin-top: var(--od-space-xl);
  display: flex;
  flex-direction: column;
  gap: var(--od-space-sm);
}

.od-card__note {
  color: var(--od-text-secondary);
  text-align: center;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 0 var(--od-space-xxs);
}

.od-card--rail {
  background: var(--od-canvas);
  border: 1px solid var(--od-border-default);
  border-radius: var(--od-radius-field);
  box-shadow: none;
}

/* The phone frame has no "Account setup" card at all: the mockup goes from the consent checkbox
   straight to CREATE MY ACCOUNT and then "Already registered? Log in". The rail's chrome -- its
   surface, heading and stepper -- is therefore dropped below 600 px and only the actions remain,
   sitting directly on the page like any other submit button. */
/* Frame 03 on the phone is brand green edge to edge with the white details card floating on it, so
   the header needs no card of its own and the footer links sit on green. */
@media (max-width: 599.98px) {
  .od-page--profile,
  .od-page--profile .od-block,
  .od-page--profile .od-panel {
    background: var(--od-brand-primary);
  }

  .od-page--profile .od-brand {
    background: transparent;
    border-radius: 0;
    margin-bottom: 0;
    padding-bottom: 0;
  }

  .od-page--profile .od-footer,
  .od-page--profile .od-footer .od-btn--link,
  .od-page--profile .od-footer__links,
  /* The separator sets its own colour, so inheriting from .od-footer__links never reaches it: on the
     green canvas it stayed text-secondary and disappeared, leaving the links running together. */
  .od-page--profile .od-footer__links li + li::before {
    color: var(--od-brand-on-primary);
  }
}

@media (max-width: 599.98px) {
  .od-card--rail {
    background: transparent;
    border: 0;
    border-radius: 0;
    padding: 0;
  }

  .od-card--rail .od-card__title,
  .od-card--rail .od-stepper {
    display: none;
  }

  .od-card--rail .od-card__actions {
    margin-top: 0;
  }
}

/* --------------------------------------------------------------------------
   10. od-avatar, profile card, skeleton (7.2, 7.3, RUL-03-03)
   -------------------------------------------------------------------------- */
.od-avatar {
  flex: 0 0 var(--od-size-avatar-web);
  width: var(--od-size-avatar-web);
  height: var(--od-size-avatar-web);
  border-radius: var(--od-radius-pill);
  background: var(--od-surface-muted);
  color: var(--od-brand-primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-size: 1.0625rem;              /* initials in h2 (17 / 600), spec 7.3 / 9.5 */
  line-height: 1;
  font-weight: 600;
  letter-spacing: 0;
}

.od-avatar--sm {
  flex-basis: var(--od-size-avatar-sm);
  width: var(--od-size-avatar-sm);
  height: var(--od-size-avatar-sm);
  font-size: 1.0625rem;
}

.od-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.od-divider {
  border: 0;
  border-top: 1px solid var(--od-border-default);
  margin: var(--od-space-md) 0;
  width: 100%;
}

.od-profile {
  width: 100%;
}

.od-profile__identity {
  display: flex;
  align-items: center;
  gap: var(--od-space-sm);
  min-width: 0;
}

.od-profile__who {
  min-width: 0;
  flex: 1 1 auto;
}

.od-profile__name {
  color: var(--od-text-primary);
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.od-profile__badge {
  color: var(--od-text-secondary);
  margin-top: 2px;
}

.od-profile__badge--pending {
  color: var(--od-warning-text);   /* 12 px text: 6.0:1 */
}

/* Details: a <dl> of label / value pairs plus the EDIT DETAILS action. Below 900 everything stacks;
   from 900 the dl becomes three columns and the action sits right of it on the same baseline. */
.od-profile__details {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--od-space-md);
  align-items: start;
}

.od-profile__list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--od-space-md);
  align-items: start;
  min-width: 0;
}

.od-profile__field {
  min-width: 0;
}

.od-profile__label {
  color: var(--od-text-secondary);
  margin-bottom: var(--od-space-xxs);
}

.od-profile__value {
  color: var(--od-text-primary);
  min-height: 1.25rem;
  overflow-wrap: anywhere;
}

.od-profile__value--multiline {
  white-space: pre-line;
}

/* "Not provided" is content, not a placeholder: text-secondary (6.3:1) + italic keeps it de-emphasised. */
.od-profile__value--empty {
  color: var(--od-text-secondary);
  font-style: italic;
}

.od-profile__action {
  min-width: 0;
}

.od-profile__action .od-btn {
  width: 100%;
}

.od-profile__note {
  color: var(--od-text-secondary);
  margin-top: var(--od-space-md);
}

/* Skeleton rows at final heights: bar + vertical margin = the text line height it replaces. */
.od-skeleton {
  display: block;
  position: relative;
  overflow: hidden;
  width: 100%;
  background: var(--od-surface-muted);
  border-radius: 6px;
}

.od-skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, var(--od-surface), transparent);
  opacity: 0.7;
  animation: od-shimmer 1200ms linear infinite;
}

.od-skeleton--body    { height: 12px; margin: 4px 0; }       /* 20 */
.od-skeleton--caption { height: 10px; margin: 3px 0; }       /* 16 */
.od-skeleton--label   { height: 8px;  margin: 3px 0; }       /* 14 */
.od-skeleton--h2      { height: 14px; margin: 5px 0; }       /* 24 */
.od-skeleton--h1      { height: 16px; margin: 6px 0; }       /* 28 */
.od-skeleton--avatar  { width: 100%; height: 100%; border-radius: var(--od-radius-pill); margin: 0; }

.od-skeleton--w-40  { width: 40%; }
.od-skeleton--w-60  { width: 60%; }
.od-skeleton--w-80  { width: 80%; }
.od-skeleton--w-100 { width: 100%; }

/* --------------------------------------------------------------------------
   11. od-logo
   -------------------------------------------------------------------------- */
/* Size is set by a modifier class, never an inline style: the CSP is style-src 'self' with no
   'unsafe-inline', so a style attribute is dropped and the custom property would never land. */
.od-logo--md { --od-logo-size: 40px; }
.od-logo--lg { --od-logo-size: 52px; }

.od-logo {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: inherit;
  text-decoration: none;
  width: max-content;
  max-width: 100%;
}

/* A squircle, not a rounded rectangle: the mockup's chip corners are ~26 % of the edge. */
.od-logo__badge {
  flex: 0 0 var(--od-logo-size, 32px);
  width: var(--od-logo-size, 32px);
  height: var(--od-logo-size, 32px);
  border-radius: calc(var(--od-logo-size, 32px) * 0.26);
  background: var(--od-surface);
  box-shadow: var(--od-elevation-1);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* The mark is the supplied PNG; the chip only frames it. Sized from the chip rather than left to the
   width/height attributes: the base `img { max-width: 100% }` plus a flex parent with no definite
   width collapsed it to 0. */
.od-logo__mark {
  display: block;
  flex: 0 0 auto;
  width: calc(var(--od-logo-size, 32px) * 0.62);
  height: calc(var(--od-logo-size, 32px) * 0.62);
}

.od-logo__wordmark {
  font-size: 1rem;
  line-height: 1.25rem;
  font-weight: 800;
  letter-spacing: 0.2px;              /* mixed-case "OpenDhi" as the mockup sets it */
  color: var(--od-brand-on-primary);
}

/* On the light mobile header the chip sits on the muted surface, and the wordmark goes dark. */
.od-logo--on-muted .od-logo__badge {
  background: var(--od-surface-muted);
  box-shadow: none;
}

.od-logo--on-muted .od-logo__wordmark,
.od-logo--on-surface .od-logo__wordmark {
  color: var(--od-text-primary);
}

.od-logo--on-surface .od-logo__badge {
  background: var(--od-surface);
}

/* --------------------------------------------------------------------------
   12. od-otp -- the six-digit code boxes (8.2 FLD-OTP)

   48 x 48, radius 10, gap 8, centred. The boxes shrink with the viewport through a clamp so six of
   them plus the gaps always fit inside the 320 px gutter without a horizontal scrollbar.
   -------------------------------------------------------------------------- */
.od-otp {
  --od-otp-box: clamp(40px, calc((100% - (5 * var(--od-space-xs))) / 6), var(--od-size-field));
}

.od-otp__label {
  text-align: center;
}

.od-otp__boxes {
  display: flex;
  justify-content: center;
  gap: var(--od-space-xs);
  min-width: 0;
}

.od-otp__box {
  width: var(--od-otp-box);
  height: var(--od-size-field);
  min-width: 0;
  padding: 0;
  text-align: center;
  border: 1px solid var(--od-border-control);
  border-radius: var(--od-radius-field);
  background: var(--od-field-fill);
  color: var(--od-text-primary);
  caret-color: var(--od-brand-primary);
  appearance: none;
  -webkit-appearance: none;
  transition: border-color var(--od-motion-standard), background-color var(--od-motion-standard), box-shadow var(--od-motion-standard);
}

.od-otp__box:focus {
  border-color: var(--od-brand-primary);
  box-shadow: inset 0 0 0 1px var(--od-brand-primary);
  background: var(--od-surface);
  outline: var(--od-focus-ring);
  outline-offset: var(--od-focus-offset);
}

.od-otp__box[aria-invalid="true"] {
  border-color: var(--od-error);
  box-shadow: inset 0 0 0 0.5px var(--od-error);
}

/* In flight. A muted fill, not opacity: the digits the user just typed are still on screen and
   fading them to 60% would drop them under the 4.5:1 floor (14.2). */
.od-otp__box[readonly] {
  cursor: default;
  background: var(--od-surface-muted);
}

.od-otp__hint {
  text-align: center;
}

/* .od-input__error is display:flex, so text-align does nothing to it -- the row has to be centred
   on the main axis instead. */
.od-otp__error {
  justify-content: center;
  text-align: center;
}

/* --------------------------------------------------------------------------
   13. od-segmented -- the SMS / Email choice (8.1 FLD-RESET-CHANNEL)

   Real radios, visually hidden; the <label> is the pill and carries the whole 48 px target.
   -------------------------------------------------------------------------- */
.od-segmented {
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.od-segmented__legend {
  padding: 0;
  margin-bottom: 6px;
  color: var(--od-text-primary);
}

/* No inner padding: the pill has to be the full 48 px field height so the touch target clears the
   44 px minimum (14.2). Padding here would eat into it. */
.od-segmented__track {
  display: flex;
  border: 1px solid var(--od-border-control);
  border-radius: var(--od-radius-field);
  background: var(--od-field-fill);
}

/* The end pills carry the track's rounding themselves. `overflow: hidden` on the track would do the
   same job but would also clip the pill's focus ring, which is drawn outside its box. */
.od-segmented__option:first-child .od-segmented__pill {
  border-top-left-radius: calc(var(--od-radius-field) - 1px);
  border-bottom-left-radius: calc(var(--od-radius-field) - 1px);
}

.od-segmented__option:last-child .od-segmented__pill {
  border-top-right-radius: calc(var(--od-radius-field) - 1px);
  border-bottom-right-radius: calc(var(--od-radius-field) - 1px);
}

.od-segmented__option {
  flex: 1 1 0;
  min-width: 0;
}

.od-segmented__pill {
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--od-size-field);
  padding: 0 var(--od-space-sm);
  color: var(--od-text-secondary);
  cursor: pointer;
  text-align: center;
  transition: background-color var(--od-motion-fast), color var(--od-motion-fast);
}

/* A divider between the options, drawn on the second one onwards so it never sits on an edge. */
.od-segmented__option + .od-segmented__option .od-segmented__pill {
  border-left: 1px solid var(--od-border-default);
}

.od-segmented__pill:hover {
  background: var(--od-surface-muted);
}

.od-segmented__input:checked + .od-segmented__pill {
  background: var(--od-brand-primary);
  color: var(--od-brand-on-primary);
}

/* The radio is visually hidden, so its focus ring has to be drawn on the pill it controls. */
.od-segmented__input:focus-visible + .od-segmented__pill {
  outline: var(--od-focus-ring);
  outline-offset: var(--od-focus-offset);
}

/* --------------------------------------------------------------------------
   14. od-dialog -- two-choice confirmation (8.4 RUL-07-03)
   -------------------------------------------------------------------------- */
.od-dialog {
  width: 100%;
  max-width: 420px;
  padding: 0;
  border: 0;
  border-radius: var(--od-radius-card);
  background: transparent;
  color: var(--od-text-primary);
  overflow: visible;
}

.od-dialog::backdrop {
  background: var(--od-scrim);
}

.od-dialog__panel {
  padding: var(--od-space-xl);
  border-radius: var(--od-radius-card);
  background: var(--od-surface);
  box-shadow: var(--od-elevation-3);
}

.od-dialog__title {
  margin-bottom: var(--od-space-xs);
}

.od-dialog__body {
  color: var(--od-text-secondary);
  margin-bottom: var(--od-space-xl);
}

.od-dialog__actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--od-space-sm);
}

.od-dialog__confirm {
  align-self: center;
  color: var(--od-error);
}

/* --------------------------------------------------------------------------
   15. Keyframes
   -------------------------------------------------------------------------- */
@keyframes od-spin {
  to { transform: rotate(360deg); }
}

@keyframes od-rotate-once {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

@keyframes od-shimmer {
  to { transform: translateX(100%); }
}

/* --------------------------------------------------------------------------
   16. Breakpoints
   -------------------------------------------------------------------------- */

/* SM >= 600: brand becomes a 180 px top banner; content max 560 centred. */
@media (min-width: 600px) {
  /* Back to the web mockup's full-bleed banner: the inset card and the mobile block are XS only. */
  .od-brand,
  .od-brand--mobile-light {
    background: var(--od-brand-primary);
    color: var(--od-brand-on-primary);
    border-radius: 0;
    margin: 0;
    min-height: var(--od-brand-banner-sm);
    padding: var(--od-space-lg) var(--od-panel-gutter);
  }

  .od-brand__mobile {
    display: none;
  }

  .od-brand__inner {
    max-width: var(--od-content-max-sm);
    margin: 0 auto;
  }

  .od-brand__headline br {
    display: inline;
  }

  .od-brand__copy {
    display: block;
  }

  .od-brand__copy br {
    display: inline;
  }

  .od-panel {
    padding: var(--od-space-md) var(--od-panel-gutter) var(--od-space-xxl);
  }

  .od-nav__list {
    max-width: var(--od-content-max-sm);
    margin: 0 auto;
    width: 100%;
  }
}

/* Two-up field pairs from 720 px. */
@media (min-width: 720px) {
  .od-form__row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* Primary and its text link sit on one line once the panel is wide enough for both. */
  .od-form__actions--split {
    flex-direction: row;
    align-items: center;
    gap: var(--od-space-md);
  }

  .od-form__actions--split .od-btn--primary {
    width: auto;
    min-width: 200px;
  }
}

/* Profile detail columns from 900 px: three columns + EDIT DETAILS on the same baseline.
   Email gets the largest share so an ordinary address stays on one line (mockup frame 03);
   the phone column never drops below its label's width so "PHONE NUMBER" cannot wrap. */
@media (min-width: 900px) {
  .od-profile__details {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--od-space-lg);
    align-items: start;
  }

  /* Mockup frame 03 keeps every value on one line. The phone is a bounded token (E.164 national
     form), so its track floors at min-content and the value never wraps; the email track stays on a
     pure fr share -- widened below -- so an unusually long address degrades by breaking inside the
     email instead of collapsing the address column or forcing the page to scroll. */
  .od-profile__list {
    grid-template-columns: minmax(0, 1.8fr) minmax(min-content, 1fr) minmax(0, 1.2fr);
    gap: var(--od-space-lg);
  }

  .od-profile__field:not(.od-profile__field--address) .od-profile__value {
    overflow-wrap: break-word;
  }

  .od-profile__value--phone {
    white-space: nowrap;
  }

  .od-profile__label {
    white-space: nowrap;
  }

  .od-profile__action {
    padding-top: 18px;              /* label 14 + gap 4: button top meets the value line */
    justify-self: end;
  }

  .od-profile__action .od-btn {
    width: auto;
    min-width: 140px;
  }

  /* The card must lose the 560 px cap at the same breakpoint the three columns appear, otherwise the
     columns + the 140 px action are squeezed into 560 px and the values wrap mid-token. */
  .od-main--profile .od-main__form,
  .od-main--profile + .od-footer {
    max-width: none;
  }
}

/* LG >= 1024: split block 37 / 63 (profile 31 / 69), radius 8, elevation-2, max 1120 centred. */
@media (min-width: 1024px) {
  /* The artboard sets a tighter rhythm between field groups than the 16 px used on the phone. */
  .od-form {
    gap: var(--od-space-sm);
  }

  .od-shell {
    align-items: center;
    justify-content: center;
    padding: var(--od-space-section) var(--od-space-xl);
  }

  .od-block {
    flex: 0 0 auto;
    max-width: var(--od-block-max);
    display: grid;
    grid-template-columns: var(--od-brand-share-auth) minmax(0, 1fr);
    border-radius: var(--od-radius-block);
    box-shadow: var(--od-elevation-2);
    overflow: hidden;
    /* No min-height: the mockup sizes this card to its content and floats it on the canvas.
       Forcing 600 px stretched the login card to nearly twice the height it needs and pushed both
       panels' content into the middle of a lot of empty space. */
  }

  .od-block--profile {
    grid-template-columns: var(--od-brand-share-profile) minmax(0, 1fr);
    min-height: 0;
  }

  .od-brand {
    min-height: 0;
    padding: var(--od-space-section) var(--od-panel-gutter);   /* GBL-04: 24 px web panel gutter */
    align-items: stretch;
  }

  .od-brand__inner {
    max-width: none;
    margin: 0;
    gap: var(--od-space-md);
    /* Top-aligned, as the mockup draws it: on sign up the green panel is as tall as the form and
       the lock-up sits at its top, not floating in the middle. */
    justify-content: flex-start;
  }

  /* Brand display headline 32 / 38 at LG (spec 3.2, brief section 2). Declared here because this
     stylesheet loads after tokens.css and the SM rule above would otherwise win. */
  .od-brand__headline {
    font-size: 2rem;
    line-height: 2.375rem;
  }

  .od-brand__inner .od-logo {
    margin-bottom: var(--od-space-xl);
  }

  .od-brand__label,
  .od-brand__list {
    display: flex;
  }

  .od-brand__list {
    margin-top: var(--od-space-xs);
  }

  .od-panel {
    padding: var(--od-space-md) var(--od-panel-gutter) var(--od-space-xxl);   /* GBL-04 */
  }

  .od-nav__list {
    max-width: none;
    margin: 0;
  }

  .od-main {
    padding-top: var(--od-space-xl);
    justify-content: flex-start;
  }

  /* The short single-column screens share one column width: centred at 62 % of the panel width (not
     of the padding box), min 320, max 420. The percentage resolves against the panel's content box,
     so the two gutters are added back at 62 %: calc(62% + 1.24 x gutter).

     Login, forgot password, OTP verification and set-new-password are the same shape -- a heading and
     one or two fields over a button -- and a user walks straight through all four. Left on the
     default 560 px content width, the column jumped 140 px wider and shifted 70 px sideways on every
     step of the reset journey. Sign up and edit profile are excluded on purpose: they carry two-up
     field pairs and need the whole panel.

     The six OTP boxes need 6 x 48 + 5 x 8 = 328 px, which clears the 420 px cap. */
  .od-main--login .od-main__form,
  .od-main--forgot .od-main__form,
  .od-main--verify .od-main__form,
  .od-main--reset .od-main__form {
    width: calc(62% + 1.24 * var(--od-panel-gutter));
    min-width: min(320px, 100%);
    max-width: 420px;
  }

  .od-main--login + .od-footer,
  .od-main--forgot + .od-footer,
  .od-main--verify + .od-footer,
  .od-main--reset + .od-footer {
    width: calc(62% + 1.24 * var(--od-panel-gutter));
    min-width: min(320px, 100%);
    max-width: 420px;
  }

  /* Sign up: the form fills the white panel. The rail only moves alongside it at XL (below), where
     there is room for both; until then it flows underneath at its stacked width. */
  .od-main--with-rail .od-main__form {
    max-width: none;
    margin: 0;
  }

  /* The rail button keeps the standard primary padding now that the column is 220 px: squeezing it
     to 4 px to fit "CREATE MY ACCOUNT" on one line left the label touching both edges. */

  /* Profile: the card fills the white area. */
  .od-main--profile .od-main__form,
  .od-main--profile + .od-footer {
    max-width: none;
  }

  .od-footer {
    max-width: none;
  }
}

/* Between 1024 and 1119 the profile card shares the block with the 31 % brand column while the block
   has not yet reached its 1120 cap, so the three value columns are at their tightest. Tighten the
   gaps and the action button there to buy the columns back ~36 px. */
@media (min-width: 1024px) and (max-width: 1119.98px) {
  .od-profile__details,
  .od-profile__list {
    gap: var(--od-space-md);
  }

  .od-profile__action .od-btn {
    min-width: 120px;
  }
}

/* Below LG the panel stretches to the viewport height; .od-main must not absorb the slack, or the
   footer is pushed to the bottom of the screen away from the CTA (the mockups keep it under it). */
@media (max-width: 1023.98px) {
  .od-main {
    flex: 0 0 auto;
  }

  .od-footer--filled {
    padding-top: var(--od-space-xxl);
  }
}

/* The two-up pairs are gated on the viewport, but at LG the sign-up form column is the panel minus
   the 200 px rail and the 32 px gap. Between 1024 and ~1100 px each column is ~159 px, too narrow for
   the mockup placeholders ("name@example.com", "Minimum 8 characters"), so the pairs stay stacked
   until the form column can carry two fields. */
/* Sign-up rail alongside the form (mockup UI-W-02).

   This waits until 1200 px rather than starting at LG. The block is three columns at LG -- brand
   (37 %), form, rail (200 px fixed) -- and at 1024 px that left the form 325 px: narrower than the
   356 px brand panel beside it, so the field pairs could not go two-up and the page read as though
   the decoration mattered more than the form. Below 1200 the rail therefore flows underneath at its
   stacked width and the form takes the whole panel, which is what the fields actually need. */
@media (min-width: 1200px) {
  .od-main--with-rail {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--od-rail-width);
    gap: var(--od-space-xxl);
    align-items: start;
    justify-content: stretch;
  }

  .od-rail {
    max-width: none;
    margin: 0;
    position: sticky;
    top: var(--od-space-xl);
  }
}

/* XL >= 1440: container stays 1120, page gutter grows. */
@media (min-width: 1440px) {
  .od-shell {
    padding: 64px 48px;
  }
}
