@layer components {
    .form-layout {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 200px;
        gap: var(--space-7);
        align-items: start;
        margin-top: var(--space-5);
    }
    .form-layout-main {
        display: flex;
        flex-direction: column;
        gap: var(--space-5);
        max-width: 32em;
    }
    .form-layout-aside {
        display: flex;
        flex-direction: column;
        gap: var(--space-3);
    }
    .form-actions {
        margin-top: var(--space-2);
    }

    .row {
        display: flex;
        flex-direction: column;
        gap: var(--space-2);
        text-align: left;
    }
    /* Anchor for absolutely-positioned children (e.g. floating
     * autocomplete results sitting under the input). */
    .row-floating { position: relative; }
    .row-label {
        font-weight: var(--weight-semibold);
        font-size: var(--text-base);
        color: var(--fg);
    }
    .row-value {
        margin: 0;
        font-size: var(--text-base);
        color: var(--fg);
        display: flex;
        align-items: center;
        gap: var(--space-2);
    }

    /* Read-only fact list — `Label: value` inline. Labels are dimmed,
     * values keep the body color. Used on the settings page for
     * email, id, account-created, and provider. */
    .row-inline-list {
        display: flex;
        flex-direction: column;
        gap: var(--space-2);
        margin: 0;
    }
    .row-inline {
        display: flex;
        align-items: baseline;
        gap: var(--space-2);
        font-size: var(--text-base);
    }
    .row-inline dt {
        color: var(--fg-muted);
        font-weight: var(--weight-medium);
    }
    .row-inline dd {
        margin: 0;
        color: var(--fg);
        display: inline-flex;
        align-items: center;
        gap: var(--space-2);
    }

    .row input:where([type="text"], [type="password"], [type="email"], [type="url"], [type="search"], [type="tel"], [type="number"]),
    .row input:not([type]),
    .row select,
    .field input:where([type="text"], [type="password"], [type="email"], [type="url"], [type="search"], [type="tel"], [type="number"]),
    .field input:not([type]),
    .field select,
    .input {
        padding: var(--space-2) var(--space-3);
        border: 1px solid var(--border);
        border-radius: var(--radius-md);
        background: var(--bg);
        color: var(--fg);
        font: inherit;
        transition: border-color var(--duration-fast) var(--easing-out),
                    box-shadow var(--duration-fast) var(--easing-out);
    }
    .row input:focus,
    .row select:focus,
    .field input:focus,
    .field select:focus,
    .input:focus {
        outline: none;
        border-color: var(--accent);
        box-shadow: 0 0 0 3px var(--accent-bg);
    }

    /* Invalid state. Caller sets aria-invalid="true" on the
     * control when validation fails; the red border outlives
     * the focus ring, and the .form-error message below
     * carries the wording. */
    .row input[aria-invalid="true"],
    .row select[aria-invalid="true"],
    .field input[aria-invalid="true"],
    .field select[aria-invalid="true"],
    .input[aria-invalid="true"] {
        border-color: var(--danger);
    }
    .row input[aria-invalid="true"]:focus,
    .row select[aria-invalid="true"]:focus,
    .field input[aria-invalid="true"]:focus,
    .field select[aria-invalid="true"]:focus,
    .input[aria-invalid="true"]:focus {
        border-color: var(--danger);
        box-shadow: 0 0 0 3px var(--danger-bg);
    }

    /* .input-group — flex container styled to look like a single
     * field, with a static prefix on the left and an editable
     * input on the right. Used for URL-shaped controls where the
     * prefix is fixed (e.g. wikilayer.org/owner/) and only the
     * tail is user-editable. The border lives on the group; the
     * inner input strips its own border so the two visually fuse.
     * Invalid + focus states propagate up via :has(). */
    .input-group {
        display: flex;
        align-items: stretch;
        border: 1px solid var(--border);
        border-radius: var(--radius-md);
        background: var(--bg);
        overflow: hidden;
        transition: border-color var(--duration-fast) var(--easing-out),
                    box-shadow var(--duration-fast) var(--easing-out);
    }
    .input-group:focus-within {
        border-color: var(--accent);
        box-shadow: 0 0 0 3px var(--accent-bg);
    }
    .input-group:has(input[aria-invalid="true"]) {
        border-color: var(--danger);
    }
    .input-group:has(input[aria-invalid="true"]):focus-within {
        border-color: var(--danger);
        box-shadow: 0 0 0 3px var(--danger-bg);
    }
    .input-prefix {
        display: flex;
        align-items: center;
        padding: var(--space-2) var(--space-3);
        background: var(--bg-hover);
        color: var(--fg-muted);
        border-right: 1px solid var(--border);
        white-space: nowrap;
        user-select: none;
    }
    .row .input-group > input,
    .field .input-group > input,
    .input-group > input {
        padding: var(--space-2) var(--space-3);
        border: 0;
        border-radius: 0;
        background: transparent;
        color: var(--fg);
        font: inherit;
        flex: 1;
        min-width: 0;
    }
    .row .input-group > input:focus,
    .field .input-group > input:focus,
    .input-group > input:focus {
        outline: none;
        box-shadow: none;
    }

    /* .input-action — interactive trailing element in an .input-group.
     * Symmetric to .input-prefix: where the prefix is a static label
     * on the left, the action is a button (or anchor styled as one)
     * on the right that submits, clears, or otherwise acts on the
     * input's value. Drops the button's own border / radius so it
     * fuses with the group's outer frame; the dividing line is the
     * group's own seam. Pair with the existing .button class for
     * baseline button styling. */
    .input-group > .input-action {
        border: 0;
        border-left: 1px solid var(--border);
        border-radius: 0;
        flex-shrink: 0;
    }
    .input-group > .input-action:focus,
    .input-group > .input-action:focus-visible {
        box-shadow: none;
    }

    /* Custom <select> chevron — the native macOS / Chrome chevron is
     * glued to the right edge with no breathing room. We hide it
     * (`appearance:none`) and paint our own from two diagonal gradient
     * stripes; that way the chevron colour follows the CSS variable
     * (theme-aware) without juggling SVG data-URLs. */
    .row select,
    .field select {
        appearance: none;
        -webkit-appearance: none;
        padding-right: calc(var(--space-3) * 2 + 0.5em);
        background-image:
            linear-gradient(45deg, transparent 50%, var(--fg-muted) 50%),
            linear-gradient(135deg, var(--fg-muted) 50%, transparent 50%);
        background-position:
            right calc(var(--space-3) + 0.32em) center,
            right var(--space-3) center;
        background-size: 0.32em 0.32em;
        background-repeat: no-repeat;
    }

    /* .choice-group — fieldset of mutually-exclusive radios where each
     * option is a clickable card with an icon, a name, and a short
     * description. Used on /manage for visibility, can be used for any
     * "pick one with explanation" form. Selected option lifts via
     * :has(input:checked). Falls back to a plain look on browsers
     * without :has(), still functional. */
    .choice-group {
        border: 0;
        padding: 0;
        margin: 0;
        display: flex;
        flex-direction: column;
        gap: var(--space-2);
    }
    .choice-group legend {
        margin-bottom: var(--space-2);
        padding: 0;
    }
    .choice {
        display: grid;
        grid-template-columns: auto auto 1fr;
        column-gap: var(--space-3);
        row-gap: 0;
        align-items: center;
        padding: var(--space-3);
        border: 1px solid var(--border);
        border-radius: var(--radius-md);
        cursor: pointer;
        transition: border-color var(--duration-fast) var(--easing-out),
                    background var(--duration-fast) var(--easing-out);
    }
    .choice:hover {
        background: var(--bg-hover);
    }
    .choice:has(input:checked) {
        border-color: var(--accent);
        background: var(--accent-bg);
    }
    .choice > input[type="radio"] {
        grid-row: 1 / span 2;
        margin: 0;
        accent-color: var(--accent);
    }
    .choice > .icon {
        grid-row: 1 / span 2;
        width: 20px;
        height: 20px;
        color: var(--fg-muted);
    }
    .choice:has(input:checked) > .icon {
        color: var(--accent);
    }
    .choice-name {
        font-weight: var(--weight-semibold);
        color: var(--fg);
    }
    .choice-desc {
        font-size: var(--text-sm);
        color: var(--fg-muted);
    }

    /* Compact alternative to .row used when the field doesn't share
     * a column with read-only siblings. */
    .field {
        display: flex;
        flex-direction: column;
        gap: var(--space-2);
        margin-bottom: var(--space-4);
        max-width: 28em;
        text-align: left;
    }
    .field-label {
        font-size: var(--text-sm);
        font-weight: var(--weight-medium);
        color: var(--fg-muted);
    }

    /* Inline validation message under a field. Caller renders the
     * <p class="form-error"> conditionally; the colour signals
     * "this is why the form didn't save", and the text-sm keeps it
     * a notch quieter than the field label so the eye reads label
     * first, error second. */
    .form-error {
        margin: var(--space-1) 0 0;
        color: var(--danger);
        font-size: var(--text-sm);
    }

    @media (max-width: 720px) {
        .form-layout {
            grid-template-columns: 1fr;
            gap: var(--space-5);
        }
        .form-layout-aside {
            order: -1;
            align-items: flex-start;
        }
    }
}
