Files

130 lines
8.7 KiB
CSS

/* TIGHT app-first type scale — the compact developer-app register (linear.app /
vercel.com / the Codex desktop look), the Hanzo default across chat / app /
desktop. Base is 14px, nav 13px, labels 12px; display sizes tightened. Kept in
lockstep with @hanzo/brand (styles/variables.css --font-size-* + typography.ts)
— the two are the SAME scale, mirrored. A surface/tenant overrides any --text-*
on :root to retune density on demand. */
:root{
/* The type knob. 1 is the published scale; every rung below multiplies by
it, so a surface or a person retunes the WHOLE ramp by setting one
number and no rung can drift out of relation with the others.
@hanzo/design's `vars({type})` emits exactly this name. */
--type-scale:1;
/* The CONTRAST knob — how far apart the rungs sit, where --type-scale is how
big they all are. Two different questions, and only the first one was
askable: a surface could take the whole ramp up 15% but could not flatten a
dense table's steps or open up a marketing page's, so both settled for the
app register's tight default.
It is authored as a TRANSFORM and not as a generated ramp, which is the
whole point. `base + (authored - base) * ratio` keeps each rung's authored
value as the INPUT, so ratio 1 reduces to exactly the published number by
algebra rather than by care — the deltas below are just `authored - 0.875`.
The alternative, deriving each rung as base * ratio^n, would restate the
ramp in a second place, and this scale is not geometric anyway: the app
register (12/13/14/15/17) is near-linear on purpose and a ratio applied to
it would erase the 1px steps the compact register is FOR. That drift is not
hypothetical here — see src/preference.ts, where a recomputed copy of this
ramp shipped `lg: 16px` against this file's 15px.
A rung is one place, so a rung added later needs a delta and nothing else. */
--type-ratio:1;
/* EVERY rung is bounded at BOTH ends, by one rule, because the knobs COMPOUND
and they compound in both directions. Clamping each knob's range separately
cannot fix either end — 0.85 x 0.9 is inside both bounds and lands at 8.5px
— so the RESULT is what carries the bound:
--text-floor 12px below this a label is not small, it is unreadable
--text-ceiling 30vw above this a heading is not type, it is a texture
The ceiling is in the VIEWPORT's units because that is what "too big" means:
measured, the tallest rung (--text-9xl, 112px) is 28.7vw on a 390px phone
and 7.8vw at 1440, so 30vw binds no published value at any real screen and
catches only a scale that has run away. Both bounds are stated once here and
`vars()` emits the same clamp for a modular scale, so a regenerated rung and
an authored one are bounded identically — the rule does not depend on which
produced the value. */
--text-floor:0.75rem;
--text-ceiling:30vw;
--text-xs:clamp(var(--text-floor), calc((0.875rem - 0.125rem * var(--type-ratio, 1)) * var(--type-scale, 1)), var(--text-ceiling)); --leading-xs:calc((1.35rem - 0.35rem * var(--type-ratio, 1)) * var(--type-scale, 1)); /* 12px — eyebrows / section labels; the smallest type anywhere */
--text-sm:clamp(var(--text-floor), calc((0.875rem - 0.0625rem * var(--type-ratio, 1)) * var(--type-scale, 1)), var(--text-ceiling)); --leading-sm:calc((1.35rem - 0.2rem * var(--type-ratio, 1)) * var(--type-scale, 1)); /* 13px — nav labels, dense body */
--text-base:clamp(var(--text-floor), calc(0.875rem * var(--type-scale, 1)), var(--text-ceiling)); --leading-base:calc(1.35rem * var(--type-scale, 1)); /* 14px — the ANCHOR: the rung the ratio pivots on, so it carries no ratio term */
--text-lg:clamp(var(--text-floor), calc((0.875rem + 0.0625rem * var(--type-ratio, 1)) * var(--type-scale, 1)), var(--text-ceiling)); --leading-lg:calc((1.35rem + 0.05rem * var(--type-ratio, 1)) * var(--type-scale, 1)); /* 15px */
--text-xl:clamp(var(--text-floor), calc((0.875rem + 0.1875rem * var(--type-ratio, 1)) * var(--type-scale, 1)), var(--text-ceiling)); --leading-xl:calc((1.35rem + 0.2rem * var(--type-ratio, 1)) * var(--type-scale, 1)); /* 17px */
--text-2xl:clamp(var(--text-floor), calc((0.875rem + 0.4375rem * var(--type-ratio, 1)) * var(--type-scale, 1)), var(--text-ceiling)); --leading-2xl:calc((1.35rem + 0.35rem * var(--type-ratio, 1)) * var(--type-scale, 1)); /* 21px */
--text-3xl:clamp(var(--text-floor), calc((0.875rem + 0.75rem * var(--type-ratio, 1)) * var(--type-scale, 1)), var(--text-ceiling)); --leading-3xl:calc((1.35rem + 0.6rem * var(--type-ratio, 1)) * var(--type-scale, 1)); /* 26px */
--text-4xl:clamp(var(--text-floor), calc((0.875rem + 1.125rem * var(--type-ratio, 1)) * var(--type-scale, 1)), var(--text-ceiling)); --leading-4xl:calc((1.35rem + 0.9rem * var(--type-ratio, 1)) * var(--type-scale, 1)); /* 32px */
--text-5xl:clamp(var(--text-floor), calc((0.875rem + 1.625rem * var(--type-ratio, 1)) * var(--type-scale, 1)), var(--text-ceiling)); --leading-5xl:1.05; /* 40px — display leading is unitless, so it tracks its own size */
--text-6xl:clamp(var(--text-floor), calc((0.875rem + 2.375rem * var(--type-ratio, 1)) * var(--type-scale, 1)), var(--text-ceiling)); --leading-6xl:1; /* 52px */
--text-7xl:clamp(var(--text-floor), calc((0.875rem + 3.125rem * var(--type-ratio, 1)) * var(--type-scale, 1)), var(--text-ceiling)); --leading-7xl:1; /* 64px */
--text-8xl:clamp(var(--text-floor), calc((0.875rem + 4.375rem * var(--type-ratio, 1)) * var(--type-scale, 1)), var(--text-ceiling)); --leading-8xl:1; /* 84px */
--text-9xl:clamp(var(--text-floor), calc((0.875rem + 6.125rem * var(--type-ratio, 1)) * var(--type-scale, 1)), var(--text-ceiling)); --leading-9xl:1; /* 112px */
/* The SAME scale under @hanzo/brand's spelling. @hanzo/gui's shell theme and
Hanzo Studio address the ramp as --font-size-*; both names are one value, so
a component written against either resolves here. --text-* is canonical. */
--font-size-xs:var(--text-xs);
--font-size-sm:var(--text-sm);
--font-size-base:var(--text-base);
--font-size-lg:var(--text-lg);
--font-size-xl:var(--text-xl);
--font-size-2xl:var(--text-2xl);
--font-size-3xl:var(--text-3xl);
--font-size-4xl:var(--text-4xl);
--font-size-5xl:var(--text-5xl);
--font-size-6xl:var(--text-6xl);
--font-size-7xl:var(--text-7xl);
--font-size-8xl:var(--text-8xl);
--font-size-9xl:var(--text-9xl);
/* The size a FORM CONTROL renders at — and the ONE step in this scale that is
not a constant. iOS Safari zooms the viewport whenever a focused input
computes below 16px, and this scale's base is 14px, so every surface that
sized a field from --text-sm or --text-base bought the zoom.
It has to be a TOKEN rather than a rule because a control's size is almost
always set inline (every component in components/forms does), and an inline
style outranks any stylesheet — including a media query. A var() resolves
per-device inside that inline style, so this is the only construction that
actually reaches the control. Fields ask for --text-control; nothing else
should. */
--text-control:var(--text-sm);
--weight-normal:400;
--weight-medium:500;
--weight-semibold:600;
--weight-bold:700;
--tracking-tight:-0.025em;
--tracking-normal:0em;
--tracking-wide:0.025em;
--tracking-widest:0.1em; /* eyebrows / uppercase category labels */
--leading-none:1;
--leading-tight:1.25;
--leading-snug:1.375;
--leading-normal:1.5;
--leading-relaxed:1.625;
--leading-golden:1.618;
/* named roles */
--type-hero:600 var(--text-5xl)/1.05 var(--font-display);
--type-h2:700 var(--text-4xl)/var(--leading-4xl) var(--font-display);
--type-h3:600 var(--text-xl)/var(--leading-xl) var(--font-display);
--type-lead:400 var(--text-lg)/var(--leading-relaxed) var(--font-sans);
--type-body:400 var(--text-sm)/var(--leading-sm) var(--font-sans);
--type-caption:400 var(--text-xs)/var(--leading-xs) var(--font-sans);
--type-code:400 var(--text-sm)/var(--leading-relaxed) var(--font-mono);
--type-eyebrow:600 0.75rem/1 var(--font-sans);
}
/* On touch, a control goes to 16px. Below that iOS Safari zooms the viewport on
focus and never zooms back out, which is the single most common way a mobile
form feels broken. pointer:coarse is the real signal — a desktop mouse keeps
the compact 13px field. */
@media (pointer:coarse){
:root{--text-control:calc(1rem * var(--type-scale, 1))}
}