/*
    Sanderwell site styles.

    Loaded after tokens.css, which owns the palette, type scale, focus ring, reset, skip link, and
    reduced-motion opt-out. Nothing here should contain a raw hex value or a raw pixel font size --
    if one is needed it belongs in tokens.css as a variable first.

    Layout is mobile-first: the base rules describe a 360px screen and the media queries add back
    what a wider one can afford. That ordering is deliberate, because the narrow case is the one
    that breaks silently when it is written as an afterthought.
*/

/* ============================================================== self-hosted faces */

/*
    Both files are committed -- see wwwroot/fonts/README.md for versions and licences. They are the
    full variable builds, so each covers its whole weight range in one request. Should either ever go
    missing the browser falls through to the next family in the stack and the site renders in Georgia
    and system-ui with no error and no layout break.

    font-display: swap so text is readable during the download rather than invisible. On a marketing
    page, a flash of a fallback face beats a flash of nothing.
*/
@font-face {
    font-family: "Source Serif 4";
    src: url("/fonts/source-serif-4-latin.woff2") format("woff2");
    font-weight: 400 700;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: Inter;
    src: url("/fonts/inter-latin.woff2") format("woff2");
    font-weight: 400 700;
    font-style: normal;
    font-display: swap;
}

/* ==================================================================== primitives */

.page-width {
    max-width: var(--page-max);
    margin-inline: auto;
    padding-inline: var(--space-5);
}

/*
    Wide content -- tables, the credibility strip -- scrolls inside its own box. The page body must
    never scroll sideways, so anything that cannot shrink is given somewhere else to overflow into.
*/
.scroll-x {
    overflow-x: auto;
}

.measure {
    max-width: var(--measure);
}

.visually-hidden-focusable:not(:focus):not(:focus-within) {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
}

/* ========================================================================= type */

h1,
h2,
h3,
h4 {
    font-family: var(--font-display);
    font-weight: 600;
    line-height: var(--leading-tight);
    letter-spacing: -0.015em;
    margin: 0 0 var(--space-4);
    text-wrap: balance;
}

/*
    clamp rather than a breakpoint: the headline has to survive 360px, and stepping it down at a
    media query leaves a band of widths on either side where it is either cramped or oversized.
*/
h1 {
    font-size: clamp(2rem, 1.35rem + 2.9vw, var(--text-4xl));
}

h2 {
    font-size: clamp(1.625rem, 1.25rem + 1.7vw, var(--text-3xl));
}

h3 {
    font-size: var(--text-xl);
}

h4 {
    font-size: var(--text-lg);
}

p {
    margin: 0 0 var(--space-4);
    max-width: var(--measure);
}

a {
    color: var(--accent-deep);
    text-underline-offset: 0.15em;
}

a:hover {
    color: var(--ink);
}

.eyebrow {
    display: block;
    font-size: var(--text-eyebrow);
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--accent-deep);
    margin-bottom: var(--space-3);
}

.lede {
    font-size: var(--text-lg);
    color: var(--muted);
    max-width: var(--measure);
}

/* ====================================================================== buttons */

.button {
    display: inline-block;
    padding: var(--space-3) var(--space-5);
    border: 1px solid transparent;
    border-radius: var(--radius);
    font: inherit;
    font-weight: 600;
    line-height: var(--leading-snug);
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}

.button--primary {
    background: var(--accent-deep);
    border-color: var(--accent-deep);
    color: var(--surface);
}

.button--primary:hover {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--surface);
}

.button--ghost {
    background: transparent;
    border-color: var(--hairline);
    color: var(--ink);
}

.button--ghost:hover {
    border-color: var(--ink);
    color: var(--ink);
}

/* Reversed sections need a button that reads against the dark ink ground. */
.reversed .button--ghost {
    border-color: var(--on-ink-hairline);
    color: var(--on-ink);
}

.reversed .button--ghost:hover {
    border-color: var(--on-ink);
    color: var(--on-ink);
}

.button-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-top: var(--space-6);
}

/*
    At the narrowest widths the buttons go full width and stack. Two half-width buttons at 360px
    leave each with room for about one and a half words.
*/
@media (max-width: 420px) {
    .button-row {
        display: grid;
    }
}

/* ===================================================================== sections */

.section {
    padding-block: var(--space-8);
}

.section--paper {
    background: var(--paper);
}

.section--ink {
    background: var(--ink);
    color: var(--on-ink);
}

.section--ink .lede,
.section--ink .eyebrow {
    color: var(--on-ink-muted);
}

.section--ink a {
    color: var(--on-ink);
}

.section__head {
    max-width: var(--measure);
    margin-bottom: var(--space-6);
}

@media (min-width: 720px) {
    .section {
        padding-block: var(--space-9);
    }
}

/* ======================================================================== cards */

.cards {
    display: grid;
    gap: var(--space-5);
    list-style: none;
    margin: 0;
    padding: 0;
}

@media (min-width: 640px) {
    .cards--2 {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 900px) {
    .cards--3 {
        grid-template-columns: repeat(3, 1fr);
    }

    .cards--4 {
        grid-template-columns: repeat(2, 1fr);
    }
}

.card {
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    padding: var(--space-5);
}

.card > :last-child {
    margin-bottom: 0;
}

.card__title {
    font-size: var(--text-lg);
    margin-bottom: var(--space-2);
}

.card p {
    color: var(--muted);
    margin-bottom: 0;
}

/*
    .cards zeroes its own margins so a grid sits flush under a .section__head. That leaves anything
    following it in the same section touching the last row of cards, so restore the gap here rather
    than on the individual pages that happen to do it.
*/
.cards + * {
    margin-top: var(--space-7);
}

/* ========================================================================= hero */

.hero {
    padding-block: var(--space-8) var(--space-7);
    border-bottom: 1px solid var(--hairline);
}

.hero h1 {
    max-width: 20ch;
}

.hero .lede {
    font-size: clamp(var(--text-base), 0.9rem + 0.5vw, var(--text-xl));
}

@media (min-width: 720px) {
    .hero {
        padding-block: var(--space-9) var(--space-8);
    }
}

/* ================================================================= feature list */

/*
    Rules between features rather than boxes around them. The five capabilities are one argument
    read top to bottom, and putting each in its own card invites the eye to treat them as
    interchangeable tiles and skip four of them.
*/
.feature-list {
    display: grid;
    gap: var(--space-6);
}

.feature {
    padding-top: var(--space-6);
    border-top: 1px solid var(--hairline);
}

.feature:first-child {
    padding-top: 0;
    border-top: 0;
}

.feature h3 {
    margin-bottom: var(--space-3);
}

.feature p {
    color: var(--muted);
    margin-bottom: 0;
}

@media (min-width: 900px) {
    .feature {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
        gap: var(--space-6);
    }

    .feature h3 {
        margin-bottom: 0;
    }
}

/* ================================================================== proof strip */

.proof {
    display: grid;
    gap: var(--space-5);
    margin: 0;
}

.proof__item {
    padding-top: var(--space-4);
    border-top: 1px solid var(--on-ink-hairline);
}

.proof dt {
    font-family: var(--font-display);
    font-size: var(--text-lg);
    font-weight: 600;
    margin-bottom: var(--space-2);
}

.proof dd {
    margin: 0;
    color: var(--on-ink-muted);
    font-size: var(--text-sm);
}

@media (min-width: 640px) {
    .proof {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--space-6);
    }
}

@media (min-width: 1000px) {
    .proof {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

/* ======================================================================== forms */

.form-layout {
    display: grid;
    gap: var(--space-8);
}

@media (min-width: 900px) {
    .form-layout {
        grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
        gap: var(--space-9);
        align-items: start;
    }
}

.field {
    margin-bottom: var(--space-5);
    max-width: 34rem;
}

.field label {
    display: block;
    font-weight: 600;
    font-size: var(--text-sm);
    margin-bottom: var(--space-2);
}

.field__optional {
    font-weight: 400;
    color: var(--muted);
}

/*
    Hints sit between the label and the input, not after it. Placed below, a hint is read out only
    after the field has already been filled in, which is too late to be a hint.

    The top margin is zero and the gap above comes from the label's own margin-bottom. This started
    as a negative top margin, which worked only while the markup cooperated: the hints were in fact
    rendered after their inputs, so the negative value hauled each one up over the input's bottom
    border and printed the hint through it. Spacing that turns into overlap when an element moves is
    a trap, so the rule no longer depends on what precedes it.
*/
.field__hint {
    font-size: var(--text-sm);
    color: var(--muted);
    margin: 0 0 var(--space-2);
}

.input {
    display: block;
    width: 100%;
    padding: var(--space-3);
    border: 1px solid var(--muted);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    /*
        font: inherit rather than a size, because a font size below 16px makes iOS Safari zoom the
        whole page on focus and leave it zoomed.
    */
    font: inherit;
}

.input--narrow {
    max-width: 12rem;
}

.input--area {
    resize: vertical;
    min-height: 8rem;
}

/*
    A red border is not sufficient on its own -- it fails for a colour-blind user and disappears in
    print. The thicker border is the redundant signal, alongside the message text and aria-invalid.
*/
.input[aria-invalid="true"] {
    border-color: var(--error);
    border-width: 2px;
}

/*
    The wrapper each field's messages land in. It is present whether or not the field is valid, so
    aria-describedby always points at something real; see the comment on the first field in
    Contact.razor for why the id is here rather than on ValidationMessage itself.

    :empty is what makes that free. An always-present element carrying margin-top would space every
    valid field as though it had failed, so the empty case is taken out of the flow entirely. It
    matches only when the element has no children at all, whitespace included -- which is why the
    markup keeps the wrapper and its ValidationMessage on a single line.
*/
.field__error {
    display: block;
    margin-top: var(--space-2);
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--error);
}

.field__error:empty {
    display: none;
}

/*
    A field that fails two rules at once gets one span per message, written by the Worker. Stacked
    rather than run together, so each reads as its own instruction; the gap is deliberately smaller
    than the gap between the field and its first message.
*/
.field__error > span {
    display: block;
}

.field__error > span + span {
    margin-top: var(--space-1);
}

.form-errors {
    border: 2px solid var(--error);
    border-radius: var(--radius);
    padding: var(--space-5);
    margin-bottom: var(--space-6);
    max-width: 34rem;
}

.form-errors__title {
    font-size: var(--text-lg);
    color: var(--error);
    margin-bottom: var(--space-3);
}

.form-errors__list {
    margin: 0;
    padding-left: var(--space-5);
}

.form-errors__list a {
    color: var(--error);
    font-weight: 600;
}

/*
    The honeypot. Positioned off-canvas rather than display:none or visibility:hidden, because both
    of those are trivially detectable and the more capable submitters check for them.
*/
.honeypot {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.form-aside {
    background: var(--paper);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    padding: var(--space-5);
}

.form-aside h2 {
    font-size: var(--text-xl);
}

.steps {
    margin: 0 0 var(--space-4);
    padding-left: var(--space-5);
    display: grid;
    gap: var(--space-3);
}

.steps li {
    color: var(--muted);
}

.steps strong {
    color: var(--ink);
    font-weight: 600;
}

/* ===================================================================== footnote */

.footnote {
    font-size: var(--text-sm);
    color: var(--muted);
    margin-top: var(--space-6);
    margin-bottom: 0;
}

/* ================================================================== site header */

.site-header {
    border-bottom: 1px solid var(--hairline);
    /*
        Opaque, not transparent with a blur. The header overlaps content once it is sticky, and a
        translucent bar puts body text behind the navigation at partial contrast -- a ratio that
        changes as the page scrolls is a ratio that cannot be measured, let alone claimed.
    */
    background: var(--surface);
}

.site-header__inner {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding-block: var(--space-4);
}

/*
    flex-start stops the link stretching to the full width of the stacked mobile header, where the
    cross axis is horizontal. It has to be released at 720px, where the cross axis turns vertical
    and the same value would pin the brand to the top of the row instead of its shared centre line.
*/
.site-header__brand {
    text-decoration: none;
    align-self: flex-start;
}

.site-header__nav {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

/*
    Hidden below the breakpoint where the header collapses to two rows. The hero's own "Request a
    demo" is the identical button roughly one thumb below it, so carrying a second one here spends
    a third of a 412x915 viewport on chrome to repeat what is already on screen. It comes back at
    720px, where it costs a row nothing and the hero button is off to one side.
*/
.site-header__cta {
    display: none;
}

.site-nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-5);
    list-style: none;
    margin: 0;
    padding: 0;
}

.site-nav__link {
    display: inline-block;
    padding-block: var(--space-1);
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--ink-2);
    text-decoration: none;
}

.site-nav__link:hover {
    color: var(--ink);
    text-decoration: underline;
}

/*
    The current page is marked twice over: this rule for a sighted user and aria-current="page" in
    the markup for everyone else. A border rather than colour alone, so the distinction survives
    both a colour-blind reader and a monochrome print.
*/
.site-nav__link--current {
    color: var(--ink);
    font-weight: 600;
    box-shadow: inset 0 -2px 0 0 var(--accent);
}

@media (min-width: 720px) {
    /*
        center, matching the lockup. This cannot stay baseline now that .lockup centres internally:
        a flex container with no baseline-aligned children takes its baseline from its startmost
        item, so baseline here would align the MARK'S BASE COURSE to the nav links' line and float
        the wordmark about 6px above them.

        Centring is coherent rather than merely a workaround, because the brand box is now the
        mark's height with the wordmark centred inside it, and the nav centres its own links and CTA
        by the align-items below. Every element in the row is then centred on the same axis.
    */
    .site-header__inner {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
    }

    /* Defer to the row's align-items; see the flex-start note above. */
    .site-header__brand {
        align-self: auto;
    }

    .site-header__nav {
        flex-direction: row;
        align-items: center;
        gap: var(--space-6);
    }

    .site-header__cta {
        display: inline-block;
    }

    .site-nav {
        gap: var(--space-6);
    }

    /*
        Sticky only from this breakpoint, which is exactly where the header collapses to a single
        row. Below it the brand and the navigation stack onto two rows about 120px tall, and pinning
        that to the top of a 360x640 phone would permanently spend a fifth of the viewport on
        navigation the visitor is not using. The CTA stays reachable there by scrolling back up,
        which on a page this short is a flick.

        z-index sits below the skip link's 100 (tokens.css) on purpose: the skip link is absolutely
        positioned near the top-left and would otherwise appear underneath the very header it exists
        to jump past.
    */
    .site-header {
        position: sticky;
        top: 0;
        z-index: 20;
    }
}

/* ================================================================== site footer */

.site-footer {
    background: var(--ink);
    color: var(--on-ink);
    padding-block: var(--space-7) var(--space-5);
    margin-top: var(--space-8);
}

.site-footer__inner {
    display: grid;
    gap: var(--space-7);
}

.site-footer__blurb {
    color: var(--on-ink-muted);
    font-size: var(--text-sm);
    margin-top: var(--space-4);
    margin-bottom: 0;
    max-width: 34ch;
}

.site-footer__nav {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-6);
}

.site-footer__heading {
    font-family: var(--font-body);
    font-size: var(--text-eyebrow);
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--on-ink-muted);
    margin-bottom: var(--space-3);
}

.site-footer__links {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--space-2);
}

.site-footer__links a {
    color: var(--on-ink);
    font-size: var(--text-sm);
    text-decoration: none;
}

.site-footer__links a:hover {
    color: var(--on-ink);
    text-decoration: underline;
}

.site-footer__legal {
    border-top: 1px solid var(--on-ink-hairline);
    margin-top: var(--space-7);
    padding-top: var(--space-5);
}

.site-footer__legal p {
    color: var(--on-ink-muted);
    font-size: var(--text-eyebrow);
    margin: 0;
    max-width: none;
}

.site-footer__legal p + p {
    margin-top: var(--space-2);
}

@media (min-width: 900px) {
    .site-footer__inner {
        grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
        align-items: start;
    }
}

/* =================================================================== brand lockup */

/*
    center, not baseline. Baseline ties the mark to the FIRST line of the text column, which is wrong
    wherever a descriptor is present: in the footer the text block is roughly 50px tall against a
    30px mark, so "Systems, LLC" was stranded in the notch under the mark's base and the mark read as
    standing beside the word rather than anchoring the block.

    The cost this pays, deliberately: the mark's base course no longer sits on the wordmark's
    baseline, so the two ground lines in the lockup are not one rule. Centring won on how it looks at
    both sizes -- see docs/business/brand/explore/lockup-alignment.html, option C.

    Consequence worth knowing before changing this back: with align-items:center no child
    participates in baseline alignment, so per the flexbox spec this container's baseline comes from
    its startmost item instead. That is the SVG, whose baseline is its bottom edge. Anything that
    baseline-aligns .lockup from outside is therefore aligning the mark's base, not the wordmark,
    which is why .site-header__inner centres its row too.
*/
.lockup {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    color: var(--ink);
    /* The lockup is usually inside an <a>; never let it carry the link underline. */
    text-decoration: none;
}

/*
    HEIGHT only; width follows the viewBox's 32:26 aspect. Never set both: a square box letterboxes
    the ink, which shrinks the mark without shrinking the space it occupies.

    Height rather than width, because height is what has to stay locked to the type -- it sets the
    header's row height and the mark's optical weight beside the wordmark. The wellhead is wider
    than the arch it replaced (32:26 against 26:23), so constraining the width instead would have
    silently shortened the mark by 8% and dropped it below the wordmark's cap height.

    The em here resolves against .lockup's inherited font size (1rem), NOT the wordmark's, because
    .lockup__mark is a sibling of .lockup__name rather than a child. So the mark does not track
    --text-2xl or --text-xl: change either and this has to be re-tuned by hand, which is why the
    full and compact sizes are two unrelated-looking numbers.
*/
.lockup__mark {
    height: 1.86em;
    width: auto;
    flex: none;
}

.lockup__text {
    display: flex;
    flex-direction: column;
    line-height: var(--leading-tight);
}

.lockup__name {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--text-2xl);
    letter-spacing: -0.01em;
}

.lockup__descriptor {
    font-size: var(--text-eyebrow);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
    margin-top: var(--space-1);
}

/*
    Tuned by eye against the 22px wordmark, not derived: at 1.64em the mark is about 26px against a
    ~14.5px cap height, so the crown clears the ascenders comfortably rather than sitting just above
    them. Header row height is set by the CTA button, not by this.
*/
.lockup--compact .lockup__mark {
    height: 1.64em;
}

.lockup--compact .lockup__name {
    font-size: var(--text-xl);
}

/* One declaration is enough: the mark is drawn in currentColor. */
.lockup--reversed {
    color: var(--on-ink);
}

.lockup--reversed .lockup__descriptor {
    color: var(--on-ink-muted);
}
