/* ==========================================================================
   VELCREST PORTAL — DASHBOARD COMPONENTS
   Loaded after styles.css. Everything here is scoped to .portal-view or to a
   class only the dashboards use, so the auth screens and the app shell are
   untouched.

   Same design system as styles.css and as velcrestwebdev.com ("Nocturne",
   2026-09-14/15). Read the header of styles.css before changing anything in
   here: transparent buttons, 2px rectangles, Outfit uppercase for display,
   Manrope for reading, and the amber spent only on the current step, field
   focus and button hover.

   NO THIRD-PARTY REQUESTS. No font CDN, no remote image. The business's
   published privacy policy says no third party is involved beyond its host
   and mail provider, and a single webfont request would make that statement
   false. The faces used here are self-hosted in portal/fonts/ via
   css/fonts.css.
   ========================================================================== */

:root {
    /* Outfit and Manrope are now genuinely loaded (portal/css/fonts.css), so
       these two aliases point at the real brand faces instead of the system
       stacks that stood in for them while no font was available. */
    --portal-font: var(--font-sans);
    --portal-font-display: var(--font-display);
    --portal-radius: 2px;
    --portal-radius-sm: 2px;
}

/* --------------------------------------------------------------- layout  */

.portal-view {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-lg);
    font-family: var(--portal-font);
}

.portal-view h1,
.portal-view h2,
.portal-view h3,
.portal-view h4 {
    font-family: var(--portal-font-display);
    font-weight: 500;
    text-transform: uppercase;
}

.portal-view .dashboard-title {
    margin-bottom: 0;
}

.view-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacing-md);
}

.back-button {
    align-self: flex-start;
}

/* Screen-reader-only live region (see #portal-live in index.html). */
.visually-hidden {
    position: absolute !important;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ---------------------------------------------------------------- panel  */

.panel {
    background-color: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--portal-radius);
    overflow: hidden;
}

/* A destructive panel earns the one hue that is not in the system. It is a
   single hairline, not a wash. */
.panel-danger {
    border-color: rgba(226, 103, 92, 0.55);
}

.panel-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--spacing-md);
    flex-wrap: wrap;
    padding: var(--spacing-lg);
    border-bottom: 1px solid var(--color-line);
    background-color: transparent;
}

.panel-title {
    font-size: 1rem;
    letter-spacing: 0.07em;
    margin: 0;
}

.panel-subtitle {
    margin: var(--spacing-sm) 0 0 0;
    color: var(--color-text-2);
    font-size: 0.875rem;
}

.panel-body {
    padding: var(--spacing-lg);
}

.panel-body-tight {
    padding-top: 0;
}

.panel-note {
    margin: var(--spacing-md) 0 0 0;
    color: var(--color-text-2);
    font-size: 0.8125rem;
    line-height: 1.6;
}

.panel-body > .panel-note:first-child {
    margin-top: 0;
}

/* ------------------------------------------------- loading/empty/error   */

.state-block {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--spacing-sm);
    padding: var(--spacing-lg);
    border: 1px dashed var(--color-line);
    border-radius: var(--portal-radius-sm);
    background-color: transparent;
}

.state-title {
    margin: 0;
    font-family: var(--font-display);
    font-size: 0.875rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-text);
}

.state-text {
    margin: 0;
    color: var(--color-text-2);
    font-size: 0.875rem;
}

.state-empty {
    border-style: dashed;
}

.state-error {
    border-style: solid;
    border-color: rgba(226, 103, 92, 0.55);
}

.state-error .state-title {
    color: var(--status-error);
}

.state-spinner {
    width: 16px;
    height: 16px;
    border: 1px solid var(--color-line);
    border-top-color: var(--color-text);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
    flex-shrink: 0;
}

/* ----------------------------------------------------------------- forms */

.field-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--spacing-lg) var(--spacing-xl);
}

.portal-view .form-group {
    margin-bottom: var(--spacing-md);
}

.profile-form {
    display: flex;
    flex-direction: column;
}

.form-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--spacing-md);
    margin-top: var(--spacing-lg);
}

.form-status {
    margin: 0;
    font-size: 0.875rem;
    color: var(--color-text-2);
    min-height: 1.2em;
}

/* The file input is a control, so its boundary uses --color-line-strong. */
.portal-view input[type="file"] {
    width: 100%;
    padding: var(--spacing-md);
    border: 1px dashed var(--color-line-strong);
    border-radius: var(--portal-radius-sm);
    background-color: transparent;
    color: var(--color-text-2);
    font-family: var(--font-sans);
    font-size: 0.9375rem;
}

.portal-view input[type="file"]:focus {
    box-shadow: none;
    border-color: var(--color-signal);
}

/* Same recipe as .btn: transparent, 2px, tracked uppercase label. */
.portal-view input[type="file"]::file-selector-button {
    margin-right: var(--spacing-md);
    padding: 0.55rem 1rem;
    border: 1px solid var(--color-line-strong);
    border-radius: var(--portal-radius-sm);
    background-color: transparent;
    color: var(--color-text);
    font-family: var(--font-sans);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    cursor: pointer;
    transition: border-color var(--transition), color var(--transition), background-color var(--transition);
}

.portal-view input[type="file"]::file-selector-button:hover {
    background-color: var(--signal-wash-soft);
    border-color: var(--color-signal);
    color: var(--color-signal);
}

.upload-host {
    padding: var(--spacing-lg);
    border-bottom: 1px solid var(--color-line);
    background-color: transparent;
}

.upload-form .form-actions {
    margin-top: var(--spacing-md);
}

/* ------------------------------------------------------------ meta lists */

.meta-list {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-md);
    margin: 0 0 var(--spacing-lg) 0;
}

.meta-row {
    display: grid;
    grid-template-columns: 180px 1fr;
    gap: var(--spacing-md);
    padding-bottom: var(--spacing-md);
    border-bottom: 1px solid var(--color-line);
}

.meta-row:last-child {
    border-bottom: none;
    padding-bottom: 0;
}

.meta-row dt {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--color-text-2);
    line-height: 1.6;
}

.meta-row dd {
    margin: 0;
    color: var(--color-text);
    word-break: break-word;
}

.meta-note {
    display: block;
    margin-top: var(--spacing-xs);
    color: var(--color-text-2);
    font-size: 0.8125rem;
}

/* Not a pill — the brand has no pills. A 2px chip in a cell colour. */
.tag {
    display: inline-block;
    padding: 3px var(--spacing-sm);
    border: 1px solid var(--color-line);
    border-radius: var(--portal-radius-sm);
    background-color: var(--color-surface-2);
    color: var(--color-text-2);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    line-height: 1.4;
}

.tag-small {
    font-size: 9.5px;
    padding: 2px 6px;
    letter-spacing: 0.16em;
}

/* --------------------------------------------------------------- stepper */

.stepper {
    display: flex;
    list-style: none;
    margin: 0;
    padding: 0;
    gap: var(--spacing-sm);
}

.step {
    flex: 1 1 0;
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding-top: var(--spacing-lg);
    min-width: 0;
}

/* Connector line into the previous step. */
.step + .step::before {
    content: "";
    position: absolute;
    top: calc(var(--spacing-lg) + 13px);
    right: 50%;
    left: -50%;
    height: 1px;
    background-color: var(--color-line);
}

.step.is-complete::before,
.step.is-current::before {
    background-color: var(--color-line-strong);
}

.step-marker {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 1px solid var(--color-line-strong);
    background-color: var(--color-surface);
    color: var(--color-text-2);
    font-family: var(--font-display);
    font-weight: 500;
    font-size: 0.8125rem;
    letter-spacing: 0.04em;
    flex-shrink: 0;
}

.step-label {
    margin-top: var(--spacing-sm);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--color-text-2);
    line-height: 1.4;
}

.step-state {
    margin-top: 2px;
    color: var(--color-text-2);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
}

/* Done: full white, no fill. */
.step.is-complete .step-marker {
    background-color: transparent;
    border-color: var(--color-text);
    color: var(--color-text);
}

.step.is-complete .step-label {
    color: var(--color-text);
}

/* WHERE YOU ARE — the one amber in this component, matching the site's rule
   that a section gets a single signal-coloured label. No glow, no ring wash. */
.step.is-current .step-marker {
    background-color: transparent;
    border-color: var(--color-signal);
    color: var(--color-signal);
}

.step.is-current .step-label,
.step.is-current .step-state {
    color: var(--color-signal);
}

.step.is-upcoming .step-marker {
    border-color: var(--color-line-strong);
    color: var(--color-text-2);
}

.step.is-upcoming .step-label,
.step.is-upcoming .step-state {
    color: var(--color-text-2);
}

/* ---------------------------------------------------------- phase picker */

.phase-form {
    margin-top: var(--spacing-xl);
    padding-top: var(--spacing-lg);
    border-top: 1px solid var(--color-line);
}

.phase-fieldset {
    border: none;
    padding: 0;
    margin: 0;
}

.phase-legend {
    padding: 0;
    margin-bottom: var(--spacing-md);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--color-text-2);
}

.phase-options {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacing-sm);
}

.portal-view .phase-option {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
    margin: 0;
    min-height: 40px;
    padding: 0.55rem var(--spacing-md);
    border: 1px solid var(--color-line-strong);
    border-radius: var(--portal-radius-sm);
    background-color: transparent;
    cursor: pointer;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--color-text);
    transition: border-color var(--transition), color var(--transition), background-color var(--transition);
}

.portal-view .phase-option:hover {
    background-color: var(--signal-wash-soft);
    border-color: var(--color-signal);
    color: var(--color-signal);
}

.phase-option input[type="radio"] {
    accent-color: var(--color-signal);
    width: 15px;
    height: 15px;
    cursor: pointer;
    flex: none;
}

/* ----------------------------------------------------------------- media */

.media-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: var(--spacing-lg);
}

.media-card {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--color-line);
    border-radius: var(--portal-radius);
    background-color: var(--color-surface);
    overflow: hidden;
}

.media-preview {
    background-color: var(--color-surface-2);
    border-bottom: 1px solid var(--color-line);
}

.media-figure {
    margin: 0;
}

.media-image {
    display: block;
    width: 100%;
    height: auto;
    max-height: 320px;
    object-fit: contain;
    background-color: var(--color-void);
}

.media-placeholder,
.media-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-sm);
    min-height: 150px;
    padding: var(--spacing-lg);
    text-align: center;
    color: var(--color-text-2);
    font-size: 0.875rem;
}

.media-state-title {
    margin: 0;
    font-family: var(--font-display);
    font-size: 0.8125rem;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-text);
}

.media-state-text,
.media-state-detail {
    margin: 0;
    font-size: 0.8125rem;
    color: var(--color-text-2);
}

.media-state-detail {
    word-break: break-word;
}

/* State is carried by the title colour, not by a coloured background wash. */
.media-state-pending .media-state-title {
    color: var(--status-warning);
}

.media-state-infected {
    border: 1px solid rgba(226, 103, 92, 0.55);
}

.media-state-infected .media-state-title,
.media-state-error .media-state-title,
.media-placeholder-error {
    color: var(--status-error);
}

.media-meta {
    padding: var(--spacing-md);
    display: flex;
    flex-direction: column;
    gap: var(--spacing-xs);
}

.media-caption {
    margin: 0;
    font-weight: 600;
    color: var(--color-text);
    word-break: break-word;
}

.media-filename {
    margin: 0;
    color: var(--color-text-2);
    font-size: 0.8125rem;
    word-break: break-all;
}

.media-facts {
    margin: 0;
    color: var(--color-text-2);
    font-size: 11px;
    letter-spacing: 0.08em;
}

.media-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--spacing-sm);
    padding: 0 var(--spacing-md) var(--spacing-md);
}

.comment-count {
    color: var(--color-text-2);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

/* ---------------------------------------------------------------- thread */

.thread {
    border-top: 1px solid var(--color-line);
    padding: var(--spacing-md);
    background-color: transparent;
}

.thread-list {
    list-style: none;
    margin: 0 0 var(--spacing-md) 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--spacing-sm);
    max-height: 320px;
    overflow-y: auto;
}

.thread-empty {
    color: var(--color-text-2);
    font-size: 0.875rem;
}

.comment {
    padding: var(--spacing-sm) var(--spacing-md);
    border-radius: 0;
    background-color: var(--color-surface-2);
    border-left: 2px solid var(--color-line-strong);
}

/* Who is speaking is told by brightness, the way the rest of the system tells
   primary from secondary. The amber is not spent here — a busy thread would
   have turned the panel orange. */
.comment-admin {
    border-left-color: var(--color-text);
}

.comment-client {
    border-left-color: var(--color-line-strong);
}

.comment-head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--spacing-sm);
    margin-bottom: 2px;
}

.comment-author {
    font-weight: 600;
    color: var(--color-text);
    font-size: 0.875rem;
}

.comment-time {
    color: var(--color-text-2);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

.comment-body {
    margin: 0;
    color: var(--color-text-2);
    font-size: 0.9375rem;
    white-space: pre-wrap;
    word-break: break-word;
}

.comment-form label {
    font-size: 11px;
}

.comment-form textarea {
    width: 100%;
    min-height: 72px;
}

/* ---------------------------------------------------------- client strip */

.client-strip {
    list-style: none;
    margin: var(--spacing-md) 0 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--spacing-md);
}

.client-strip-item {
    padding: var(--spacing-lg);
    border: 1px solid var(--color-line);
    border-radius: var(--portal-radius-sm);
    background-color: var(--color-surface);
}

.client-strip-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--spacing-md);
    flex-wrap: wrap;
    margin-bottom: var(--spacing-md);
}

.client-strip-name {
    margin: 0;
    font-family: var(--font-display);
    font-size: 0.9375rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-text);
}

.client-strip-meta {
    margin: 2px 0 0 0;
    color: var(--color-text-2);
    font-size: 0.8125rem;
    word-break: break-all;
}

.client-strip-progress {
    margin-top: var(--spacing-md);
}

.row-actions {
    display: flex;
    gap: var(--spacing-sm);
    flex-wrap: wrap;
}

/* ------------------------------------------------------ confirm deletion */

.confirm-delete p {
    margin-bottom: var(--spacing-md);
    color: var(--color-text-2);
}

.confirm-list {
    margin: 0 0 var(--spacing-md) var(--spacing-lg);
    padding: 0;
    color: var(--color-text-2);
    font-size: 0.875rem;
}

.confirm-list li {
    margin-bottom: var(--spacing-xs);
}

/* ------------------------------------------------------------ focus ring */

/* styles.css already carries the system's ring — 2px solid --color-text at a
   3px offset, the same one the marketing site uses — on :focus-visible for
   everything. These two only exist because the elements below need the ring
   to appear on a PARENT rather than on the control itself. There is nothing
   inside an `opacity: 0` container in this portal; if that ever changes, the
   ring must move onto a visible ancestor or it will be drawn invisibly. */
.portal-view .phase-option:focus-within,
.portal-view .form-checkbox:focus-within {
    outline: 2px solid var(--color-text);
    outline-offset: 3px;
}

/* ==========================================================================
   TWO-FACTOR (added 2026-09-15)

   The one exception to this sheet's "scoped to .portal-view" rule, noted at
   the top of the file: the three 2FA screens live inside #auth-container, not
   inside a dashboard view. They are here rather than in styles.css because
   styles.css belongs to the branding pass and this does not.

   Same system as everything above and nothing new added to it: transparent
   buttons, flat 2px rectangles, Outfit for display, Manrope for reading, and
   the amber spent only where it is already spent — field focus and button
   hover. NO EXTERNAL RESOURCE OF ANY KIND. The monospace stack below is
   system fonts only; the QR code is inline bytes from our own API.
   ========================================================================== */

/* The enrolment and backup-code cards carry more than a login form does. */
.auth-screen-wide {
    max-width: 480px;
}

/* -------------------------------------------------------- numbered steps */

.twofa-step {
    margin-bottom: var(--spacing-lg);
    padding-bottom: var(--spacing-lg);
    border-bottom: 1px solid var(--color-line);
}

.twofa-step-label {
    margin: 0 0 var(--spacing-sm) 0;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--color-text-2);
}

.twofa-step-text {
    margin: 0 0 var(--spacing-md) 0;
    color: var(--color-text-2);
    font-size: 0.875rem;
    line-height: 1.7;
}

.twofa-step-text:last-child {
    margin-bottom: 0;
}

.twofa-step-text strong {
    color: var(--color-text);
    font-weight: 700;
}

/* ------------------------------------------------------------- QR code   */

/* A QR code has to be dark-on-light with a quiet zone or phone cameras
   struggle with it, so this is the one deliberately light surface in the
   portal. It is a machine target, not a design element. */
.twofa-qr-frame {
    width: 200px;
    max-width: 100%;
    margin: 0 auto var(--spacing-md);
    padding: 12px;
    background-color: #ffffff;
    border-radius: var(--portal-radius-sm);
}

.twofa-qr {
    display: block;
    width: 100%;
    height: auto;
    image-rendering: pixelated; /* keep module edges crisp when scaled */
}

.twofa-manual {
    margin-top: var(--spacing-md);
}

/* The setup key is a fallback for anyone who cannot scan, so it has to be
   readable a character at a time and selectable in one gesture. */
.twofa-key {
    margin: 0 0 var(--spacing-md) 0;
    padding: var(--spacing-md);
    border: 1px solid var(--color-line-strong);
    border-radius: var(--portal-radius-sm);
    background-color: var(--color-surface-2);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
    font-size: 0.9375rem;
    letter-spacing: 0.14em;
    line-height: 1.7;
    color: var(--color-text);
    word-break: break-all;
    -webkit-user-select: all;
    user-select: all;
}

/* ------------------------------------------------------- code entry      */

/* Wide tracking so six digits read as six digits, not as a number. */
.twofa-code-input {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
    font-size: 1.5rem;
    letter-spacing: 0.4em;
    text-indent: 0.4em; /* balance the trailing letter-space so it looks centred */
    text-align: center;
}

.twofa-key-input {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
    font-size: 1.0625rem;
    letter-spacing: 0.18em;
    text-align: center;
}

/* ------------------------------------------------------------ messages   */

.twofa-message {
    margin: var(--spacing-md) 0 0 0;
    font-size: 0.875rem;
    line-height: 1.6;
    color: var(--color-text-2);
}

/* Validation borrows the signal, exactly as the marketing site's .field-error
   does. The brick red stays reserved for destructive and hard-failure states. */
.twofa-message-error {
    color: var(--color-signal);
}

.twofa-message.hidden {
    display: none !important;
}

/* --------------------------------------------------------- backup codes  */

.twofa-codes {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
    gap: var(--spacing-sm);
    margin: 0 0 var(--spacing-lg) 0;
    padding: 0;
    list-style: none;
}

.twofa-code {
    padding: var(--spacing-sm) var(--spacing-xs);
    border: 1px solid var(--color-line);
    border-radius: var(--portal-radius-sm);
    background-color: var(--color-surface-2);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
    font-size: 0.9375rem;
    letter-spacing: 0.08em;
    text-align: center;
    color: var(--color-text);
    word-break: break-all;
    -webkit-user-select: all;
    user-select: all;
}

.twofa-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacing-md);
    margin-bottom: var(--spacing-lg);
}

.twofa-actions .btn {
    flex: 1 1 140px;
}

.twofa-note {
    margin: 0 0 var(--spacing-lg) 0;
    padding-left: var(--spacing-md);
    border-left: 2px solid var(--color-line-strong);
    color: var(--color-text-2);
    font-size: 0.875rem;
    line-height: 1.7;
}

/* ------------------------------------------------------------- footer    */

.twofa-email {
    display: block;
    margin-bottom: var(--spacing-sm);
    color: var(--color-text-2);
    word-break: break-all;
}

/* A button that reads as a link. It is a <button> and not an <a> because it
   performs an action rather than going anywhere, and an href-less anchor is
   not keyboard operable. */
.link-button {
    display: inline;
    padding: 0;
    border: 0;
    background: none;
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
    color: var(--color-text);
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: var(--color-line-strong);
    cursor: pointer;
    transition: color var(--transition), text-decoration-color var(--transition);
}

.link-button:hover {
    color: var(--color-signal);
    text-decoration-color: var(--color-signal);
}

/* ------------------------------------------- account settings: 2FA line  */

.twofa-settings {
    border-top: 1px solid var(--color-line);
    padding-top: var(--spacing-md);
}

.twofa-settings-line {
    margin: 0;
    color: var(--color-text-2);
    font-size: 0.875rem;
}

.twofa-settings-warn {
    color: var(--color-signal);
}

/* ---------------------------------------------------------- narrow view  */

@media (max-width: 480px) {
    .twofa-qr-frame {
        width: 176px;
        padding: 10px;
    }

    .twofa-code-input {
        font-size: 1.25rem;
        letter-spacing: 0.3em;
        text-indent: 0.3em;
    }

    .twofa-codes {
        grid-template-columns: repeat(auto-fill, minmax(108px, 1fr));
    }

    .twofa-actions .btn {
        flex: 1 1 100%;
    }
}

/* ---------------------------------------------------------- responsive   */

/* ---------------------------------------------------------------------------
   APP SHELL CORRECTION — this rule does not belong to the dashboards and
   SHOULD BE MOVED INTO styles.css by whoever owns that file.

   styles.css sets `.app-container { display: flex }` (a row) and, below
   1024px, turns `.mobile-header` from display:none into display:flex. But
   `.mobile-header` is a SIBLING of `.sidebar` and `.main-content` inside that
   row, so it becomes a full-height COLUMN rather than a bar across the top:
   measured on a 400px viewport it took 268px, leaving the entire portal — not
   just these dashboards — 132px wide to render in.

   Stacking the shell vertically below the same 1024px breakpoint puts the
   header back across the top and gives the content the full width. The
   sidebar is position:absolute at this breakpoint, so it is out of flow and
   unaffected.
   --------------------------------------------------------------------------- */
@media (max-width: 1024px) {
    .app-container {
        flex-direction: column;
    }

    .mobile-header {
        width: 100%;
        flex: 0 0 auto;
    }

    .main-content {
        width: 100%;
        min-height: 0;
    }
}

@media (max-width: 720px) {
    .meta-row {
        grid-template-columns: 1fr;
        gap: 2px;
    }
}

@media (max-width: 560px) {
    /* The stepper turns into a vertical list: four labels will not fit
       side by side on a phone without truncating them. */
    .stepper {
        flex-direction: column;
        gap: 0;
    }

    .step {
        flex-direction: row;
        align-items: center;
        gap: var(--spacing-md);
        text-align: left;
        padding: var(--spacing-sm) 0 var(--spacing-sm) 0;
    }

    .step + .step::before {
        top: 0;
        bottom: 50%;
        left: 13px;
        right: auto;
        width: 1px;
        height: auto;
    }

    .step-label {
        margin-top: 0;
        flex: 1 1 auto;
    }

    .step-state {
        margin-top: 0;
        flex-shrink: 0;
    }

    .media-grid {
        grid-template-columns: 1fr;
    }

    .field-grid {
        grid-template-columns: 1fr;
    }

    .panel-head {
        padding: var(--spacing-md);
    }

    .panel-body,
    .upload-host {
        padding: var(--spacing-md);
    }

    .client-strip-item {
        padding: var(--spacing-md);
    }

    .form-actions {
        align-items: stretch;
        flex-direction: column;
    }

    .form-actions .btn {
        width: 100%;
    }
}

@media (max-width: 420px) {
    .client-strip-head {
        flex-direction: column;
    }

    .row-actions .btn,
    .client-strip-head .btn {
        width: 100%;
    }

    .media-actions {
        flex-direction: column;
        align-items: stretch;
    }

    .media-actions .comment-count {
        order: -1;
    }

    /* The phase chips are the widest fixed run of tracked type on a phone. */
    .portal-view .phase-option {
        width: 100%;
    }
}

@media (prefers-reduced-motion: reduce) {
    .state-spinner,
    .loading-spinner,
    .btn-loader::before {
        animation: none;
    }
}
