.form {
    display: grid;
    gap: 16px;
    background: var(--trim-white);
    border: 1px solid var(--rule);
    border-radius: 1px;
    padding: clamp(22px, 4vw, 34px);
}

/* The honeypot (lead/_screening): off screen, out of the tab order, and
   hidden from screen readers, so only a bot fills it. */
.form-trap {
    position: absolute;
    left: -10000px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
}

/* Paired fields line up on their inputs, not their labels: a label that wraps
   to two lines would otherwise drop its own input a line below its neighbour's.
   Bottom-aligning the contents fixes it for any label, at any width. */
/* Paired fields share one set of rows: labels, controls, messages. A label
   that wraps to two lines grows row one for both halves instead of pushing
   its own control down, and an inline error hangs in row three instead of
   lifting its control above the field beside it. */
.form-row {
    grid-template-rows: auto auto auto;
    row-gap: 6px;
}

.form-row > .field {
    grid-row: span 3;
    grid-template-rows: subgrid;
    gap: 0;
}

.form-row > .field > .field-label {
    align-self: end;
}

/* Street and ZIP are one address: the street gets the room, the ZIP gets five
   characters' worth. They stay paired at every width, a lone ZIP on its own
   row reads as a second question rather than the end of the first. */
.form-row.is-address {
    grid-template-columns: minmax(0, 1fr) 7.5rem;
}

.field {
    display: grid;
    gap: 6px;
}

.field label,
.field legend {
    font-size: var(--type-ui);
    font-weight: 500;
}

.field input,
.field select,
.field textarea {
    width: 100%;
    padding: 11px 13px;
    border: 1px solid var(--umber-soft);
    border-radius: 2px;
    background: var(--trim-white);
    font: inherit;
    font-size: var(--type-body);
    color: var(--umber-ink);
}

/* Browser-default placeholder grey measures 4.2:1 on trim white. A step
   lighter than umber-soft clears 4.5:1 and still reads as a hint. */
.field input::placeholder,
.field textarea::placeholder {
    color: color-mix(in srgb, var(--umber-soft) 90%, var(--trim-white));
    opacity: 1;
}

.field-check {
    grid-template-columns: 1.15em 1fr;
    gap: 10px 12px;
    align-items: start;
}

.field-check input[type="checkbox"] {
    appearance: none;
    flex: none;
    width: 1.15em;
    height: 1.15em;
    margin: 0.2em 0 0;
    padding: 0;
    border: 1.5px solid var(--umber-soft);
    border-radius: 2px;
    background: var(--trim-white) center / 100% no-repeat;
}

/* Drawn, not native, so the box is the same size and shape in every state. */
.field-check input[type="checkbox"]:checked {
    border-color: var(--door-juniper);
    background-color: var(--door-juniper);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-7' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.field-check label {
    font-weight: 400;
    font-size: var(--type-label);
    color: var(--umber-soft);
    line-height: 1.5;
}

/* The disclosure the checkbox points at with aria-describedby. It is no longer
   inside the <label>, so it needs its own place in the two-column grid. */
/* Rails wraps an invalid checkbox and its label in div.field_with_errors. As
   the lone grid item it took the checkbox's column and left the label an
   inline box, so the label's lines inherited the wrapper's taller strut.
   Let the checkbox and label be the grid items again. */
.field-check .field_with_errors {
    display: contents;
}

/* The checkmark is a background image, which forced-colors mode drops, so a
   ticked box would look empty. Hand the box back to the system there. */
@media (forced-colors: active) {
    .field-check input[type="checkbox"] {
        appearance: auto;
    }
}

/* An unticked required box in error is red: border, a pale red fill, and the
   whole consent, links and disclosure included. Ticking it clears the state. */
.field-check.field-invalid:not(:has(input:checked)) input[type="checkbox"] {
    border-color: var(--danger);
    background-color: color-mix(in srgb, var(--danger) 14%, var(--trim-white));
}

.field-check.field-invalid:not(:has(input:checked)) .consent-text,
.field-check.field-invalid:not(:has(input:checked)) .consent-text a,
.field-check.field-invalid:not(:has(input:checked)) .consent-detail,
.field-check.field-invalid:not(:has(input:checked)) label {
    color: var(--danger);
}

.field-check.field-invalid:has(input:checked) .field-error {
    display: none;
}

.field-check .consent-text,
.field-check .consent-detail,
.field-check .field-error,
.field-check .hint {
    grid-column: 2;
}

.field-check .consent-text {
    margin: 0;
    font-size: var(--type-label);
    line-height: 1.5;
}

.field-check .consent-detail {
    margin: 0;
    font-size: var(--type-label);
    line-height: 1.5;
    color: var(--umber-soft);
}

.field input:focus,
.field select:focus,
.field textarea:focus {
    outline: 2px solid var(--umber-ink);
    outline-offset: 0;
    border-color: var(--umber-ink);
}

.field textarea {
    min-height: 120px;
    resize: vertical;
}

.field .hint {
    font-size: var(--type-label);
    color: var(--umber-soft);
}

.required-mark {
    color: var(--danger);
    margin-left: 2px;
}

.field-invalid input,
.field-invalid select,
.field-invalid textarea {
    border-color: var(--danger);
}

.field-invalid input:focus,
.field-invalid select:focus,
.field-invalid textarea:focus {
    outline-color: var(--danger);
    border-color: var(--danger);
}

/* The message can echo what was typed, and what was typed can be a 200-
   character string with no spaces in it. Break it rather than let it push the
   field's column past the form. */
.field-error {
    font-size: var(--type-label);
    color: var(--danger);
    overflow-wrap: anywhere;
}

/* The form's grid of fields dissolves into this, a plaque, not a paragraph,
   because the moment a lead form exists to reach deserves the site's own
   "something earned" object, not a bare confirmation line. Settles in a beat
   after the toast lands (see toast-in), so the two read as one sequence
   instead of two unrelated notifications firing at once. */
.form-success {
    display: flex;
    align-items: center;
    gap: 18px;
    min-height: 120px;
    color: var(--umber-ink);
    animation: form-success-in 380ms 80ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

.form-success p {
    max-width: 48ch;
}

@keyframes form-success-in {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
    .form-success {
        animation: none;
    }
}

.form-status {
    font-size: var(--type-ui);
    padding: 12px 14px;
    border-radius: 1px;
    background: var(--clapboard-putty);
}

.form-note {
    margin-top: -6px;
    font-size: var(--type-label);
    color: var(--umber-soft);
    text-wrap: pretty;
}

.field-check[hidden] {
    display: none;
}

/* The four fields most owners skip, folded away rather than dropped: a summary
   on the form's own hairline, opening the same field grid underneath it. */
.form-more {
    border-top: 1px solid var(--rule);
    padding-top: 16px;
}

.form-more summary {
    display: inline-flex;
    /* The label wraps to two lines on a phone; the marker belongs on the first
       one, not floating between them. */
    align-items: start;
    gap: 10px;
    list-style: none;
    cursor: pointer;
    font-size: var(--type-ui);
    font-weight: 500;
    color: var(--door-juniper);
}

.form-more summary::-webkit-details-marker {
    display: none;
}

/* The FAQ's 1.5px juniper plus and minus, at field scale. */
.form-more summary::before {
    content: "";
    flex: none;
    margin-top: 2px;
    width: 14px;
    height: 14px;
    background:
        linear-gradient(var(--door-juniper), var(--door-juniper)) center / 14px 1.5px no-repeat,
        linear-gradient(var(--door-juniper), var(--door-juniper)) center / 1.5px 14px no-repeat;
}

.form-more[open] summary::before {
    background:
        linear-gradient(var(--door-juniper), var(--door-juniper)) center / 14px 1.5px no-repeat;
}

.form-more summary:hover {
    color: var(--door-deep);
}

.form-more-fields {
    display: grid;
    gap: 16px;
    margin-top: 16px;
}

/* A grouped set of money boxes inside the disclosure: a legend and one note
   covering all four, so each field's own label can stay short. */
.form-group {
    display: grid;
    gap: 12px;
    margin: 0;
    padding: 16px 0 0;
    border: 0;
    border-top: 1px solid var(--rule);
}

.form-group legend {
    padding: 0;
    font-size: var(--type-ui);
    font-weight: 500;
    color: var(--umber-ink);
}

.form-group-note {
    font-size: var(--type-label);
    line-height: 1.5;
    color: var(--umber-soft);
    text-wrap: pretty;
}

@media (max-width: 600px) {
    .form-row {
        grid-template-columns: 1fr;
    }

    /* ...except the address, which stays one line with its ZIP. */
    .form-row.is-address {
        grid-template-columns: minmax(0, 1fr) 5.5rem;
        gap: 12px;
    }
}
