Add web animation skills (iart-ai/web-animation-skills)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYeokZtSmqg42ZGWsLXX2C
This commit is contained in:
42 files changed
+4170
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,177 @@
|
||||
---
|
||||
name: glassmorphism
|
||||
description: This skill should be used when the user asks to "add a glassmorphism effect", "frosted glass UI", "Apple liquid glass style", "frosted blur card", "translucent glass panel animation", "make a frosted nav bar", "build a glass modal/dialog", "animate a glass card on hover", or "add a refracting liquid-glass hero". Covers frosted translucent panels with backdrop-filter blur, edge/specular highlights, SVG liquid-glass refraction, motion on hover/scroll/enter, and accessible reduced-transparency fallbacks.
|
||||
version: 0.1.0
|
||||
---
|
||||
|
||||
# Glassmorphism (Frosted & Liquid Glass)
|
||||
|
||||
Translucent, frosted UI: panels you can see through, blurred so the backdrop reads as soft color, rimmed by a bright edge of light, floating on layered shadow. The 2026 "liquid glass" evolution adds refraction — the background visibly bends through the panel — plus moving specular highlights. The goal is depth and material, not just opacity.
|
||||
|
||||
## When to use
|
||||
|
||||
- Frosted cards, nav bars, sidebars, modals/dialogs, and toolbars over busy/photo backgrounds
|
||||
- Login/hero panels and overlays that should feel layered above the page
|
||||
- Apple-style "liquid glass" heroes: refracting, slowly churning surfaces with a specular sweep
|
||||
- Hover/scroll/enter motion on glass — frost deepening, highlight brightening, the panel lifting
|
||||
- Any place where content needs to read as floating glass while staying legible
|
||||
|
||||
## The four ingredients of believable glass
|
||||
|
||||
Glass is never one property. Every panel needs all four, or it reads as a plain translucent box:
|
||||
|
||||
1. **Frost** — `backdrop-filter: blur()` plus `saturate()` to keep backdrop color vivid through the blur.
|
||||
2. **Translucency** — a `background` with alpha (~0.08–0.18), never fully opaque, never fully clear.
|
||||
3. **Edge of glass** — a hairline border *brighter* than the fill, ideally a lit top-left → dim bottom-right highlight, so the rim catches light.
|
||||
4. **Depth** — layered `box-shadow`: an ambient drop shadow to float it, plus an `inset` top highlight and bottom shade to round the surface.
|
||||
|
||||
```css
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
backdrop-filter: blur(16px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(160%); /* Safari needs the prefix */
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.25), /* float */
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.35), /* top highlight */
|
||||
inset 0 -1px 0 rgba(0, 0, 0, 0.12); /* bottom shade */
|
||||
}
|
||||
```
|
||||
|
||||
Frost tiers: light UI chrome `blur(8–12px)` / alpha `0.08–0.14`; heavy modal/login `blur(24–40px)` / alpha `0.10–0.18`. Dark glass: fill `rgba(0,0,0,0.3)`, softer top highlight.
|
||||
|
||||
**`backdrop-filter` only blurs content that sits behind the element.** Over a flat background color it does nothing visible — glass needs a busy backdrop (image, gradient, video) to frost.
|
||||
|
||||
## Specular edge highlight and anti-banding grain
|
||||
|
||||
A flat border is dead. Draw a masked 1px ring with a lit corner via `::before`, and dither away gradient **banding** (stepped contour lines under heavy blur) with a faint `feTurbulence` noise overlay via `::after` at `opacity ≤ 0.06`. Full masked-border and inline-noise recipes are in `references/glass-recipes.md`.
|
||||
|
||||
## Motion: animate the glass, not the geometry
|
||||
|
||||
Animate the *qualities* of the glass — frost deepening, highlight brightening, the panel lifting — so it reads as the surface "leaning into the light."
|
||||
|
||||
```css
|
||||
.glass {
|
||||
transition: backdrop-filter .4s ease, background .4s ease,
|
||||
box-shadow .4s ease, transform .4s cubic-bezier(.16,1,.3,1);
|
||||
}
|
||||
.glass:hover {
|
||||
backdrop-filter: blur(24px) saturate(180%);
|
||||
background: rgba(255,255,255,.18);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 16px 48px rgba(0,0,0,.3), inset 0 1px 0 rgba(255,255,255,.5);
|
||||
}
|
||||
```
|
||||
|
||||
- **Entrance** (frost resolving from clear): pair a `[data-enter]` transition with `@starting-style { backdrop-filter: blur(0); opacity: 0; transform: translateY(16px) }`.
|
||||
- **Specular sweep**: a skewed white gradient animated across the surface on hover or as a slow ambient loop.
|
||||
- **Caveat**: animating the blur *radius* is the most expensive thing here — the backdrop is re-blurred every frame. If hover janks, animate `transform`/`box-shadow`/`background` (cheap) and keep the blur delta small or snap it.
|
||||
|
||||
## Liquid glass — SVG refraction (the 2026 look)
|
||||
|
||||
Bend the background through the panel like a thick lens: drive `feDisplacementMap` from animated `feTurbulence`, applied via `filter` (it distorts the element's own pixels) layered over a `backdrop-filter` frost.
|
||||
|
||||
```html
|
||||
<svg width="0" height="0" style="position:absolute"><filter id="liquid-glass">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.008 0.012" numOctaves="2" seed="3" result="n">
|
||||
<animate attributeName="baseFrequency" dur="18s"
|
||||
values="0.008 0.012;0.012 0.008;0.008 0.012" repeatCount="indefinite"/>
|
||||
</feTurbulence>
|
||||
<feDisplacementMap in="SourceGraphic" in2="n" scale="22" xChannelSelector="R" yChannelSelector="G"/>
|
||||
</filter></svg>
|
||||
```
|
||||
|
||||
`scale` = refraction strength (8–14 subtle, 20–30 pronounced, >40 molten). Lower `baseFrequency` = larger, slower waves (more liquid). Keep the `animate` `dur` long (12–20s) so it breathes. These filters are heavy — use on **one** hero element, not a grid.
|
||||
|
||||
## Performance — backdrop-filter is expensive
|
||||
|
||||
- Each glass element re-samples and blurs everything behind it. **Limit the count**; a dozen on screen tanks scroll FPS.
|
||||
- Promote heavy glass to its own layer (`transform: translateZ(0)` or `will-change: transform`), and remove `will-change` after the animation.
|
||||
- Don't stack `backdrop-filter` elements over each other — nested blur re-blurs the blur (quadratic cost).
|
||||
- Big radii (>40px) over large areas are the worst case; cap radius and panel size.
|
||||
|
||||
## Accessibility — non-negotiable
|
||||
|
||||
Glass is decoration; content must stay legible and the effect must be defeatable.
|
||||
|
||||
```css
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
.glass { background: #1a1a2e; backdrop-filter: none; -webkit-backdrop-filter: none; }
|
||||
.glass::after { display: none; } /* drop grain */
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.glass { transition: none; }
|
||||
.liquid { filter: none; } /* stop the refraction churn */
|
||||
}
|
||||
@supports not (backdrop-filter: blur(1px)) {
|
||||
.glass { background: rgba(30,30,50,.85); } /* opaque-enough fallback */
|
||||
}
|
||||
```
|
||||
|
||||
**Contrast over busy backgrounds**: translucent panels over photos can drop text below WCAG. Add a semi-opaque scrim or darkening gradient *under* the text, optionally `text-shadow: 0 1px 2px rgba(0,0,0,.4)`, and verify ≥ 4.5:1 body / ≥ 3:1 large against the *brightest* region of the backdrop.
|
||||
|
||||
## Deliver & verify (standalone HTML)
|
||||
|
||||
> **Packaged helper** (`scripts/`): `scripts/seek-shot.sh anim.html 0 1.5 3` freezes the `?t=N` harness and screenshots each moment; `scripts/contact-sheet.sh sheet.png frame-*.png` tiles them for one-glance review. See `scripts/README.md`.
|
||||
|
||||
For a self-contained glass demo (card, nav, modal, liquid-glass hero) the deliverable is **one HTML file that opens directly in a browser** — markup, CSS, and (if liquid) one inline SVG filter, no build step. A single file is the right tier for a glass surface; don't reach for a bundler. The panel **must overlap a real backdrop** (drop in a photo or gradient) or there is nothing to frost and the demo lies.
|
||||
|
||||
**Output contract:**
|
||||
- One `.html` file: a busy backdrop, your glass markup, the CSS (frost + edge highlight + grain + shadows), and any inline SVG `feDisplacementMap` filter.
|
||||
- A way to land on the **resolved state** *and* a **frozen frame** of any loop — glass verifies by both state (rest/hover/enter) and time (the sweep/refraction).
|
||||
- Include the freeze harness below so any moment can be pinned for inspection.
|
||||
|
||||
**Seek harness — pin a deterministic state + frame.** `?state=hover` forces the hover look; `?t=N` freezes CSS animations and the SVG refraction at `N` seconds, so a screenshot lands on a still, deterministic frame:
|
||||
|
||||
```html
|
||||
<script>
|
||||
const p = new URLSearchParams(location.search);
|
||||
const state = p.get("state");
|
||||
if (state) document.documentElement.dataset.state = state; // CSS keys off :root[data-state="hover"]
|
||||
const t = p.get("t");
|
||||
if (t !== null) {
|
||||
document.getAnimations().forEach(a => { a.currentTime = parseFloat(t)*1000; a.pause(); }); // freeze CSS sweep
|
||||
const svg = document.querySelector("svg");
|
||||
svg?.pauseAnimations?.(); svg?.setCurrentTime?.(parseFloat(t)); // freeze refraction
|
||||
}
|
||||
window.__ready = true;
|
||||
</script>
|
||||
```
|
||||
|
||||
**Verify loop — render → set state/freeze → screenshot → check:** open each meaningful state (`?state=rest`, `?state=hover`) and a frozen loop frame (`?t=2`), screenshot, and confirm the glass **reads as glass** — blur and edge highlight visibly present, no color banding, text contrast holds over the backdrop — plus check **artifacts** (flat box because the backdrop is plain color, missing `-webkit-` prefix in Safari, refraction too strong/molten, jank from animating blur). Then load `prefers-reduced-transparency` and confirm the **solid fallback renders** legibly. Any headless tool works:
|
||||
|
||||
```bash
|
||||
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/glass.html?state=hover" hover.png
|
||||
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/glass.html?t=2" loop.png
|
||||
```
|
||||
|
||||
To check the fallback headlessly, emulate the media feature (e.g. Playwright `--color-scheme`-style flags, or a `?reduced=1` query that toggles a class mirroring the media query) and screenshot that the panel is solid and readable.
|
||||
|
||||
**Before you finish:**
|
||||
1. Opens standalone — no console errors; the glass overlaps a real busy backdrop, not a flat color.
|
||||
2. Reads as glass — blur + bright edge highlight both visible, layered shadow floats it, no banding (grain present).
|
||||
3. Text contrast holds over the brightest part of the backdrop (≥ 4.5:1 body).
|
||||
4. `?state=` / `?t=N` freeze lands a deterministic, settled frame.
|
||||
5. `prefers-reduced-transparency` → solid opaque panel; `prefers-reduced-motion` → refraction/sweep stopped; `@supports` fallback for no `backdrop-filter`.
|
||||
6. Performance sane — glass count limited, blur not animated on a janky path, liquid refraction on one element only.
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Need | Approach |
|
||||
|------|----------|
|
||||
| Base frost | `backdrop-filter: blur(16px) saturate(160%)` + alpha bg |
|
||||
| Edge of glass | brighter `border` + masked `::before` lit-corner ring |
|
||||
| Float / depth | `box-shadow` drop + `inset` top highlight & bottom shade |
|
||||
| Kill banding | `feTurbulence` noise `::after`, `opacity ≤ .06` |
|
||||
| Hover motion | transition `transform`/`box-shadow`/`background`, small blur delta |
|
||||
| Entrance | `@starting-style { backdrop-filter: blur(0); opacity:0 }` |
|
||||
| Liquid refraction | `feDisplacementMap` ← animated `feTurbulence`, via `filter` |
|
||||
| Specular sweep | skewed white gradient animated across surface |
|
||||
| Safari | always pair `-webkit-backdrop-filter` |
|
||||
| Reduced transparency | solid opaque panel, drop grain |
|
||||
| No backdrop-filter | `@supports not (...)` opaque-enough fallback |
|
||||
|
||||
## Reference files
|
||||
|
||||
- `references/glass-recipes.md` — full copy-paste recipes: base glass card with tiers, the masked specular edge ring, anti-banding grain, hover/enter/scroll motion, the specular sweep, the SVG `feDisplacementMap`/`feTurbulence` liquid-glass filter with tuning knobs, performance rules, the reduced-transparency/reduced-motion/`@supports` fallbacks, contrast defenses, and the standalone freeze harness.
|
||||
@@ -0,0 +1,242 @@
|
||||
# Glassmorphism Recipes
|
||||
|
||||
Copy-paste recipes for frosted translucent panels, the "edge of glass" highlight, animated liquid-glass refraction, and the reduced-transparency fallback. All standalone CSS + optional inline SVG — no build step.
|
||||
|
||||
## 1. The base glass card
|
||||
|
||||
The four ingredients of believable glass: **blur** (frost), **translucency** (you see through it), **edge highlight** (light catches the rim), **layered shadow** (it floats). Plus a touch of `saturate()` so the backdrop reads vivid through the frost.
|
||||
|
||||
```css
|
||||
.glass {
|
||||
/* translucency — never fully opaque, never fully clear */
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
/* frost: blur the backdrop, lift saturation so color survives the blur */
|
||||
backdrop-filter: blur(16px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(160%); /* Safari still needs the prefix */
|
||||
border-radius: 20px;
|
||||
/* edge of glass: a hairline border slightly brighter than the fill */
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
/* depth: ambient drop + a top inner highlight + a bottom inner shade */
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.25),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.35),
|
||||
inset 0 -1px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
```
|
||||
|
||||
Tuning by tier:
|
||||
- **Light frost** (UI chrome over content): `blur(8–12px)`, alpha `0.08–0.14`.
|
||||
- **Heavy frost** (modal scrim, login card): `blur(24–40px)`, alpha `0.10–0.18`.
|
||||
- Dark glass: swap the fill to `rgba(0,0,0,0.3)` and the top inner highlight to a softer white.
|
||||
|
||||
`backdrop-filter` only has anything to blur if there is content **behind** the element — it must overlap a busy background (image, gradient, video). On a flat color it looks like a plain translucent box.
|
||||
|
||||
## 2. The specular edge highlight (the rim of light)
|
||||
|
||||
A single border is flat. Real glass catches a bright sweep along the top-left edge and falls off. Layer a soft conic/linear highlight via a pseudo-element with a masked border.
|
||||
|
||||
```css
|
||||
.glass {
|
||||
position: relative;
|
||||
isolation: isolate; /* keep the highlight blend local */
|
||||
}
|
||||
.glass::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 1px; /* border thickness */
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(255, 255, 255, 0.7),
|
||||
rgba(255, 255, 255, 0) 40%,
|
||||
rgba(255, 255, 255, 0) 60%,
|
||||
rgba(255, 255, 255, 0.25)
|
||||
);
|
||||
/* mask trick: show only the 1px ring, punch out the interior */
|
||||
-webkit-mask:
|
||||
linear-gradient(#000 0 0) content-box,
|
||||
linear-gradient(#000 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
```
|
||||
|
||||
This gives the panel a lit top-left corner and a dim bottom-right, the cue the brain reads as "curved glass under a light."
|
||||
|
||||
## 3. Kill banding with grain
|
||||
|
||||
Heavy `blur()` over a smooth gradient produces visible color **banding** (stepped contour lines). A faint noise overlay dithers it away.
|
||||
|
||||
```css
|
||||
.glass::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
opacity: 0.04; /* barely there */
|
||||
mix-blend-mode: overlay;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||
}
|
||||
```
|
||||
|
||||
`feTurbulence` generates the noise tile inline — no asset file. Keep opacity ≤ 0.06 or it reads as texture, not dither.
|
||||
|
||||
## 4. Motion: animate the glass on hover / enter / scroll
|
||||
|
||||
Animate the *qualities* of the glass, not its geometry. Frost deepening and the highlight brightening on hover reads as the panel "leaning into the light."
|
||||
|
||||
```css
|
||||
.glass {
|
||||
transition:
|
||||
backdrop-filter 0.4s ease,
|
||||
background 0.4s ease,
|
||||
box-shadow 0.4s ease,
|
||||
transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
.glass:hover {
|
||||
backdrop-filter: blur(24px) saturate(180%);
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
transform: translateY(-4px);
|
||||
box-shadow:
|
||||
0 16px 48px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
```
|
||||
|
||||
Caveat: animating `backdrop-filter` itself is the most expensive thing here — the backdrop is re-sampled and re-blurred every frame. If hover feels janky, animate only `transform` + `box-shadow` + `background` (cheaper) and snap the blur, or keep the blur change small.
|
||||
|
||||
**Entrance** (frost resolving in) reads beautifully with `@starting-style`:
|
||||
|
||||
```css
|
||||
.glass[data-enter] {
|
||||
transition: opacity 0.5s ease, backdrop-filter 0.6s ease, transform 0.5s ease;
|
||||
}
|
||||
@starting-style {
|
||||
.glass[data-enter] {
|
||||
opacity: 0;
|
||||
backdrop-filter: blur(0px) saturate(100%);
|
||||
transform: translateY(16px) scale(0.98);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Specular sweep** — a moving glint across the surface on hover or as an ambient loop:
|
||||
|
||||
```css
|
||||
.glass .sweep {
|
||||
position: absolute; inset: 0; border-radius: inherit; overflow: hidden; pointer-events: none;
|
||||
}
|
||||
.glass .sweep::before {
|
||||
content: ""; position: absolute; top: 0; left: -60%; width: 50%; height: 100%;
|
||||
background: linear-gradient(105deg, transparent, rgba(255,255,255,0.35), transparent);
|
||||
transform: skewX(-15deg);
|
||||
animation: sweep 4s ease-in-out infinite;
|
||||
}
|
||||
@keyframes sweep { 0%,100% { left: -60%; } 50% { left: 110%; } }
|
||||
```
|
||||
|
||||
## 5. Liquid glass — SVG refraction (the 2026 look)
|
||||
|
||||
Apple-style "liquid glass" bends the background as if seen through a thick, wobbling lens. Drive a `feDisplacementMap` from animated `feTurbulence` and apply it via `filter` (note: `filter`, not `backdrop-filter` — it distorts the element's own rendered content; layer it over a `backdrop-filter` frost for the full effect).
|
||||
|
||||
```html
|
||||
<svg width="0" height="0" style="position:absolute">
|
||||
<filter id="liquid-glass">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.008 0.012"
|
||||
numOctaves="2" seed="3" result="noise">
|
||||
<!-- animate the field so the refraction slowly churns -->
|
||||
<animate attributeName="baseFrequency"
|
||||
dur="18s" values="0.008 0.012; 0.012 0.008; 0.008 0.012"
|
||||
repeatCount="indefinite"/>
|
||||
</feTurbulence>
|
||||
<feDisplacementMap in="SourceGraphic" in2="noise"
|
||||
scale="22" xChannelSelector="R" yChannelSelector="G"/>
|
||||
</filter>
|
||||
</svg>
|
||||
```
|
||||
|
||||
```css
|
||||
.liquid {
|
||||
backdrop-filter: blur(6px) saturate(150%); /* light frost under the refraction */
|
||||
filter: url(#liquid-glass); /* the lens distortion */
|
||||
}
|
||||
```
|
||||
|
||||
Knobs:
|
||||
- `scale` on `feDisplacementMap` = refraction strength. 8–14 = subtle ripple; 20–30 = pronounced lens; >40 = molten.
|
||||
- Lower `baseFrequency` = larger, slower waves (more "liquid"); higher = tight, busy ripples.
|
||||
- Animate `baseFrequency` (above) or `scale` for a living surface. Keep `dur` long (12–20s) so it breathes rather than shimmers.
|
||||
|
||||
Cost warning: SVG displacement filters are heavy and force off-GPU paths in some browsers. Use on **one** hero element, not a grid of cards. Test on a mid-range laptop; drop to plain `backdrop-filter` if frames stutter.
|
||||
|
||||
## 6. Performance — backdrop-filter is expensive
|
||||
|
||||
- Each `backdrop-filter` element re-samples and blurs everything behind it. **Limit the count** — a dozen glass cards on screen will tank scroll FPS.
|
||||
- Promote heavy glass to its own compositor layer: `transform: translateZ(0)` or `will-change: transform` (remove `will-change` after the animation; leaving it on wastes memory).
|
||||
- Avoid stacking `backdrop-filter` elements over each other (nested blur re-blurs the blur — quadratic cost).
|
||||
- Prefer animating `transform`/`opacity`/`box-shadow` over animating the blur radius. If you must animate blur, keep the radius delta small.
|
||||
- Big blur radii (>40px) over large areas are the single worst case; cap the radius and the panel size.
|
||||
|
||||
## 7. Accessibility — non-negotiable
|
||||
|
||||
Glass is decorative; the content on it must stay legible and the effect must be defeatable.
|
||||
|
||||
**Honor reduced transparency and reduced motion** — fall back to a solid, opaque panel:
|
||||
|
||||
```css
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
.glass {
|
||||
background: #1a1a2e; /* solid, accessible panel */
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
.glass::after { display: none; } /* drop grain/noise */
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.glass, .glass .sweep::before { transition: none; animation: none; }
|
||||
.liquid { filter: none; } /* stop the churning refraction */
|
||||
}
|
||||
```
|
||||
|
||||
**Contrast over busy backgrounds.** Translucent panels over photos can drop text below WCAG contrast. Defenses:
|
||||
- Put a contrast floor *under* the text: a semi-opaque scrim or a darkening gradient inside the panel.
|
||||
- Add `text-shadow: 0 1px 2px rgba(0,0,0,0.4)` to body text on glass.
|
||||
- Target ≥ 4.5:1 for body text, ≥ 3:1 for large text — and verify against the *brightest* region of the backdrop, not the average.
|
||||
|
||||
**Feature fallback** for browsers without `backdrop-filter`:
|
||||
|
||||
```css
|
||||
@supports not (backdrop-filter: blur(1px)) {
|
||||
.glass { background: rgba(30, 30, 50, 0.85); } /* opaque-enough fallback */
|
||||
}
|
||||
```
|
||||
|
||||
## 8. Standalone freeze harness
|
||||
|
||||
Glass demos verify by *state* (rest / hover / enter) and *time* (the sweep/refraction loop). Wire both so a screenshot lands deterministically:
|
||||
|
||||
```html
|
||||
<script>
|
||||
const p = new URLSearchParams(location.search);
|
||||
const state = p.get("state"); // ?state=hover → force the hover look
|
||||
if (state) document.documentElement.dataset.state = state;
|
||||
const t = p.get("t"); // ?t=2 → freeze CSS animations at 2s
|
||||
if (t !== null) {
|
||||
document.getAnimations().forEach(a => { a.currentTime = parseFloat(t) * 1000; a.pause(); });
|
||||
// SVG SMIL (the <animate>): pin the refraction frame too
|
||||
document.querySelector("svg")?.pauseAnimations?.();
|
||||
document.querySelector("svg")?.setCurrentTime?.(parseFloat(t));
|
||||
}
|
||||
window.__ready = true;
|
||||
</script>
|
||||
```
|
||||
|
||||
CSS drives the forced state: `:root[data-state="hover"] .glass { /* hover values */ }`.
|
||||
|
||||
---
|
||||
Frosted glass that reads as glass — blur, edge light, and refraction, with a clean fallback. Built by **[iart.ai](https://iart.ai/?utm_source=github&utm_medium=readme&utm_campaign=web-animation-skills&utm_content=skill_footer&utm_term=glassmorphism)** — the AI motion agent for editable, on-brand motion graphics.
|
||||
Reference in new issue
Block a user