PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m3s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 11s
PR Checks / Build Frontend (no push) (pull_request) Successful in 49s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 5m29s
Main's staging E2E has been red since #90. Two of the four failures were the staging container serving a stale image at the moment the gate ran — apple-icon and the header lockup both check out against staging now. The other two were real, and both are the same mistake: a colour pairing verified against one ground while the element sits on another. * .heroEyebrow --brand on its own 10% tint over Sand is 4.19:1. The token clears AA on Sand, but the tint darkens the ground under the label, and that composite was never the thing measured. --brand-strong is 5.85:1. * .hiwVisual The preview panel was grounded in Sand while everything inside it is a translucent status tint — and those tints are specced to clear AA over --bg-card. Over Sand the report-card chips landed at 4.22:1 and 4.29:1. The ratio depended on a background two levels up. Moving the panel to the card surface puts them at 5.69:1 and 5.81:1; a border keeps it reading as an inset frame now that panel and card share a colour. * Footer .sectionTitle --sage flips with the theme; the footer band does not (it is teal in both). So the pairing only held in one of them — the dark sage measured 4.08:1, on every page. Now --on-sunken-muted, which is what the --on-sunken-* family exists for, as Footer.tsx's own header comment already says. * .compareHeadLabel --text-muted on the header row's tint is 4.45:1 in the dark theme. Under by a hair, same cause. Now --text-secondary. The harness gained the footer, because it had no footer and so could not have caught the one failure that appeared on all three pages. Rewriting fragments now uses each CSS module's own hash — the footer rewritten with HomeView's prefix renders unstyled, which would have made any contrast measured on it meaningless while still reporting a number. Verified: 0 AA failures in both themes across the assembled page, measured with the same probe the e2e test uses, against the real compiled CSS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
195 lines
4.1 KiB
CSS
195 lines
4.1 KiB
CSS
/*
|
|
* The footer band.
|
|
*
|
|
* --surface-sunken is deep teal in BOTH themes on purpose, so nothing in here
|
|
* may read a --text-* or --bg-* token: those invert with the theme and would
|
|
* put dark ink on the teal (or teal on teal) in one of the two. Every
|
|
* foreground below comes from the --on-sunken-* family, which is pinned to the
|
|
* band rather than to the page.
|
|
*/
|
|
|
|
.footer {
|
|
background: var(--surface-sunken);
|
|
color: var(--on-sunken);
|
|
margin-top: auto;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 3rem 1.5rem 2rem;
|
|
}
|
|
|
|
.content {
|
|
display: grid;
|
|
grid-template-columns: 1.6fr 1fr 1fr;
|
|
gap: 2rem;
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
.section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 1rem;
|
|
}
|
|
|
|
/* ─── Brand lockup ──────────────────────────────────────────────── */
|
|
|
|
.lockup {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.625rem;
|
|
margin: 0;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.lockupMark {
|
|
display: inline-flex;
|
|
flex: 0 0 auto;
|
|
/* Height drives; the artwork is taller than it is wide. */
|
|
height: 34px;
|
|
}
|
|
|
|
.lockupMark img {
|
|
display: block;
|
|
height: 100%;
|
|
width: auto;
|
|
}
|
|
|
|
/* Same wordmark as the header, inverted: "school" in the band's foreground,
|
|
"compare" in sage — the one accent that reads cleanly on deep teal. */
|
|
.wordmark {
|
|
font-family: var(--font-display);
|
|
font-size: var(--step-2);
|
|
font-weight: 700;
|
|
line-height: 1;
|
|
letter-spacing: -0.035em;
|
|
color: var(--on-sunken);
|
|
}
|
|
|
|
.wordmarkAccent {
|
|
color: var(--sage);
|
|
}
|
|
|
|
/* Key messaging, so this is the one place in the footer that takes Manrope
|
|
for a full sentence. */
|
|
.tagline {
|
|
margin: 0;
|
|
font-family: var(--font-display);
|
|
font-size: var(--step-1);
|
|
font-weight: 600;
|
|
line-height: 1.35;
|
|
letter-spacing: -0.015em;
|
|
color: var(--on-sunken);
|
|
}
|
|
|
|
.description {
|
|
margin: -0.5rem 0 0;
|
|
font-size: var(--step--1);
|
|
line-height: 1.6;
|
|
color: var(--on-sunken-muted);
|
|
max-width: 34ch;
|
|
}
|
|
|
|
.sectionTitle {
|
|
margin: 0;
|
|
font-family: var(--font-display);
|
|
font-size: var(--step--1);
|
|
font-weight: 700;
|
|
line-height: 1;
|
|
/* --sage flips with the theme; this band does not (it is teal in both), so
|
|
the pairing only held in one of them — the dark sage measured 4.08:1 here
|
|
against a 4.5 floor, on every page. The --on-sunken-* family exists
|
|
precisely because this surface is theme-invariant. */
|
|
color: var(--on-sunken-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
}
|
|
|
|
.links {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.link {
|
|
font-family: var(--font-ui);
|
|
font-size: var(--step--1);
|
|
color: var(--on-sunken-muted);
|
|
text-decoration: none;
|
|
border-radius: var(--radius-sm);
|
|
transition: color var(--transition);
|
|
}
|
|
|
|
.link:hover {
|
|
color: var(--on-sunken);
|
|
}
|
|
|
|
.linkDisabled {
|
|
font-size: var(--step--1);
|
|
color: var(--on-sunken-faint);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* ─── Bottom rule ───────────────────────────────────────────────── */
|
|
|
|
/*
|
|
* The divider is drawn as a pseudo-element at low opacity rather than as an
|
|
* rgba() border: there is no channel token for --on-sunken, and every literal
|
|
* that would work here (rgba(255,255,255,…)) is exactly the kind of hardcoded
|
|
* colour the token layer exists to remove.
|
|
*/
|
|
.bottom {
|
|
position: relative;
|
|
padding-top: 2rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.bottom::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 1px;
|
|
background: var(--on-sunken);
|
|
opacity: 0.16;
|
|
}
|
|
|
|
.copyright,
|
|
.disclaimer {
|
|
margin: 0;
|
|
font-size: var(--step--1);
|
|
color: var(--on-sunken-muted);
|
|
}
|
|
|
|
.disclaimer .link {
|
|
color: var(--on-sunken-link);
|
|
}
|
|
|
|
.disclaimer .link:hover {
|
|
color: var(--on-sunken);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.container {
|
|
padding: 2rem 1rem 1.5rem;
|
|
}
|
|
|
|
.content {
|
|
grid-template-columns: 1fr;
|
|
gap: 2rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.bottom {
|
|
text-align: center;
|
|
}
|
|
}
|