@aberhamm/liquid-glass-react 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Platform-neutral design tokens (plan 022).
3
+ *
4
+ * The single source of truth for the library's plain-data design constants —
5
+ * tint/saturation defaults, the blur-per-unit mapping, the drop-shadow layers
6
+ * and scroll-aware depth factors, the specular/press-glow geometry, the
7
+ * material-variant parameters, and the accessibility (reduced-transparency /
8
+ * increased-contrast) fills. These were previously embedded as web-local
9
+ * `const`s inside `liquid-glass.tsx`; they live here so BOTH the web file
10
+ * (`liquid-glass.tsx`) and the future React Native file
11
+ * (`liquid-glass.native.tsx`, plan 025) can consume ONE source of truth.
12
+ *
13
+ * ## Hard constraints (what may live here)
14
+ *
15
+ * This module is a leaf with ZERO platform coupling:
16
+ *
17
+ * - It imports nothing from the React renderer packages.
18
+ * - It contains NO DOM style types and NO browser-global references.
19
+ * - It holds only the underlying NUMBERS / COLORS / structured data. Anything
20
+ * inherently CSS — `mixBlendMode`, `backdrop-filter` strings, the
21
+ * `box-shadow` serialization (`buildDropShadow`), the `radial-gradient`
22
+ * templates — STAYS in the web file. The native file will serialize these
23
+ * same values into RN style objects.
24
+ *
25
+ * The only import is the platform-neutral {@link DisplacementMode} string-union
26
+ * type (no runtime, no DOM), so {@link DEFAULTS.mode} keeps its exact type.
27
+ *
28
+ * @internal Not part of the public package API pre-1.0 (eng-review decision):
29
+ * not re-exported from `index.ts`. The native barrel (plan 025) may import from
30
+ * `./tokens` directly. Exposing publicly later is additive / non-breaking.
31
+ */
32
+ // ---------------------------------------------------------------------------
33
+ // Defaults (parity spec — plan 004)
34
+ // ---------------------------------------------------------------------------
35
+ /** Default prop values for `<LiquidGlass>` (the parity spec). */
36
+ export const DEFAULTS = {
37
+ displacementScale: 70,
38
+ blurAmount: 0.0625,
39
+ saturation: 140,
40
+ aberrationIntensity: 2,
41
+ cornerRadius: 999,
42
+ mode: 'standard',
43
+ padding: '24px 32px',
44
+ elasticity: 0.15,
45
+ };
46
+ /**
47
+ * Maps the small public `blurAmount` (default `0.0625`) onto a px backdrop blur.
48
+ * The upstream convention treats `blurAmount` as a rem-ish factor; we scale by
49
+ * 16 (1rem) so the default lands at ~1px and larger values scale linearly.
50
+ */
51
+ export const BLUR_PX_PER_UNIT = 16;
52
+ // ---------------------------------------------------------------------------
53
+ // Accessibility fills (reduced transparency — plan 013)
54
+ // ---------------------------------------------------------------------------
55
+ /**
56
+ * Scheme-aware translucent fill layered ON TOP of the blurred backdrop when
57
+ * `prefers-reduced-transparency: reduce` is active and the backdrop tiers (1/2)
58
+ * still carry a `backdrop-filter`. It frosts the surface toward opaque so the
59
+ * material reads as static frosted glass rather than live lensing. Higher alpha
60
+ * than the tier-3 solid fill because here it composites over a blurred backdrop,
61
+ * not bare content. (a11y plan 013.)
62
+ */
63
+ export const REDUCED_TRANSPARENCY_FILL = {
64
+ light: 'rgba(255, 255, 255, 0.75)',
65
+ dark: 'rgba(30, 30, 35, 0.75)',
66
+ };
67
+ /**
68
+ * Scheme-aware solid fill for the no-`backdrop-filter` tier (3) under reduced
69
+ * transparency: a higher alpha than the default `0.55` solid fallback so the
70
+ * surface is more opaque / less transparent as the setting requests.
71
+ */
72
+ export const REDUCED_TRANSPARENCY_SOLID_FILL = {
73
+ light: 'rgba(255, 255, 255, 0.92)',
74
+ dark: 'rgba(30, 30, 35, 0.92)',
75
+ };
76
+ // ---------------------------------------------------------------------------
77
+ // Increased-contrast tokens (a11y plan 014)
78
+ // ---------------------------------------------------------------------------
79
+ /**
80
+ * Solid border color drawn as an INSET `box-shadow` ring on the surface so the
81
+ * box geometry is unchanged (no layout shift) — a real `border` would grow the
82
+ * box or eat into padding. Scheme-aware near-opaque ink/light for a clearly
83
+ * delineated edge that the soft bevel alone cannot provide.
84
+ */
85
+ export const CONTRAST_BORDER_COLOR = {
86
+ light: 'rgba(0, 0, 0, 0.92)',
87
+ dark: 'rgba(255, 255, 255, 0.92)',
88
+ };
89
+ /** Inset solid-border ring width (px), prepended to the surface box-shadow. */
90
+ export const CONTRAST_BORDER_WIDTH = 2;
91
+ /**
92
+ * More-opaque scheme-aware surface fill under increased contrast: it reduces
93
+ * the backdrop tint/show-through so foreground content reads at a higher
94
+ * contrast ratio. Layered over any retained blur (tiers 1/2) or used as the
95
+ * solid fill (tier 3). At 0.92 alpha it is at least as opaque as the
96
+ * reduced-transparency fills, so when BOTH a11y axes are active the contrast
97
+ * fill never undercuts reduced-transparency's opacity intent — contrast wants
98
+ * MORE opacity, not less, on every tier.
99
+ */
100
+ export const CONTRAST_FILL = {
101
+ light: 'rgba(255, 255, 255, 0.92)',
102
+ dark: 'rgba(22, 22, 26, 0.92)',
103
+ };
104
+ /**
105
+ * Under increased contrast, saturation is pinned to 100% (no decorative
106
+ * vibrancy boost) so the backdrop doesn't push color that competes with
107
+ * foreground text. The default `saturation` (140) is decorative and harms
108
+ * contrast, so it is overridden — never raised.
109
+ */
110
+ export const CONTRAST_SATURATION = 100;
111
+ /**
112
+ * Content-adaptive foreground ink (plan 018). When `adaptiveTint` is on, the
113
+ * content layer's `color` flips with the verdict so small content (button
114
+ * labels, captions) stays legible: DARK ink over a bright/light backdrop, LIGHT
115
+ * ink over a dark backdrop. `color` is inheritable, so children that don't set
116
+ * their own color pick it up; children with an explicit color keep it. Best-
117
+ * effort legibility — see the `adaptiveTint` TSDoc limitation note.
118
+ */
119
+ export const CONTENT_INK = {
120
+ light: 'rgba(255, 255, 255, 0.96)',
121
+ dark: 'rgba(17, 17, 20, 0.96)',
122
+ };
123
+ // ---------------------------------------------------------------------------
124
+ // Pointer-tracked specular hotspot + glow-on-press (plan 016)
125
+ // ---------------------------------------------------------------------------
126
+ /**
127
+ * Resting specular-hotspot center as a percentage of the surface box. With no
128
+ * pointer over the glass (or under reduced-motion) the highlight falls back to a
129
+ * neutral, top-biased position so the material reads like overhead light on
130
+ * glass rather than a flat sweep. Top-center, slightly above the box.
131
+ */
132
+ export const SPECULAR_REST_X = '50%';
133
+ export const SPECULAR_REST_Y = '0%';
134
+ /**
135
+ * Peak opacity of the specular hotspot's bright core (the inner stop). Hover
136
+ * brightens it slightly (see `LiquidGlass`); these are the base values.
137
+ */
138
+ export const SPECULAR_CORE_OPACITY = { rest: 0.55, hover: 0.85 };
139
+ /**
140
+ * Radius of the specular hotspot gradient as a percentage of the surface's
141
+ * larger dimension. Big enough to read as a soft glassy hotspot, small enough
142
+ * that it tracks visibly rather than washing the whole surface.
143
+ */
144
+ export const SPECULAR_RADIUS = '70%';
145
+ /**
146
+ * Peak opacity of the press-glow bloom's core when active. The glow originates
147
+ * at the contact point and spreads, then fades via the layer's opacity
148
+ * transition (gated on reduced-motion).
149
+ */
150
+ export const PRESS_GLOW_OPACITY = 0.5;
151
+ /** Radius of the press-glow bloom as a percentage of the surface box. */
152
+ export const PRESS_GLOW_RADIUS = '60%';
153
+ // ---------------------------------------------------------------------------
154
+ // Scroll-aware shadow modulation (plan 021)
155
+ // ---------------------------------------------------------------------------
156
+ /**
157
+ * The static drop-shadow (the `static` factor of 1 reproduces today's exact
158
+ * values) is scaled by a single multiplier per backdrop scheme. The shadow
159
+ * DEEPENS/DARKENS over a dark backdrop (factor > 1 → larger offset/blur + darker
160
+ * alpha, lifting the glass above dense scrolling content) and EASES/LIGHTENS over
161
+ * a light/solid one (factor < 1). Factors are deliberately subtle so the polish
162
+ * reads as a gentle depth shift, not a jump.
163
+ *
164
+ * Keys map directly to `BackdropScheme`; `static` is the SSR / first-paint /
165
+ * unsampled fallback and equals the committed baseline shadow exactly.
166
+ */
167
+ export const SCROLL_SHADOW_DEPTH = {
168
+ /** Conservative fallback: byte-for-byte the existing static shadow. */
169
+ static: 1,
170
+ /** Dark/dense backdrop ⇒ deeper, darker shadow. */
171
+ dark: 1.35,
172
+ /** Light/solid backdrop ⇒ shallower, lighter shadow. */
173
+ light: 0.7,
174
+ };
175
+ /** Base outer drop-shadow layers (offsetY, blur, alpha) at rest and on hover. */
176
+ export const SHADOW_LAYERS = {
177
+ rest: [
178
+ { y: 8, blur: 24, alpha: 0.22 },
179
+ { y: 1, blur: 4, alpha: 0.14 },
180
+ ],
181
+ hover: [
182
+ { y: 12, blur: 32, alpha: 0.28 },
183
+ { y: 2, blur: 8, alpha: 0.18 },
184
+ ],
185
+ };
186
+ // ---------------------------------------------------------------------------
187
+ // Material variant parameters (plan 019)
188
+ // ---------------------------------------------------------------------------
189
+ /**
190
+ * Per-variant surface parameters (plan 019). A small parameter LOOKUP — NOT a
191
+ * theming system — that feeds the existing surface/content styles.
192
+ *
193
+ * - `'regular'` is a pure pass-through: `saturationFactor = 1`, no scrim, and
194
+ * `forceNonAdaptive = false`, so the default render is byte-for-byte unchanged
195
+ * and `adaptiveTint` keeps working exactly as in plan 018.
196
+ * - `'clear'` is permanently MORE transparent: the decorative backdrop saturation
197
+ * is pulled back (`saturationFactor < 1` reduces the tint/vibrancy show-through)
198
+ * and `forceNonAdaptive` hard-disables the adaptive sampling path. A subtle
199
+ * dimming scrim is rendered as a CONTENT SIBLING (see the layer stack) so labels
200
+ * stay legible over busy media without re-introducing adaptivity.
201
+ */
202
+ export const VARIANT_PARAMS = {
203
+ regular: {
204
+ /** Multiplier on the requested `saturation` (1 = unchanged). */
205
+ saturationFactor: 1,
206
+ /** Force the adaptive-tint path off regardless of `adaptiveTint`. */
207
+ forceNonAdaptive: false,
208
+ /** Scheme-aware dimming-scrim color behind the content, or `null` for none. */
209
+ scrim: null,
210
+ },
211
+ clear: {
212
+ // Reduce the tint/vibrancy so the material reads as clearer (more of the
213
+ // backdrop shows through), per the Clear aesthetic.
214
+ saturationFactor: 0.7,
215
+ // Clear has NO adaptive behavior by definition — it never samples.
216
+ forceNonAdaptive: true,
217
+ // A subtle scheme-aware scrim so foreground labels stay readable over busy
218
+ // media even though the surface itself is more transparent.
219
+ scrim: {
220
+ light: 'rgba(0, 0, 0, 0.18)',
221
+ dark: 'rgba(0, 0, 0, 0.28)',
222
+ },
223
+ },
224
+ };
225
+ // ---------------------------------------------------------------------------
226
+ // Frosted DEFAULT-look tokens (the contract for plan 023)
227
+ // ---------------------------------------------------------------------------
228
+ /**
229
+ * The frosted-glass DEFAULT fill: a faint white tint that gives the surface a
230
+ * subtle milky body even before any blur. ≈ `rgba(255, 255, 255, 0.10)`, the
231
+ * `oklch(1 0 0 / 0.1)` equivalent used by the Video.js liquid-glass reference.
232
+ *
233
+ * Defined HERE as the CONTRACT only — plan 022 does NOT apply it to any surface
234
+ * (that would change the committed pixel baseline). Plan 023 wires it onto the
235
+ * frosted DEFAULT look. Distinct in intent from the a11y
236
+ * {@link REDUCED_TRANSPARENCY_FILL} / {@link CONTRAST_FILL} fills, which exist
237
+ * to force opacity under specific OS settings.
238
+ */
239
+ export const FROSTED_DEFAULT_FILL = 'rgba(255, 255, 255, 0.1)';
240
+ /**
241
+ * The frosted-glass DEFAULT hairline ring: a crisp ~1px inset edge that defines
242
+ * the frosted surface's perimeter. `color` is a translucent white drawn as an
243
+ * inset ring; `width` is in px.
244
+ *
245
+ * Defined HERE as the CONTRACT only — plan 022 does NOT apply it (no surface
246
+ * change). Plan 023 serializes it onto the frosted DEFAULT surface (e.g. as an
247
+ * inset `box-shadow` ring, geometry-neutral like the existing bevel).
248
+ */
249
+ export const FROSTED_HAIRLINE_RING = {
250
+ /** Inset ring color (translucent white). */
251
+ color: 'rgba(255, 255, 255, 0.5)',
252
+ /** Ring width in px (hairline). */
253
+ width: 1,
254
+ };