-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdepth.css
More file actions
385 lines (335 loc) · 16.7 KB
/
Copy pathdepth.css
File metadata and controls
385 lines (335 loc) · 16.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/* ==========================================================================
glassmorphism-theme / depth layer
--------------------------------------------------------------------------
The part of this register that tokens cannot carry: the ground the glass is
translucent against, the three elevation tiers as usable classes, and the
stacking rules that keep a frosted surface legible over a lit backdrop.
Every value here is a var() reference to tokens.css. There is not one color
literal in this file.
Requires tokens.css. Load it, then components.css, then this:
<link rel="stylesheet" href="tokens/tokens.css" />
<link rel="stylesheet" href="components/components.css" />
<link rel="stylesheet" href="components/depth.css" />
HOW SEPARABLE THIS IS
Mechanically, completely: nothing in tokens.css or components.css refers to
anything here, so deleting this file and assets/ breaks no rule and throws
no error. Visually it is the opposite of the neobrutalism sibling's play
layer, which could be deleted and leave a quieter version of the same
register behind. Delete this one and there is no register left. Glass is
the only material whose appearance is entirely a function of what is behind
it, and with a flat fill behind it what you have is gray boxes with light
borders. CUSTOMIZE.md treats that honestly rather than as an axis move.
The SVGs in assets/ are meant to be inlined into your markup rather than
loaded through <img>. An SVG in an <img> is an isolated document that
cannot read the page's custom properties, so it would carry its own colors
and the single source of truth would be gone. Inlined, the paint rules
below reach them.
========================================================================== */
/* --------------------------------------------------------------------------
THE GROUND
A fixed layer behind everything, so the light stays put while the page
scrolls over it. Fixed rather than scrolling because a moving light source
states that the reader is travelling past the lights rather than that the
content is travelling past the reader, and the second reading is the one a
page wants.
isolation: isolate rather than a z-index race. The ground establishes a
stacking context so its children cannot be interleaved with page content by
accident.
-------------------------------------------------------------------------- */
.gl-ground {
position: fixed;
inset: 0;
z-index: -1;
isolation: isolate;
overflow: hidden;
background: var(--gl-ground);
pointer-events: none;
}
/* --------------------------------------------------------------------------
THE ORBS
Three light sources, each an inlined SVG taking its hue from the `color`
property set here. They are absolutely positioned as a percentage of the
ground, so the composition survives every viewport without a breakpoint.
Positioned by where the core lands rather than by where the box starts.
left and top name the bright point and the translate pulls the box back
around it, so moving an orb means naming the place you want lit. Hanging
orbs off the corners by their bounding boxes puts the light in the margins
and leaves every panel sitting on flat black, which is the commonest way a
glass page ends up looking like gray boxes.
The transform is safe here, and safe for a duller reason than rule 4 might
suggest: the orbs are not ancestors of anything that carries a
backdrop-filter, so nothing downstream can be affected either way.
-------------------------------------------------------------------------- */
.gl-orb {
position: absolute;
display: block;
width: var(--gl-orb-size);
height: auto;
transform: translate(-50%, -50%);
}
/* The core stop is the ceiling the contrast math depends on. Overriding it
here rather than in the file means the SVGs stay honest when opened alone
and the token stays the single source of the bound. */
.gl-orb .gl-orb-core {
stop-color: currentColor;
stop-opacity: var(--gl-orb-alpha);
}
/* The bent falloff: 40% of the core, so the ramp reads as a bloom rather than
as a disc. calc() on a unitless token, so it tracks the ceiling. */
.gl-orb .gl-orb-mid {
stop-color: currentColor;
stop-opacity: calc(var(--gl-orb-alpha) * 0.4);
}
.gl-orb .gl-orb-inner {
stop-color: currentColor;
stop-opacity: calc(var(--gl-orb-alpha) * 0.25);
}
.gl-orb .gl-orb-edge {
stop-color: currentColor;
stop-opacity: 0;
}
/* The three hues, and three cores that all land inside the frame. Warm high
and to the right, where the fold's working surface sits; primary low and
left, under the body copy; cool at the bottom so a footer is not sitting on
flat black. Warm light above cool light is the arrangement the eye reads as
depth rather than as two stains, because it is the one the sky supplies. */
.gl-orb-drift {
color: var(--gl-orb-violet);
left: 76%;
top: 26%;
width: var(--gl-orb-size);
}
.gl-orb-bloom {
color: var(--gl-orb-indigo);
left: 14%;
top: 68%;
width: var(--gl-orb-size);
}
.gl-orb-halo {
color: var(--gl-orb-teal);
left: 62%;
top: 96%;
width: var(--gl-orb-size-sm);
}
/* --------------------------------------------------------------------------
THE VEIL
A fine grid between the orbs and the glass, and what makes the frost
visible at the panel boundary.
Blur is a local average, so blurring a smooth gradient returns something
close to the gradient. Measured on this theme's own ground, turning
backdrop-filter on over the bare gradient changes the pixels inside a panel
by a mean of 1.86/255, which is not a difference anyone will see.
Adding the veil barely moves that whole-panel figure, to 2.12/255, so the
honest version of this rule is not that the veil makes the blur "work".
What it does is local to the edge: scanned across the ground the grid
reads at a standard deviation of 0.88, and inside a panel it measures 0.
The lines are crisp on the open ground and gone under the glass, and it is
that discontinuity at the boundary, rather than any change in the average,
that the eye reads as a frosted surface.
The pitch is a token because it has to stay under the blur radius. At
--gl-veil-size 44px and --gl-blur-2 20px the lines are visibly softened but
not erased. Push the pitch far below the blur radius and the grid
dissolves into a flat tint, which returns you to blurring a gradient.
This is CSS rather than a fifth file in assets/ for a specific reason: the
pitch cannot be a token inside an SVG. A <pattern> takes its tile size from
its width and height attributes. SVG2 does promote those to CSS geometry
properties, so `#p { width: var(--pitch) }` parses, and getComputedStyle
will report the new value, which makes this look like it works.
It does not. Measured on the rendered output rather than on the computed
style: with the attribute at 10 and CSS setting 40px, the painted tile
stays at a 10px pitch. The declaration is accepted and ignored, which is a
worse failure than being rejected, because the check most people would run
reports success.
So making the pitch a token and the veil an asset are mutually exclusive,
and the token is worth more.
-------------------------------------------------------------------------- */
.gl-veil {
position: absolute;
inset: 0;
opacity: var(--gl-veil-opacity);
background-image:
repeating-linear-gradient(
to right,
var(--gl-veil-line) 0,
var(--gl-veil-line) var(--gl-border-width),
transparent var(--gl-border-width),
transparent var(--gl-veil-size)
),
repeating-linear-gradient(
to bottom,
var(--gl-veil-line) 0,
var(--gl-veil-line) var(--gl-border-width),
transparent var(--gl-border-width),
transparent var(--gl-veil-size)
);
/* The grid runs to the edges of a screen and a grid that reaches a hard
edge reads as a texture applied to the page. Fading it out at the corners
leaves it reading as something the light happens to fall across. */
-webkit-mask-image: radial-gradient(ellipse at 50% 38%, black 0%, transparent 78%);
mask-image: radial-gradient(ellipse at 50% 38%, black 0%, transparent 78%);
}
/* --------------------------------------------------------------------------
THE ELEVATION TIERS
The same three steps components.css uses, as classes, for the surfaces the
five components do not cover.
Blur and alpha rise together because they are two readings of one fact.
Something nearer the viewer occludes more of what is behind it, which is
the alpha, and throws it further out of focus, which is the blur. Raise one
alone and the surface stops reading as a distance: more alpha at the same
blur is a tinted pane rather than a nearer one, and more blur at the same
alpha is a surface that is somehow both far away and unfocused.
Saturation rises with the blur for a mechanical reason. Averaging a region
of colors pulls them toward the mean, and the mean of a colorful region is
grayer than any of it. Lifting saturation puts back what the averaging took
and keeps a blurred orb reading as colored light rather than as haze.
-------------------------------------------------------------------------- */
.gl-elev-1 {
background: var(--gl-surface-1);
-webkit-backdrop-filter: var(--gl-backdrop-1);
backdrop-filter: var(--gl-backdrop-1);
border: var(--gl-border-width) solid var(--gl-edge);
border-radius: var(--gl-radius-lg);
box-shadow: var(--gl-shadow-inset);
}
.gl-elev-2 {
background: var(--gl-surface-2);
-webkit-backdrop-filter: var(--gl-backdrop-2);
backdrop-filter: var(--gl-backdrop-2);
border: var(--gl-border-width) solid var(--gl-edge-boundary);
border-radius: var(--gl-radius-lg);
box-shadow:
inset 0 var(--gl-border-width) 0 0 var(--gl-highlight),
var(--gl-shadow-2);
}
/* Tier 3 carries the scrim without being asked. The tier exists for surfaces
that float over other surfaces, and rule 3 below says a surface over content
takes a scrim. Making it conditional would mean shipping a tier whose
correct use is always the modified one, so the modifier is folded in. The
consequence is worth stating plainly: tier 3 is the only tier whose contrast
does not depend on the ground at all. */
.gl-elev-3 {
background-color: var(--gl-scrim);
background-image: linear-gradient(var(--gl-surface-3), var(--gl-surface-3));
-webkit-backdrop-filter: var(--gl-backdrop-3);
backdrop-filter: var(--gl-backdrop-3);
border: var(--gl-border-width) solid var(--gl-edge-boundary);
border-radius: var(--gl-radius-lg);
box-shadow:
inset 0 var(--gl-border-width) 0 0 var(--gl-highlight),
var(--gl-shadow-3);
}
/* --------------------------------------------------------------------------
THE STACKING RULES
Three rules. The first two are enforced here; the third cannot be.
RULE 1. One backdrop-filter per stack.
A filtered surface inside a filtered surface blurs its parent's finished
output rather than the page, so the alphas multiply instead of adding and
the blur is applied twice to something already blurred. The result is a
milky panel with no depth in it and twice the compositing cost. A nested
surface keeps its fill and drops its filter, which is what the rule below
does. It reads as nearer because its fill is lighter than its parent's,
which is the cue that was doing the work anyway.
-------------------------------------------------------------------------- */
.gl-elev-1 .gl-elev-1,
.gl-elev-1 .gl-elev-2,
.gl-elev-1 .gl-elev-3,
.gl-elev-2 .gl-elev-1,
.gl-elev-2 .gl-elev-2,
.gl-elev-2 .gl-elev-3,
.gl-elev-3 .gl-elev-1,
.gl-elev-3 .gl-elev-2,
.gl-elev-3 .gl-elev-3 {
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
/* --------------------------------------------------------------------------
RULE 2. A tier cannot sit on itself.
Two surfaces at the same alpha, one inside the other, produce a boundary
the reader can only find by its border. Nesting is a statement that one
thing is above another, so the inner surface has to be a tier up. This
catches the case in the browser rather than in review: a same-tier nest
gets the next tier's fill, and the composition still reads.
The pair is listed rather than generated because a tier-3 surface inside a
tier-3 surface has nowhere to go. Three tiers is the ceiling this ground
can carry, so that nest is a design error the CSS cannot fix; the outline
below makes it visible instead of letting it pass. */
.gl-elev-1 > .gl-elev-1 {
background: var(--gl-surface-2);
}
.gl-elev-2 > .gl-elev-2 {
background: var(--gl-surface-3);
}
.gl-elev-3 > .gl-elev-3 {
outline: var(--gl-border-width-heavy) dashed var(--gl-destructive);
}
/* --------------------------------------------------------------------------
RULE 3. A surface over content takes a scrim, not a tier.
The three tiers are measured against the ground, and the ground is bounded
by --gl-ground-peak. A surface whose backdrop is the document rather than
the ground has no such bound behind it, because the document contains ink.
Two cases, and a page of any size has both: a sticky bar with content
scrolling under it, and any surface that floats over another surface.
Measured on this theme, a nav link on a tier-2 fill with body text passing
underneath lands at 1.24:1. The same link over the scrim holds 5.27:1 with
pure white behind it, which is a backdrop no page in this theme can
actually produce.
The tier's fill stays on top of the scrim as a background-image, so the
surface keeps the elevation it was carrying. What changes is what is
underneath: a bounded dark fill instead of whatever happened to scroll
past. The blur is still doing its work, and doing it on something the
reader can no longer read through, which is the point.
-------------------------------------------------------------------------- */
.gl-over-content {
background-color: var(--gl-scrim);
}
.gl-elev-1.gl-over-content {
background-image: linear-gradient(var(--gl-surface-1), var(--gl-surface-1));
}
.gl-elev-2.gl-over-content {
background-image: linear-gradient(var(--gl-surface-2), var(--gl-surface-2));
}
.gl-elev-3.gl-over-content {
background-image: linear-gradient(var(--gl-surface-3), var(--gl-surface-3));
}
/* RULE 4, which CSS cannot enforce: nothing between a surface and the ground
may carry `opacity` below 1 or a `filter`. Either establishes a backdrop
root, the backdrop stops there, and every panel underneath quietly loses
its blur while keeping its fill. There is no selector for "my ancestor has
an opacity", so this one lives in a comment.
Measured rather than assumed, because the usual phrasing of this rule
includes `transform` and that part did not reproduce. Chromium, striped
backdrop, standard deviation of the stripes read through the panel, where a
low number means the blur is working:
no backdrop-filter at all 115 (negative control)
backdrop-filter, no wrapper 2 (positive control)
ancestor transform: translateX 2 blur survived
ancestor transform: translateZ 2 blur survived
ancestor will-change: transform 2 blur survived
ancestor opacity: 0.99 115 BLUR LOST
ancestor filter: blur(0px) 115 BLUR LOST
So `opacity` and `filter` are the two to hunt for. A transform on an
ancestor is not the culprit here, whatever the folklore says, though it
does create a containing block and will move a fixed-position ground out
from under your page, which is its own way of producing flat panels.
Engines have differed on this historically; the figures above are one
engine on one day, which is more than the folklore has. Check ancestors for
opacity and filter first. */
/* --------------------------------------------------------------------------
WHEN THE MATERIAL IS SWITCHED OFF
tokens.css already swaps the surfaces for solids and zeroes the blur under
prefers-reduced-transparency and prefers-contrast. What it cannot do from a
token is remove the geometry, so the ground layer retires its orbs here.
The orbs go rather than dim. A reader who has asked for reduced
transparency has asked not to have text on a variable backdrop, and a faint
orb is still a variable backdrop. The veil goes with them, since its only
job was to give a blur something to soften and there is no longer a blur.
-------------------------------------------------------------------------- */
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
.gl-orb,
.gl-veil {
display: none;
}
}
/* Nothing in this file animates. The ground is fixed rather than parallaxed,
and the orbs hold still, so prefers-reduced-motion has nothing to switch
off here. */