/* Self-service account pages.
 *
 * signin.css owns the door (brand, inputs, primary button); this file adds only
 * what the account surface needs on top: field help and errors, the password
 * meter, the honeypot, result-page links, and the wider multi-column layout of
 * /account. There is no inline style anywhere in these templates, so the
 * blueprint can serve `style-src 'self'`.
 *
 * Nothing here declares a colour literal. Every value reads theme.css, which is
 * what keeps this surface and the player the same product -- and what lets the
 * one page that gains a light theme (the panel) inherit it correctly. */

/* ---- shared bits ---- */
.optional {
  font-weight: 400;
  letter-spacing: .06em;
  text-transform: lowercase;
  color: var(--muted);
}
.legal-links a { display: inline-flex; align-items: center; min-height: 24px; }

.field-help {
  font-size: .75rem;
  line-height: 1.55;
  color: var(--muted);
  margin: var(--space-2) 0 0;
  max-width: var(--measure);
  overflow-wrap: anywhere;
}

.field-error {
  font-size: .75rem;
  line-height: 1.5;
  color: var(--danger-tint);
  margin: var(--space-2) 0 0;
  overflow-wrap: anywhere;
}

.form-note {
  font-size: .75rem;
  line-height: 1.55;
  color: var(--muted);
  margin: var(--space-4) 0 0;
}
.form-note.error { color: var(--danger-tint); }
.form-note.success {
  color: var(--text);
  border-left: 2px solid var(--accent);
  padding-left: var(--space-3);
  margin: 0 0 var(--space-5);
}

/* Parked outside the viewport and hidden, so a person can neither see nor tab
   into it. Any value at all means a script filled it in. */
.field-honeypot {
  position: absolute;
  left: -9999px;
  top: 0;
  width: 1px;
  height: 1px;
  overflow: hidden;
  visibility: hidden;
}

/* Most of this page is a switch: the verification form, the two-factor
   enrollment, the "no other device" line and each provider's Sign in /
   Disconnect pair all appear and disappear on the `hidden` attribute. A
   component that sets its own `display` would override the UA rule, and the
   furniture for a state the account is not in would stay on screen. */
[hidden] { display: none !important; }

/* ---- password meter ---- */
.pw-meter {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
}
.pw-meter-bar {
  display: block;
  width: 74px;
  height: 3px;
  background: var(--border);
  overflow: hidden;
  position: relative;
}
.pw-meter-bar::after {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--fill, 0%);
  background: var(--pw-color, var(--border));
  transition: width .18s ease-out, background .18s ease-out;
}
.pw-meter-text {
  font-size: .75rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}
.pw-meter[data-state="weak"] { --pw-color: var(--danger); }
.pw-meter[data-state="fair"] { --pw-color: var(--accent); }
.pw-meter[data-state="good"] { --pw-color: var(--ok); }

/* ---- result pages ---- */
.primary-link,
.quiet-link {
  display: block;
  margin-top: var(--space-5);
  text-align: center;
  text-decoration: none;
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--on-accent);
  background: var(--accent);
  border: 1px solid var(--accent);
  padding: var(--space-3) var(--space-4);
}
.primary-link:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.quiet-link {
  background: none;
  border: 0;
  color: var(--muted);
  font-weight: 600;
  padding: 0;
  margin-top: var(--space-4);
  text-transform: none;
  letter-spacing: .02em;
}
.quiet-link:hover { color: var(--text); }

.door-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-4);
}
.door-links a {
  display: inline-flex;
  align-items: center;
  min-height: var(--control-h-md);
  color: var(--muted);
  font-size: .75rem;
  text-decoration: none;
}
.door-links a:hover { color: var(--text); text-decoration: underline; }

/* ---- /account ---- */
body.account {
  align-items: flex-start;
  padding-top: var(--space-6);
}
/* Four panels stacked in one column read as a form, not as an account, so the page
   is as wide as it needs to be: a rail of section links beside the one panel being
   used, and on a phone that rail laid back down as a row. The 1060px is the shell's
   budget, not a measure -- prose inside a panel is capped separately. */
body.account main { max-width: 1060px; width: 100%; }

.account-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.account-head .who { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.account-head .avatar {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 42px;
  height: 42px;
  /* The one deliberate circle on the page: an avatar stands for a person, not
     for a control, so it keeps the round shape the sharp-corner language
     reserves for media. */
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--accent-tint);
}
.account-head .who h2 {
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: 1.3;
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}
.account-head .who p {
  font-size: .75rem;
  color: var(--muted);
  margin: var(--space-1) 0 0;
}
.account-actions-top { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* The glance row. Every chip here is a thing a panel explains in full below. */
.status-strip {
  list-style: none;
  margin: 0 0 var(--space-4);
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: var(--border-soft);
  border: 1px solid var(--border);
  overflow: hidden;
}
.status-strip .chip {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  min-width: 0;
}
.chip-label {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
}
.status-strip .badge { max-width: 100%; }

/* The shell. Four sections stacked in one column is a scroll that never ends, and
   three of them are not the one you came for: the links move to a rail beside the
   content and account.js shows one section at a time. The rail is sticky because the
   section it is choosing between can still be taller than the window. */
.account-shell {
  display: grid;
  /* 176px is the widest the labels have ever been on one line at this size, which is
     the whole job. Anything more comes out of the panel beside it. */
  grid-template-columns: 176px minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}

/* Jump links, standing up. They stay links rather than becoming tabs: every target
   is a real fragment, so /account#panel-sessions works with no script at all and the
   back button walks the sections. */
.section-nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  position: sticky;
  top: var(--space-4);
  margin: 0;
}
.section-nav a {
  display: flex;
  align-items: center;
  /* --control-h-md, not --control-h-sm: this is the same component as the status
     page's section navigation, and a tab you can only reach with a mouse is not a
     tab on a phone. */
  min-height: var(--control-h-md);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--border);
  padding: var(--space-2) var(--space-3);
}
.section-nav a:hover { color: var(--text); border-color: var(--border-hover); }
.section-nav a.active {
  color: var(--text);
  border-color: var(--accent);
  background: var(--accent-soft);
  /* Which line is chosen has to be readable without comparing four washes of colour,
     so the rail carries the same edge marker the admin sidebar draws. */
  box-shadow: inset 3px 0 0 var(--accent);
}
.section-nav a.quiet {
  /* One gap further down than the rest of the rail: the destructive entry should not
     read as the fifth ordinary one. */
  margin-top: var(--space-3);
  color: var(--danger-tint);
  border-color: var(--danger-line);
  border-style: dashed;
}
.section-nav a.quiet:hover { color: var(--text); background: var(--danger); border-color: var(--danger); }
.section-nav a.quiet.active {
  background: var(--danger-soft);
  box-shadow: inset 3px 0 0 var(--danger);
}

/* The notes that belong to no one section, then the section that is showing. Only one
   panel is ever unhidden while the script is running, so this needs no grid of its
   own -- stacked in source order is what a browser without JavaScript reads. */
.account-panes { min-width: 0; }

/* The one action that cannot be undone sits under the grid, apart from the rows it
   would erase, rather than sharing the rail with the sections. */
.danger-zone {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

/* The account section is a summary: a fact per row, with the buttons that change
   it at the action end of the same row. */
.state-line .prov-cell { justify-content: flex-end; }

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  padding: var(--space-5);
  margin-bottom: var(--space-4);
  /* Jump links land on the heading, and a heading that sits under the sticky
     accent bar reads as though the link did nothing. */
  scroll-margin-top: var(--space-5);
}
.panel > h3 {
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text);
  margin: 0 0 var(--space-2);
}
.panel > .panel-hint,
.panel .panel-hint {
  font-size: .75rem;
  line-height: 1.6;
  color: var(--muted);
  margin: 0 0 var(--space-2);
  max-width: var(--measure);
  /* These sentences quote the account's own username and address, and neither is
     guaranteed to contain a breakable space at 320px. */
  overflow-wrap: anywhere;
}
.panel.danger { border-color: var(--danger-line); padding: var(--space-3) var(--space-5); }
.panel.danger summary { color: var(--danger-tint); }
.panel.danger .panel-hint { margin-top: var(--space-3); }

/* Results render inside the section that produced them. */
.section-notes { margin-top: var(--space-4); }
.section-notes:empty { display: none; }

/* Off-screen until focused: keyboard users can jump past the brand block. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
  border: 1px solid var(--accent);
  color: var(--text);
  font-size: .75rem;
  z-index: 10;
}
.skip-link:focus { left: var(--space-3); top: var(--space-3); }

/* One focus language for the whole page. signin.css removes the outline on
   inputs to draw its own border, so without this the only way to see where you
   are is a border the field already has. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-4);
  align-items: flex-end;
}
.row > .grow { flex: 1 1 190px; }
.row label { margin-top: var(--space-4); }
/* signin.css makes a primary button a full-width CTA, which is right for a door
   page where it is the only thing on the form. Paired with a second action it
   instead shoves that action onto its own line, so inside a row the button goes
   back to being as wide as its label. */
body.account .row > button { flex: 0 0 auto; width: auto; }

/* Labels that wrap their input need the gap the unwrapped ones got from the
   label margin; keeps the label text above the field in both shapes. */
.panel label > input { margin-top: var(--space-2); }
/* `label:first-of-type { margin-top: 0 }` assumes a form that starts at the top
   of its own box. On this page forms follow prose, a QR or another field, so
   the first label was landing flush against whatever came before it. */
.panel form > label:first-of-type { margin-top: var(--space-4); }

.state-line {
  display: flex;
  /* A label and its action are two fixed widths in a row that at 320px is not
     wide enough for both; the action drops below rather than running off. */
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border);
  font-size: .75rem;
  min-width: 0;
}
.state-line:first-of-type { border-top: 0; }
/* A second form in a panel is a second task, not the tail of the first: without
   a divider "Save changes" and "Confirm my email" stack up as two primary
   buttons belonging to one action. The line goes on the form because the state
   row that opens it drops its own top border as the first of its type. */
.panel form + form {
  margin-top: var(--space-5);
  border-top: 1px solid var(--border);
}
.state-line .label {
  flex: 0 0 auto;
  color: var(--muted);
  text-transform: uppercase;
  font-size: var(--text-xs);
  letter-spacing: .12em;
  font-weight: 600;
}
/* `@username` is a value, not a sentence: it reads as one when it is set in the
   same face as the prose around it. admin.css owns this rule there but this page
   does not load that file, so it belongs here too. */
.mono { font-family: var(--font-mono); }
.state-line .mono,
.state-line .value {
  min-width: 0;
  text-align: right;
  overflow-wrap: anywhere;
}
/* The address is the thing this row is about, so it reads larger and heavier than
   the label beside it. */
#profile-email {
  font-size: .85rem;
  font-weight: 700;
}

.badge {
  display: inline-block;
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--border);
  color: var(--muted);
  overflow-wrap: anywhere;
}
.badge.ok { color: var(--ok-tint); border-color: var(--ok-line); background: var(--ok-soft); }
.badge.warn { color: var(--warn-tint); border-color: var(--warn-line); background: var(--warn-soft); }
.badge.bad { color: var(--danger-tint); border-color: var(--danger-line); background: var(--danger-soft); }

/* ---- controls: one height, one padding, one border ----
   `ghost` and `tiny` are the same control at two weights, and the link-shaped
   twins below are the same control again because /account deep-links to the
   provider sign-in pages -- a bare <a> there would read as body text. */
button.ghost,
button.tiny,
a.ghost-link,
a.tiny-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--control-h-sm);
  padding: 0 var(--space-3);
  background: none;
  border: 1px solid var(--border);
  color: var(--text);
  font-family: inherit;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
/* A thumb cannot land on a 32px target reliably, so the compact control height is
   the wrong promise on any surface that gets touched: coarse pointer, or narrow
   enough to be a phone. */
@media (hover: none), (max-width: 899px) {
  button.ghost,
  button.tiny,
  a.ghost-link,
  a.tiny-link { min-height: var(--control-h-md); }
}
/* A button label that outgrows its column may break instead of painting past the
   panel: "Sign out other devices" is the one that does it. The link-shaped twins
   keep `nowrap` because their labels are fixed words. */
button.ghost,
button.tiny {
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: center;
}
button.ghost:hover,
a.ghost-link:hover,
button.tiny:hover,
a.tiny-link:hover { border-color: var(--accent-line); color: var(--accent-tint); background: var(--hover-tint); }button.ghost:active,
a.ghost-link:active,
button.tiny:active,
a.tiny-link:active { background: var(--surface-2); }
button.ghost:disabled,
button.tiny:disabled { opacity: .45; cursor: default; }
button.ghost:disabled:hover,
button.tiny:disabled:hover { border-color: var(--border); color: var(--text); background: none; }
button.destructive,
a.tiny-link.destructive { border-color: var(--danger-line); color: var(--danger-tint); }
button.destructive:hover,
button.destructive:active,
a.tiny-link.destructive:hover,
a.tiny-link.destructive:active {
  border-color: var(--danger);
  color: var(--on-accent);
  background: var(--danger);
}
button.ghost.linkish { text-align: center; }

/* The primary action is the only filled control on the page, so it is the one
   place a token pair is repeated rather than a modifier. signin.css parks it 22px
   below the last field; that offset is not on the spacing scale this page uses. */
body.account button.primary {
  min-height: var(--control-h-md);
  padding: 0 var(--space-4);
  margin-top: var(--space-5);
}

/* The action end of a service or state row. A fixed budget keeps the row from
   shuffling sideways when a badge grows to "Needs signing in again". The budget is
   a floor, not a licence to escape: capped to the row, the badge and the button
   wrap inside the cell instead of painting over the next panel. */
.prov-cell {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  flex: 0 0 auto;
  min-width: 168px;
  max-width: 100%;
}

.devices {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  /* The only list on this page with no fixed length: a row carries a name, three
     pieces of detail and the raw header, so six signed-in browsers used to stretch
     this panel past the bottom of the screen. It scrolls inside itself now and the
     panel keeps one height whatever the number. The `min()` is so a short viewport
     never gets a list taller than the page it sits on, and `contain` is so the scroll
     stops here when the rows run out instead of picking up the whole page with it. */
  max-height: min(20rem, 60vh);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) transparent;
}
.devices::-webkit-scrollbar { width: 8px; }
.devices::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); }
.devices::-webkit-scrollbar-track { background: transparent; }
.devices li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border);
}
/* The name keeps a line it can actually be read on, so the row wraps around the
   button rather than the button squeezing the name into an ellipsis. */
.devices .device-main { flex: 1 1 12rem; min-width: 0; }
/* The name and the "this device" mark share a line and must not trade places:
   the mark is short and fixed, the name is whatever a browser chose to call
   itself, so the name is the thing that gives way. */
.devices .device-line {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
}
.devices .device-title {
  font-size: var(--text-base);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.devices .device-meta {
  font-size: .75rem;
  color: var(--muted);
  margin-top: var(--space-1);
  overflow-wrap: anywhere;
}
/* The raw User-Agent is still the record of what signed in, so it stays on the
   page -- as the technical detail it is, under the name a person can recognise,
   and only on hover or when there is nothing better to say. */
.devices .device-ua {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--faint);
  margin-top: var(--space-1);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.devices .this-one {
  flex: 0 0 auto;
  color: var(--accent-tint);
  font-weight: 700;
  font-size: var(--text-xs);
  letter-spacing: .1em;
  text-transform: uppercase;
  white-space: nowrap;
}
/* The button keeps its own line when a readable device name and the control will
   not share one, and drops back into the row when they will. Shrinking it instead
   is what let the label escape the panel at 320px. */
.devices button { flex: 0 1 auto; min-width: 0; margin-left: auto; }

.enroll {
  margin-top: var(--space-4);
  border: 1px solid var(--border);
  padding: var(--space-4);
}
.enroll-grid {
  display: flex;
  gap: var(--space-5);
  align-items: flex-start;
  flex-wrap: wrap;
}
.qr-frame {
  /* A QR needs a light backing to be readable by a camera; that is the one
     reason this page may carry white, and theme.css names it. The border is for
     the light theme, where white on white leaves the frame edgeless. */
  background: var(--qr-frame);
  border: 1px solid var(--border);
  padding: var(--space-3);
  width: 168px;
  height: 168px;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
}
.qr-frame img { width: 148px; height: 148px; display: block; }
.qr-fallback {
  font-size: var(--text-sm);
  color: var(--muted);
  line-height: 1.55;
}
.enroll-steps { flex: 1 1 240px; min-width: 0; }
.enroll-steps ol {
  margin: 0 0 var(--space-3) var(--space-4);
  font-size: .75rem;
  line-height: 1.7;
  color: var(--muted);
}
.secret-code {
  font-family: var(--font-mono);
  font-size: .75rem;
  letter-spacing: .06em;
  word-break: break-all;
  background: var(--bg);
  border: 1px solid var(--border);
  padding: var(--space-2) var(--space-3);
  margin: 0 0 var(--space-3);
}

/* The one notice that belongs to no section, sitting above whichever section is
   showing. It has to contribute nothing while it has nothing to say: a top margin on
   an empty box is what pushed the panel out of line with the rail beside it. */
.note-stack { margin: var(--space-4) 0; }
.note-stack:empty { display: none; }
.note {
  font-size: .75rem;
  line-height: 1.55;
  padding: var(--space-2) var(--space-3);
  border-left: 2px solid var(--border);
  background: var(--hover-tint);
  margin: 0 0 var(--space-2);
  overflow-wrap: anywhere;
}
.note.ok { border-left-color: var(--ok); }
.note.bad { border-left-color: var(--danger); }
.note.warn { border-left-color: var(--accent); }

/* A refusal in the delete flow is said over the popup, not in a line under the
   field that caused it. <dialog> paints in the top layer, above anything appended
   to <body>, so the stack is created inside the open popup; the body rule is the
   fallback for a refusal that arrives with no popup open. It sits where the rest
   of the app's toasts sit: bottom centre, square, on the page's own surface. */
.toast-stack {
  position: fixed;
  bottom: var(--space-4);
  left: 50%;
  transform: translateX(-50%);
  width: min(420px, calc(100vw - 32px));
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  z-index: 1;
  pointer-events: none;
}
.toast {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  font-size: .8rem;
  line-height: 1.5;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 2px solid var(--danger);
  box-shadow: var(--pop-shadow);
  overflow-wrap: anywhere;
}

details.reveal summary {
  cursor: pointer;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--danger-tint);
  /* The disclosure that opens the one irreversible action on the page should not
     be a 17px strip of text to aim at. */
  display: flex;
  align-items: center;
  min-height: var(--control-h-sm);
}
details.reveal[open] summary { color: var(--text); }
details.reveal > .reveal-body { margin-top: var(--space-2); }

.confirm-box {
  border: 1px dashed var(--danger-line);
  padding: var(--space-4);
  margin-top: var(--space-4);
}

/* ---- linked services ---- */
.service-list { list-style: none; margin: var(--space-1) 0 0; padding: 0; }
.service {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border);
}
.service:first-child { border-top: 0; }
.service-main { min-width: 0; flex: 1 1 190px; display: flex; flex-direction: column; gap: var(--space-1); }
.service-name { font-size: var(--text-base); font-weight: 650; }
.service-hint { color: var(--muted); font-size: .75rem; line-height: 1.5; }
.signout-link { color: var(--danger-tint); }
.signout-link:hover { color: var(--danger-tint); background: var(--danger); border-color: var(--danger); }

/* ---- the popups ----
   <dialog> supplies the focus trap, the Escape key and the top layer, so none of
   that is re-implemented here. The page loads no admin.css, so the bar's shell is
   declared against the same tokens everything else on this surface uses.
   `data-autopen` rather than `open` is what lets showModal() run without throwing;
   the not([open]) rule below is the no-script shape of the same dialog. */
.account-dialog {
  width: min(560px, calc(100vw - 32px));
  max-height: min(88vh, 760px);
  margin: auto;
  padding: 0;
  border: 1px solid var(--border);
  /* Square, like every other surface on this page. */
  border-radius: 0;
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--pop-shadow);
}
.account-dialog::backdrop { background: var(--overlay); }
/* The browser's default dialog overflow would scroll the box itself, on top of
   .dialog-body, giving the popup two scrollbars. Only the body scrolls. */
.account-dialog[open] {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  overflow: hidden;
}
/* No script: the server marked the dialog the trigger link named, and a closed
   dialog is display:none by default. This puts it back in the page as an ordinary
   block at the end of <main>, so every field in it is reachable. The script drops
   the flag before it calls showModal(), so a popup that is closed stays closed. */
.account-dialog[data-autopen]:not([open]) {
  display: block;
  position: static;
  width: auto;
  max-width: none;
  max-height: none;
  margin: var(--space-5) 0 0;
  box-shadow: none;
}
.dialog-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border);
}
.dialog-head h2 {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-size: .95rem;
  margin: 0;
}
/* A long popup scrolls its body and keeps its head and foot: that is what stops a
   phone-sized viewport from pushing the submit button out of reach. */
.dialog-body {
  padding: var(--space-5);
  overflow: auto;
  overscroll-behavior: contain;
}
.dialog-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--border);
}
/* Two tasks in one popup are two blocks, separated the same way two forms in a
   panel are: the line belongs to the block that follows, so the first one has no
   rule above it. `.enroll` brings its own frame and margin. */
.dialog-block { min-width: 0; }
.dialog-block + .dialog-block,
.enroll + .dialog-block {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}
/* A popup opened at one task shows only that task, so there is no line between it
   and a block that is not on screen: whichever block the view shows is the first
   thing in the body. */
.account-dialog[data-view] .dialog-body > [data-dialog-block]:not([hidden]) {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}
.dialog-block > h3 {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text);
  margin: 0 0 var(--space-2);
}
.dialog-block > .field-help { margin-top: 0; }
/* signin.css zeroes the first label's margin because a door form begins at the top
   of its own box; here every form begins under prose or a state row. */
.dialog-body form > label:first-of-type { margin-top: var(--space-4); }
/* A form that begins the body already sits below the padding under the head. */
.dialog-body > form:first-child > label:first-of-type { margin-top: 0; }
.dialog-body label > input { margin-top: var(--space-2); }
/* A tick is a sentence, not a field name: no small caps, no letter spacing, and
   the box is a hand-sized target even though the text is clickable too. */
.dialog-body label.check {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-4) 0 0;
  font-size: .78rem;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text);
  cursor: pointer;
}
.dialog-body label.check input {
  width: 1.5rem;
  height: 1.5rem;
  margin: 0;
  padding: 0;
  flex: 0 0 auto;
  accent-color: var(--accent);
}
/* "Did it send?" and "what happened" answers, inside the popup that asked. */
.dialog-body .note { margin: var(--space-3) 0 0; }
.dialog-body .note:empty { display: none; }

/* ---- connecting a service ----
   Both providers share one popup: only the copy and the endpoint change. The
   credential form is the standalone door's two fields; the challenge panels are
   whatever the provider asks for next, one at a time. */
#connect-intro { margin-top: 0; }
#connect-steps > div > .field-help { margin: 0 0 var(--space-3); }
/* These labels follow the provider's question, not another field, so the door's
   18px offset is not on this page's spacing scale. */
#connect-code-panel label,
#connect-captcha-panel label,
#connect-choice-manual-label { margin-top: 0; }
/* The picture is 308x90 from the provider; it may not paint wider than the popup
   on a phone, and the frame is what makes a light challenge on a dark page read
   as an image rather than a hole. */
.connect-captcha {
  display: block;
  width: 100%;
  max-width: 308px;
  height: auto;
  margin: 0 0 var(--space-2);
  border: 1px solid var(--border);
  background: var(--bg);
}
/* A radio is a choice, not field help: sentence case, one row per option, and
   the whole row is the target. */
#connect-choice-options { margin-top: var(--space-3); }
.connect-choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  font-size: .82rem;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.4;
  color: var(--text);
  cursor: pointer;
}
.connect-choice:first-child { margin-top: 0; }
.connect-choice:hover { border-color: var(--accent-line); }
.connect-choice input {
  /* A radio inside a row that is entirely clickable, but it is still its own target:
     24px is the floor WCAG sets and the one the rest of this page keeps. */
  width: 1.5rem;
  height: 1.5rem;
  margin: 0;
  padding: 0;
  flex: 0 0 auto;
  accent-color: var(--accent);
}
#connect-choice-manual { margin-top: var(--space-2); }
/* What the server is doing while an answer is checked or an approval is waited
   on. It is a line, not a field, so it collapses when there is nothing to say. */
.connect-status {
  font-size: .75rem;
  line-height: 1.5;
  color: var(--muted);
  margin: var(--space-3) 0 0;
  overflow-wrap: anywhere;
}
.connect-status:empty { display: none; }
.connect-status.error { color: var(--danger-tint); }
/* The wait itself: one small ring beside the sentence that says what is being
   waited on. It belongs to the same quiet line as the status, not to a banner. */
.connect-working {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: .75rem;
  line-height: 1.5;
  color: var(--muted);
  margin: var(--space-3) 0 0;
}
.connect-working[hidden] { display: none; }
.connect-spinner {
  margin: 4px;
  width: 1rem;
  height: 1rem;
  flex: 0 0 auto;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: connect-spin .8s linear infinite;
}
@keyframes connect-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /* A turning ring is decoration a reduced-motion reader asked not to see; the
     sentence beside it still says what is happening. */
  .connect-spinner { animation: none; border-top-color: var(--accent); opacity: .7; }
}
/* The check is often a picture grid, so the frame is both the picture and the
   tap surface. Coordinates are held as percentages, which keeps the marks on
   the same picture at every width. */
.connect-captcha-frame {
  position: relative;
  width: 100%;
  max-width: 460px;
  margin: 0 0 var(--space-2);
}
.connect-captcha-frame .connect-captcha { margin: 0; max-width: none; }
.connect-captcha-frame.tappable .connect-captcha { cursor: crosshair; }
.captcha-mark {
  position: absolute;
  transform: translate(-50%, -50%);
  width: 1.75rem;
  height: 1.75rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 2px solid var(--on-accent);
  background: var(--accent);
  color: var(--on-accent);
  font-size: .72rem;
  font-weight: 600;
  line-height: 1;
  pointer-events: none;
}
#connect-captcha-clear { margin: 0 0 var(--space-2); }
#connect-method-options { margin-top: var(--space-3); }
#connect-code-different,
#connect-approval-different { margin-top: var(--space-3); display: block; }
#connect-code-different[hidden],
#connect-approval-different[hidden] { display: none; }
#connect-retry { margin-top: var(--space-3); }
@media (max-width: 560px) {
  .dialog-head, .dialog-foot { padding: var(--space-3) var(--space-4); }
  .dialog-body { padding: var(--space-4); }
  .account-dialog { max-height: 92vh; }
}

/* ---- breakpoints ----
   The rail needs room beside the content to be a rail; below that it becomes the jump
   row this page used to have, and the sections still switch the same way. */
@media (max-width: 899px) {
  .account-shell { grid-template-columns: minmax(0, 1fr); }
  .section-nav {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--space-2);
    /* The shell's own row gap is what separates the rail from the section below it
       once they stack, so a margin here would just double the space. */
    margin: 0;
  }
  /* Back to the horizontal shape in every respect, including the marker: a link that
     is 40px tall does not need a third edge painted on it. */
  .section-nav a { padding: 0 var(--space-4); }
  .section-nav a.active,
  .section-nav a.quiet.active { box-shadow: none; }
  .section-nav a.quiet { margin-top: 0; margin-left: auto; }
}

@media (max-width: 760px) {
  .status-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 560px) {
  body.account { padding-top: var(--space-4); }
  .panel { padding: var(--space-4); }
  .panel.danger { padding: var(--space-3) var(--space-4); }
  .account-head { flex-direction: column; align-items: flex-start; }
  .account-actions-top { width: 100%; }
  .section-nav a.quiet { margin-left: 0; }
  /* The 4-column glance row is 2x2 by now; the action cell stops reserving
     desktop width so the two-line service row can sit under its name. */
  .prov-cell { min-width: 0; justify-content: flex-start; }
  .service { align-items: flex-start; }
}

@media (max-width: 380px) {
  .status-strip { grid-template-columns: minmax(0, 1fr); }
  .enroll-grid { gap: var(--space-4); }
}

@media (prefers-reduced-motion: reduce) {
  .pw-meter-bar::after { transition: none; }
}

/* Reserve the complete two-factor dialog from the first step onward. */
#dialog-totp { height: min(640px, 88dvh); }
#dialog-totp .begin-code-slot { height: 100px; margin-top: var(--space-4); }
#reauth-send-begin { width: 100%; height: 52px; }
#dialog-totp .begin-otp-row { display: flex; gap: 12px; align-items: center; }
#dialog-totp .begin-otp-row input { flex: 1; min-width: 0; width: 0; }
#reauth-resend-begin { flex: 0 0 120px; min-height: 44px; }

/* A submitted answer gives way to a centered wait, then the next question. */
#dialog-connect.provider-working #connect-setup,
#dialog-connect.provider-working #connect-steps,
#dialog-connect.provider-working #connect-intro { display: none; }
#dialog-connect.provider-working #connect-working {
  min-height: 240px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  text-align: center;
}
#dialog-connect.provider-working .connect-spinner { width: 42px; height: 42px; }
@keyframes provider-step-enter {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}
#connect-setup:not([hidden]), #connect-steps:not([hidden]), #connect-working:not([hidden]) {
  animation: provider-step-enter .25s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  #connect-setup, #connect-steps, #connect-working { animation: none; }
}

#account-id { overflow-wrap: anywhere; user-select: all; min-width: 0; flex: 1; }
#copy-account-id { background: transparent; color: var(--text); font: inherit; white-space: nowrap; cursor: pointer; }

.account-id-line .prov-cell { justify-content: flex-start; text-align: left; }
