fix: replace the traced infinity mark with the real artwork; add §4 Offerings
Q32 — the traced mark was a WRONG SHAPE and had shipped. Pouya compared it against the master and rejected it. Two grounds reproduce from the path and are verified here: all four cubic branches meet the origin at exactly 90 degrees, so the loops are tangent rather than crossing and at stroke-width 28 render as two kissing circles (signed crossing number 0; the strokes fuse across 61% of the mark's height at 2rem); and the master's ink bbox is 2668x1704 = 1.5657:1. The path is deleted, not kept as a fallback. Pouya's 1.23:1 figure is reconciled rather than left dangling: it is the bounding box of the path's COORDINATES, not the curve. Control points sit at y +/-160 where the curve reaches +/-120, so the hull is 400x320 and with stroke 428x348 = 1.2299. A trap rather than a slip — x is monotone, so the control points give the right width and a 33% inflated height, and the "does the width look right" check passes. The real artwork is now in the repo: master, tight crop (the render source, so the file's aspect ratio IS the mark's), full lockup, and the SVG. InfinityMark renders AVIF/WebP; a Retina device takes 3,063 B. Favicons regenerated; favicon.svg deleted. Q33/Q36 — Pouya accepts arbitration appointments now. §4 gains an Offerings category: competence for an offering, permission for a credential, with an explicit boundary so it cannot become a route around D13. The masthead tagline is restored, and the footer designation strip now carries "Q.Arb — commenced August 2026" so §4's paired-disclosure condition is actually met on every page rather than only asserted. Two conventions added to CLAUDE.md, both earned this session: anything a spec makes a claim about must be reachable from the repo (R14 — the traced mark survived two review passes because the artwork was not here to compare against); and a command that did not run is not evidence of absence (`timeout` is not installed on macOS, so four Drive reads never executed and were reported as an empty directory). Reviews: claims-auditor FAIL/13 and adversarial-reviewer 2 blocking, all resolved. The severe one was self-inflicted — `flex: none` landed on the <img> while <Picture>'s <picture> wrapper is the flex item, so the logo compressed to 28.5x32 at 1024px with seven nav items. The page-level overflow check passed throughout because the brand block absorbed the deficit by crushing the mark. Harness now asserts rendered aspect ratio. Opened: Q38, Q39. Closed: Q32, Q33, Q36. Narrowed: Q35. Added: R13, R14. AGENTS.md entry (v) carries a RESUME HERE section. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012XquaEq4BgWMCwUqLEyNkF
This commit is contained in:
co-authored by
Claude Opus 5
parent
8134709548
commit
364b09632e
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 586 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 251 KiB |
@@ -1,29 +1,53 @@
|
||||
---
|
||||
/**
|
||||
* The SML infinity mark. AGENTS.md D7 keeps it unchanged; docs/02 requires it
|
||||
* be inline SVG rather than a raster. (The "~470 KB PNG" this comment used to
|
||||
* cite had no source anywhere in the repo — see AGENTS.md Q34. The mark is
|
||||
* geometry, which is reason enough without a number.)
|
||||
* The SML infinity mark.
|
||||
*
|
||||
* PROVENANCE — read before editing the path. This geometry is lifted verbatim
|
||||
* from the deployed site's own loading-thumbnail SVG (the element it labels
|
||||
* `smlMark`), fetched from https://adr.smlcompany.ca/ on 2026-08-26. The only
|
||||
* change is arithmetic: the source drew the path offset by `translate(60 0)`
|
||||
* inside a 1200×800 frame, and that offset is folded into the coordinates here
|
||||
* so the curve is symmetric about the origin. Stroke widths (28 outer, 6 inner)
|
||||
* and the 0.7 inner opacity are the source's.
|
||||
* ⚠️ DELIBERATE, TEMPORARY EXCEPTION TO docs/02's "inline SVG, never a PNG".
|
||||
* Tracked as AGENTS.md Q38, with a standing reminder (R13) so it cannot become
|
||||
* permanent by neglect. Read both before changing this file.
|
||||
*
|
||||
* It is therefore SML's own artwork, not a redrawing — but it came from a
|
||||
* loading placeholder, which is not necessarily the canonical file. See
|
||||
* AGENTS.md Q32.
|
||||
* WHY A RASTER — and the reason is PAYLOAD, not fidelity. The mark is not a
|
||||
* stroked curve; it is a shaded ribbon of variable width that twists in three
|
||||
* dimensions, maroon flowing into champagne, passing over itself at the
|
||||
* crossing. That is gradient-mesh artwork, not the flat vector paths docs/02
|
||||
* assumes.
|
||||
*
|
||||
* Two-tone by design: the outer stroke takes `currentColor` so the mark sits
|
||||
* correctly on cream, ink, and maroon; the inner hairline is gold. Gold as an
|
||||
* icon stroke is explicitly allowed — the rule it must never break is gold as
|
||||
* TEXT on cream, which measures 2.10:1 (docs/02).
|
||||
* We do hold an SVG — src/assets/brand/sml-logo-source.svg — and **it renders
|
||||
* faithfully**: rasterised at 8333px it reproduces the master exactly, at the
|
||||
* same 1.566:1 [verified 2026-08-26]. An earlier version of this comment implied
|
||||
* it was inadequate artwork. It is not; that was unfair and is corrected here.
|
||||
* What rules it out is weight and composition: 257,278 bytes against 3,063 for
|
||||
* the AVIF a Retina browser takes — 84× — and SEVEN embedded base64 PNGs plus
|
||||
* a 1,225-stop gradient mesh, so inlining it would breach CLAUDE.md's rule
|
||||
* against base64-inlining images. The exception ends when a vector master lands
|
||||
* that is both faithful AND light.
|
||||
*
|
||||
* WHAT THIS REPLACES, and why it had to go. Until 2026-08-26 this component
|
||||
* drew a hand-traced cubic path lifted from the old site's loading thumbnail.
|
||||
* Pouya compared it against the master and it was wrong in three ways; two are
|
||||
* reproducible from the path itself:
|
||||
*
|
||||
* 1. TANGENT, NOT CROSSING. All four branches met the origin at exactly 90°,
|
||||
* so the loops were mutually tangent on a vertical line rather than
|
||||
* crossing. At stroke-width 28 that renders as two circles kissing — the
|
||||
* one thing an infinity mark must not be. Verified by computing the
|
||||
* tangent vector of every segment at the origin.
|
||||
* 2. WRONG PROPORTION. The real mark's ink bounding box is 2668 × 1704 =
|
||||
* 1.5657:1. The traced path measured 1.667:1 ink / 1.597:1 stroked.
|
||||
* 3. FLAT. Two uniform strokes standing in for a shaded ribbon.
|
||||
*
|
||||
* It is deleted rather than kept as a fallback, on Pouya's instruction: a wrong
|
||||
* mark that renders is worse than a missing one, because it stops looking wrong.
|
||||
*
|
||||
* SOURCE OF TRUTH. src/assets/brand/sml-infinity-mark.png is the master tight-
|
||||
* cropped to its ink bounding box, so the file's aspect ratio IS the mark's and
|
||||
* layout can be tuned to it directly. Provenance: docs/reference/brand-assets.md.
|
||||
*/
|
||||
import { Picture } from 'astro:assets';
|
||||
import mark from '../assets/brand/sml-infinity-mark.png';
|
||||
|
||||
interface Props {
|
||||
/** Rendered height. Width follows the 11:7 aspect ratio. */
|
||||
/** Rendered height. Width follows 1.5657:1. */
|
||||
size?: string;
|
||||
/** Accessible name. Omit for decorative use — the default. */
|
||||
label?: string;
|
||||
@@ -31,36 +55,64 @@ interface Props {
|
||||
}
|
||||
|
||||
const { size = '1.75rem', label, class: className } = Astro.props;
|
||||
const decorative = label === undefined;
|
||||
|
||||
const PATH = `M -200 0
|
||||
C -200 -160, 0 -160, 0 0
|
||||
C 0 160, 200 160, 200 0
|
||||
C 200 -160, 0 -160, 0 0
|
||||
C 0 160, -200 160, -200 0 Z`;
|
||||
// Sized to the largest render that SURVIVES, which is the footer at 2.25rem =
|
||||
// 56.4 CSS px wide. The first attempt used 320px, justified by the 4rem sample
|
||||
// on the proof sheet — a page step 2 deletes — and then stacked densities on
|
||||
// top, so the ladder double-counted its own headroom and every Retina device
|
||||
// pulled a 640px image into a 56px slot.
|
||||
//
|
||||
// PASSING A WIDTH EXPLICITLY IS STILL THE POINT. Without it Astro emits the
|
||||
// untouched 2668px master as the <img src> fallback: 1,146,406 bytes, which any
|
||||
// client without AVIF or WebP support would actually download, sitting in dist
|
||||
// looking like an optimisation had happened.
|
||||
const INTRINSIC_WIDTH = 64;
|
||||
---
|
||||
|
||||
<svg
|
||||
<Picture
|
||||
src={mark}
|
||||
width={INTRINSIC_WIDTH}
|
||||
densities={[1, 2, 3]}
|
||||
formats={['avif', 'webp']}
|
||||
fallbackFormat="png"
|
||||
alt={label ?? ''}
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
class:list={['mark', className]}
|
||||
viewBox="-220 -140 440 280"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
role={decorative ? undefined : 'img'}
|
||||
aria-hidden={decorative ? 'true' : undefined}
|
||||
aria-label={label}
|
||||
focusable="false"
|
||||
style={`block-size:${size}`}
|
||||
>
|
||||
<path d={PATH} stroke="currentColor" stroke-width="28"></path>
|
||||
<path d={PATH} stroke="var(--rule)" stroke-width="6" opacity="0.7"></path>
|
||||
</svg>
|
||||
pictureAttributes={{ style: `block-size:${size}` }}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.mark {
|
||||
/* 440 : 280 — the viewBox. Height is set inline; width follows. */
|
||||
aspect-ratio: 11 / 7;
|
||||
inline-size: auto;
|
||||
/* THE FLEX ITEM IS THE <picture>, NOT THE <img>.
|
||||
`class:list` lands on the <img>, which Astro's <Picture> wraps in a
|
||||
<picture> — and that wrapper is what `.brand`'s flex layout actually sizes.
|
||||
So `flex: none` on .mark reached the wrong box entirely and the mark was
|
||||
free to be squeezed: measured 28.52 x 32 at 1024px with seven nav items,
|
||||
against a correct 50.09 x 32 — an aspect of 0.891 where it should be 1.5657.
|
||||
|
||||
This is the SAME defect CLAUDE.md already records for <Button> in
|
||||
SiteHeader — a parent cannot style a child component's root — reintroduced
|
||||
inside the fix for it, one round later. The bare `picture` selector below is
|
||||
scoped by Astro's cid, which the emitted markup does carry, and the height
|
||||
now goes on the wrapper via `pictureAttributes` rather than on the image.
|
||||
|
||||
Worse than the bug: the page-level overflow check passed throughout, because
|
||||
the brand block absorbed the deficit by crushing the logo. "0 overflow at
|
||||
every width" was true and misleading — it measured the document, not the
|
||||
elements inside it. The harness now asserts the rendered aspect ratio. */
|
||||
picture {
|
||||
display: block;
|
||||
flex: none;
|
||||
aspect-ratio: 667 / 426;
|
||||
inline-size: auto;
|
||||
}
|
||||
.mark {
|
||||
/* 2668 / 1704 reduced — the master's exact ink bounding box, so the box the
|
||||
layout reserves is the shape that fills it. */
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
/* Belt and braces: if anything ever squeezes the box again, the artwork
|
||||
letterboxes instead of distorting. */
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,6 +27,17 @@ import InfinityMark from './InfinityMark.astro';
|
||||
// deployed, which is the same cadence everything else here updates on.
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
// Med-Arb was filtered out of this column for a few hours on 2026-08-26 and is
|
||||
// RESTORED, because removing it was wrong on three counts and `adversarial-
|
||||
// reviewer` caught all three. (1) docs/01 specifies this footer as the FULL
|
||||
// sitemap and lists /med-arb/ as deliberately out of the primary nav, "linked
|
||||
// contextually" — so dropping it here left the page with no site-wide link at
|
||||
// all, on a project whose entire premise is crawlability. (2) The label names a
|
||||
// PAGE, and docs/01 frames that page around the C.Med-Arb arc rather than as a
|
||||
// present offering, so listing it is not the §4 inference it looked like.
|
||||
// (3) It settled half of Q35 unilaterally while the other half — Energy and
|
||||
// Shareholder — stayed in both nav and footer, and the record claimed no
|
||||
// unilateral action had been taken. Both halves of Q35 go to Pouya together.
|
||||
const processLinks = [
|
||||
{ href: '/mediation/', label: 'Mediation' },
|
||||
{ href: '/arbitration/', label: 'Arbitration' },
|
||||
@@ -48,8 +59,19 @@ const aboutLinks = [
|
||||
<InfinityMark size="2.25rem" />
|
||||
<span class="footer-brand-name">{SITE.name}</span>
|
||||
</a>
|
||||
{
|
||||
/* The designation strip carries the credentialing STAGE, not just the
|
||||
held designation, and that is a §4 Offerings condition rather than a
|
||||
flourish. The masthead says "Mediation · Arbitration · Toronto" on
|
||||
every page; §4 permits the arbitration half on the condition that the
|
||||
site "makes the first while stating the second plainly", and
|
||||
"neither half may be dropped". Before this line rendered
|
||||
CREDENTIALS.inProgress, that condition was unmet on every page that
|
||||
ships — the constant existed in site.ts and was rendered nowhere.
|
||||
Q.Arb reads as commenced, never as held (§4). */
|
||||
}
|
||||
<p class="footer-designation">
|
||||
{CREDENTIALS.designations.join(' · ')}
|
||||
{[...CREDENTIALS.designations, ...CREDENTIALS.inProgress].join(' · ')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -38,7 +38,10 @@ const inSection = (href: string) => path === href || path.startsWith(href);
|
||||
<div class="wrap header-inner">
|
||||
<a class="brand" href="/">
|
||||
<InfinityMark size="2rem" />
|
||||
<span class="brand-name">{SITE.name}</span>
|
||||
<span class="brand-text">
|
||||
<span class="brand-name">{SITE.name}</span>
|
||||
<span class="eyebrow brand-tagline">{SITE.tagline}</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<nav class="nav" aria-label="Primary">
|
||||
@@ -131,22 +134,44 @@ const inSection = (href: string) => path === href || path.startsWith(href);
|
||||
|
||||
/* --- Brand --------------------------------------------------------------- */
|
||||
|
||||
/* No tagline under the name. `SITE.tagline` reads "Mediation · Arbitration ·
|
||||
Toronto", and docs/01 specifies that string as the HOME HERO EYEBROW —
|
||||
nothing asks for a header tagline. Set under the name on every page it
|
||||
stops being page copy and becomes a designation strip, with `Arbitration`
|
||||
unqualified beside a name. AGENTS.md §4 records Q.Arb as commenced August
|
||||
2026 and requires it never read as held. See AGENTS.md Q33. */
|
||||
/* The tagline is BACK, and the reasoning is worth keeping rather than just
|
||||
the outcome. It was removed on 2026-08-26 because `Arbitration` under
|
||||
Pouya's name read as a held capability, and §4 records Q.Arb as merely
|
||||
commenced. Q33 answered that the same day, and the premise was wrong:
|
||||
ADR designations are voluntary credentials, not licences. Anyone may be
|
||||
appointed an arbitrator in Ontario, so the constraint was always
|
||||
positional, never legal — Pouya accepts sole, party-appointed and
|
||||
co-arbitration work today. See §4 Offerings, which is where the register
|
||||
now draws the line between a CREDENTIAL claim and an OFFERING. */
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
/* 44px floor — this is the tap target that goes home. */
|
||||
min-block-size: 44px;
|
||||
/* 48px, not 44. Still clears the touch floor, and it reserves the height the
|
||||
two-line brand takes at >=76rem so the sticky header is one constant 81px
|
||||
across every width where it is sticky — which is what --header-h and
|
||||
scroll-padding-top are keyed to. One number instead of two bands. */
|
||||
min-block-size: 48px;
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
margin-inline-end: auto;
|
||||
}
|
||||
.brand-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
/* The tagline appears only where there is room for it — see the 76rem block.
|
||||
Measured: at 11px with 0.18em tracking the string is ~285px wide, and
|
||||
restoring it under the name pushed the one-row header past its content box
|
||||
by 18px at 1024 with six items and 84px with seven. The brand name carries
|
||||
the identity on its own; the tagline is a flourish, and `/` opens with the
|
||||
same words as the hero eyebrow (docs/01). */
|
||||
.brand-tagline {
|
||||
display: none;
|
||||
font-size: 0.6875rem; /* 11px — the eyebrow floor in docs/02 */
|
||||
white-space: nowrap;
|
||||
}
|
||||
.brand-name {
|
||||
font-family: var(--font-serif);
|
||||
font-size: var(--text-xl);
|
||||
@@ -255,7 +280,7 @@ const inSection = (href: string) => path === href || path.startsWith(href);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Nav takes its own row below 64rem. No `order` anywhere: reordering flex
|
||||
/* Nav takes its own row below 66rem. No `order` anywhere: reordering flex
|
||||
items moved the CTA onto row one visually while it stayed last in the DOM,
|
||||
so a keyboard user tabbed off the brand, down through all 13 nav links
|
||||
including the practice panel, and back UP to the CTA. WCAG 2.4.3, and
|
||||
@@ -267,17 +292,17 @@ const inSection = (href: string) => path === href || path.startsWith(href);
|
||||
/* `white-space` INHERITS into the Button, which is how a parent reaches a
|
||||
child component's root at all here — see the note on why this wrapper
|
||||
exists. Without it the label wrapped to two lines, standing the header up
|
||||
at 93px instead of 77px. `flex: none` stops the row squeezing it. */
|
||||
taller than it needed to be. `flex: none` stops the row squeezing it. */
|
||||
.header-cta {
|
||||
flex: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* Below 64rem the CTA is dropped, not shrunk — and that is the whole reason
|
||||
/* Below 66rem the CTA is dropped, not shrunk — and that is the whole reason
|
||||
no `order` is needed. Wherever the nav has its own row, `/contact/` is
|
||||
already on it, so the button is a duplicate link paying for itself in
|
||||
header height. It was previously dropped only below 40rem and reordered in
|
||||
between, which is what put focus order out of step with visual order. */
|
||||
@media (max-width: 63.999rem) {
|
||||
@media (max-width: 65.999rem) {
|
||||
.header-cta {
|
||||
display: none;
|
||||
}
|
||||
@@ -285,12 +310,19 @@ const inSection = (href: string) => path === href || path.startsWith(href);
|
||||
|
||||
/* --- Desktop ------------------------------------------------------------- */
|
||||
|
||||
/* --- Desktop: 64rem (1024px) ---------------------------------------------
|
||||
The threshold is measured, not conventional. The one-row header has to hold
|
||||
the brand, seven nav items and the CTA side by side, and **at 1024px it
|
||||
does, with 32px of clearance between the nav and the CTA — measured in
|
||||
headless Chrome with a seventh nav item injected.** Below that it does not,
|
||||
which is why the nav takes its own row there.
|
||||
/* --- Desktop: 66rem (1024px) ---------------------------------------------
|
||||
The threshold is measured, and the first measurement was WRONG — worth
|
||||
recording, because of how it was wrong. It read "32px of clearance at
|
||||
1024px". That 32px was `.header-inner`'s own `column-gap`, i.e. exactly
|
||||
zero slack, mistaken for headroom. The true max-content sum at seven items
|
||||
is 48 + 184.3 brand + 24 margin + 32 gap + 480.1 nav + 32 gap + 197.2 CTA
|
||||
+ 48 = **1045.6px — 21.6px OVER a 1024px viewport.** Nothing overflowed
|
||||
only because flexbox shrank the brand and crushed the mark inside it.
|
||||
|
||||
Binary search on the built page with a seventh item gives the real
|
||||
thresholds: the row fits from **1047px**, and the tagline from **1207px**.
|
||||
66rem (1056) and 76rem (1216) are the clean tokens above each. Below 66rem
|
||||
the nav takes its own row.
|
||||
|
||||
Insights is the seventh item and arrives by itself at build step 7
|
||||
(SiteHeader gates it on the collection), so a breakpoint verified only
|
||||
@@ -305,7 +337,7 @@ const inSection = (href: string) => path === href || path.startsWith(href);
|
||||
Sticky only from here up, too. Below this the nav takes a second row and
|
||||
the header stands at 137px, which is more of a small viewport than a
|
||||
sticky header is worth. Deviation from docs/02 "Sticky"; recorded there. */
|
||||
@media (min-width: 64rem) {
|
||||
@media (min-width: 66rem) {
|
||||
.site-header {
|
||||
position: sticky;
|
||||
inset-block-start: 0;
|
||||
@@ -321,11 +353,11 @@ const inSection = (href: string) => path === href || path.startsWith(href);
|
||||
}
|
||||
/* nowrap, and flex:none so the nav is never squeezed below its content
|
||||
width. Measured before this: at 960-1250px the seven-item nav broke to
|
||||
two rows and the header stood at 142px instead of 80px. */
|
||||
two rows and the header stood at 141px instead of 81px. */
|
||||
.nav-list {
|
||||
flex-wrap: nowrap;
|
||||
flex: none;
|
||||
/* 16px from 64rem, widening to 24px at 75rem where there is room for it.
|
||||
/* 16px from 66rem, widening to 24px at 76rem where there is room for it.
|
||||
Measured with seven items at every width from 1024px up. */
|
||||
column-gap: var(--space-4);
|
||||
}
|
||||
@@ -372,14 +404,24 @@ const inSection = (href: string) => path === href || path.startsWith(href);
|
||||
you scroll off the top. That is a smaller effect than docs/02's "condenses
|
||||
on scroll", and docs/02 has been amended to say so and why. */
|
||||
|
||||
@media (min-width: 75rem) {
|
||||
/* The tagline arrives at 76rem. The nav gap does NOT widen here as well:
|
||||
measured, doing both at one breakpoint put the row 4px past its content box
|
||||
at exactly 1200px with seven items. Two things growing at the same width is
|
||||
how a breakpoint gets over-subscribed. */
|
||||
@media (min-width: 76rem) {
|
||||
.brand-tagline {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 80rem) {
|
||||
.nav-list {
|
||||
column-gap: var(--space-5);
|
||||
}
|
||||
}
|
||||
|
||||
@supports (animation-timeline: scroll()) {
|
||||
@media (min-width: 64rem) and (prefers-reduced-motion: no-preference) {
|
||||
@media (min-width: 66rem) and (prefers-reduced-motion: no-preference) {
|
||||
.site-header {
|
||||
animation-name: header-lift;
|
||||
animation-duration: 1ms;
|
||||
|
||||
@@ -26,11 +26,12 @@ const lang = SITE.locale.replace('_', '-');
|
||||
<SEO {...Astro.props} />
|
||||
|
||||
{
|
||||
/* .ico first for clients that ignore the SVG, and because some crawlers
|
||||
request /favicon.ico at the root regardless of what is declared here. */
|
||||
/* No SVG favicon. The mark is a shaded ribbon, not flat vector paths, so
|
||||
there is no honest SVG of it to serve — see InfinityMark.astro and
|
||||
AGENTS.md Q38. The .ico carries 16/32/48, and is what crawlers request
|
||||
at the root regardless of what is declared here. */
|
||||
}
|
||||
<link rel="icon" href="/favicon.ico" sizes="48x48" />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="16x16 32x32 48x48" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
|
||||
{
|
||||
|
||||
@@ -113,16 +113,16 @@
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
scroll-behavior: smooth;
|
||||
/* No offset by default: below 64rem the header is not sticky, so nothing is
|
||||
/* No offset by default: below 66rem the header is not sticky, so nothing is
|
||||
covering the target. See the media query below. */
|
||||
scroll-padding-top: var(--space-4);
|
||||
}
|
||||
|
||||
/* The header is sticky from 64rem up, and `scroll-padding-top` has to clear it
|
||||
/* The header is sticky from 66rem up, and `scroll-padding-top` has to clear it
|
||||
or "Skip to content" drops the reader behind it — the one control that exists
|
||||
specifically for keyboard users, landing them on content they cannot see.
|
||||
--header-h is defined in tokens.css beside the value it has to match. */
|
||||
@media (min-width: 64rem) {
|
||||
@media (min-width: 66rem) {
|
||||
html {
|
||||
scroll-padding-top: calc(var(--header-h) + var(--space-4));
|
||||
}
|
||||
|
||||
@@ -112,13 +112,16 @@
|
||||
|
||||
/* --- Layout ------------------------------------------------------------ */
|
||||
|
||||
/* Sticky-header height at >= 64rem, where the header IS sticky. global.css
|
||||
/* Sticky-header height at >= 66rem, where the header IS sticky. global.css
|
||||
drives `scroll-padding-top` off this, so the skip link does not drop the
|
||||
reader behind the header. If SiteHeader's padding or nav sizing changes,
|
||||
re-measure and change this with it — one fact living in two files.
|
||||
[measured 2026-08-26 — headless Chrome at 1024/1100/1280/1440px, with six
|
||||
nav items and with a seventh injected: 77px at every one] */
|
||||
--header-h: 4.8125rem; /* 77 — measured, not chosen */
|
||||
nav items and with a seventh injected. 81px at every one: 32 padding + 48
|
||||
reserved brand block + the 1px bottom border, which is easy to forget and
|
||||
is why this is measured rather than added up. The brand reserves 48px so
|
||||
the height does not change when the tagline appears at 76rem] */
|
||||
--header-h: 5.0625rem; /* 81 — measured, not chosen */
|
||||
|
||||
--width-content: 80rem; /* 1280 */
|
||||
--width-wide: 90rem; /* 1440 */
|
||||
|
||||
Reference in New Issue
Block a user