mirror of
https://github.com/hanzoai/design.git
synced 2026-10-30 14:58:37 +00:00
130 lines
8.7 KiB
CSS
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))}
|
|
}
|