Liquid Glass for the web: the rim refraction, light and motion of iOS 27 glass on any rounded element. React bindings and pill tabs. MIT.
| 1 | /* The API reference for @colineapp/liquid-glass, as data. The page's reference |
| 2 | section renders it, and scripts/agent-docs.ts writes it out as plain text for |
| 3 | agents (llms.txt, agents.md, /docs/*.md, /options.json), so both say the same |
| 4 | thing. Keep it in step with the package's types. */ |
| 5 | |
| 6 | export const PACKAGE = |
| 7 | // The version on npm, which the page and docs name. Bump after each publish. |
| 8 | export const VERSION = "0.1.0"; |
| 9 | export const INSTALL = `npm install ${PACKAGE}`; |
| 10 | // The site's address, which `coline-app deploy --name liquid-glass` gives it. |
| 11 | export const SITE_URL = "https://liquid-glass.coline.space"; |
| 12 | export const NPM_URL = `https://www.npmjs.com/package/${PACKAGE}`; |
| 13 | export const SOURCE_URL = "https://upstream.codes/coline/liquid-glass"; |
| 14 |
| 15 | export const SUMMARY = |
| 16 | "Liquid Glass for the web: the rim refraction, light and motion of iOS 27's glass on any rounded element, with React bindings and tabs whose thumb lifts into a lens. MIT."; |
| 17 |
| 18 | export type ApiEntry = { |
| 19 | /** Option, method or export name. */ |
| 20 | name: string; |
| 21 | type: string; |
| 22 | default?: string; |
| 23 | description: string; |
| 24 | /** A short example an agent can copy as is. */ |
| 25 | example?: string; |
| 26 | }; |
| 27 |
| 28 | export type ApiGroup = { |
| 29 | id: string; |
| 30 | title: string; |
| 31 | intro: string; |
| 32 | entries: ApiEntry[]; |
| 33 | }; |
| 34 |
| 35 | export const GLASS_OPTIONS: ApiGroup = { |
| 36 | id: "options", |
| 37 | title: "Glass options", |
| 38 | intro: |
| 39 | "Options for `<LiquidGlass />`, `useLiquidGlass(ref, options)`, `createLiquidGlass(element, options)` and `glass.update(options)`. Every option is optional.", |
| 40 | entries: [ |
| 41 | { |
| 42 | name: "appearance", |
| 43 | type: '"system" | "class" | "dark" | "light"', |
| 44 | default: '"system"', |
| 45 | description: |
| 46 | '`system` follows prefers-color-scheme. `class` is dark while `<html>` has the `dark` class (Tailwind\'s class strategy, next-themes). `dark` and `light` fix it.', |
| 47 | example: '<LiquidGlass appearance="class" />', |
| 48 | }, |
| 49 | { |
| 50 | name: "variant", |
| 51 | type: '"regular" | "clear" | "lens"', |
| 52 | default: '"regular"', |
| 53 | description: |
| 54 | "`regular` diffuses and evens out the page so text on the glass stays legible over anything. `clear` only bends and lights the page, for glass over media. `lens` is the clear lens a control lifts into; pair it with `geometry: LENS_GEOMETRY`.", |
| 55 | example: '<LiquidGlass variant="clear" />', |
| 56 | }, |
| 57 | { |
| 58 | name: "floating", |
| 59 | type: 'boolean | "dialog"', |
| 60 | default: "false", |
| 61 | description: |
| 62 | 'For menus, popovers, dialogs and sheets. Floating glass diffuses the page more, as iOS 27\'s does, so text beneath washes into soft colour, and the surface casts a drop shadow (`--liquid-glass-shadow`). `"dialog"` casts a dialog\'s deeper shadow (`--liquid-glass-dialog-shadow`).', |
| 63 | example: '<LiquidGlass floating="dialog" />', |
| 64 | }, |
| 65 | { |
| 66 | name: "interactive", |
| 67 | type: "boolean", |
| 68 | default: "false", |
| 69 | description: |
| 70 | "For glass that holds controls, like SwiftUI's `.glassEffect(.regular.interactive())`: the pointer warms the glass beneath it, and a press blooms out from there.", |
| 71 | example: "<LiquidGlass interactive />", |
| 72 | }, |
| 73 | { |
| 74 | name: "swell", |
| 75 | type: "boolean", |
| 76 | default: "false", |
| 77 | description: |
| 78 | "For glass you press, like a search field: pressed, the surface swells about its centre on the tabs' spring and springs back with a slight overshoot on release.", |
| 79 | example: "<LiquidGlass interactive swell />", |
| 80 | }, |
| 81 | { |
| 82 | name: "tint", |
| 83 | type: "string", |
| 84 | default: "near-black in dark, white in light", |
| 85 | description: |
| 86 | "Colour washed over the body by the tone's `tint` share (50% on dark regular glass, 40% on light). Any CSS colour, including `var()`.", |
| 87 | example: '<LiquidGlass tint="var(--background)" />', |
| 88 | }, |
| 89 | { |
| 90 | name: "hue", |
| 91 | type: "string | null", |
| 92 | default: "null", |
| 93 | description: |
| 94 | "A colour whose hue the glass takes on, like tinted iOS glass: each pixel beneath shifts toward the hue at its own brightness, so colours behind keep their chroma. Any concrete CSS colour; neutral colours leave the glass untinted.", |
| 95 | example: '<LiquidGlass hue="#3d6bff" />', |
| 96 | }, |
| 97 | { |
| 98 | name: "hueStrength", |
| 99 | type: "number (0–1)", |
| 100 | default: "1 on light glass, 0.5 on dark", |
| 101 | description: "How much of the hue the glass takes.", |
| 102 | example: '<LiquidGlass hue="#ff4fa3" hueStrength={0.7} />', |
| 103 | }, |
| 104 | { |
| 105 | name: "geometry", |
| 106 | type: "Partial<GlassGeometry>", |
| 107 | default: "DEFAULT_GEOMETRY", |
| 108 | description: |
| 109 | "The rim's shape, measured from iOS 27: its width and depth, how steeply it bends, the darkened edge and the glints. See the geometry fields below. `LENS_GEOMETRY` is the rim for a control-high lens.", |
| 110 | example: "<LiquidGlass geometry={{ maxFall: 0.5, edge: 1 }} />", |
| 111 | }, |
| 112 | { |
| 113 | name: "tone", |
| 114 | type: "{ dark?: Partial<GlassTone>; light?: Partial<GlassTone> }", |
| 115 | description: |
| 116 | "Per-appearance overrides of the variant's filters: body `blur`, `floatingBlur`, `saturate`, `contrast`, `brightness`, `tint` share, and the rim's `highlightContrast`, `highlightBrightness`, `shadeBrightness`, `glowSaturate`, `glowBrightness`, `edgeBrightness`.", |
| 117 | example: "<LiquidGlass tone={{ dark: { brightness: 0.4 }, light: { tint: 0.5 } }} />", |
| 118 | }, |
| 119 | { |
| 120 | name: "fit", |
| 121 | type: '"box" | "fixed" | "scroll"', |
| 122 | default: '"box"', |
| 123 | description: |
| 124 | "`box` fills the surface. `fixed` fills the nearest containing block for fixed elements, such as a transformed popper wrapper, so the glass holds still while the surface scrolls its own content. `scroll` spans the surface's whole scroll height, for a dialog or sheet that scrolls itself.", |
| 125 | example: '<LiquidGlass fit="scroll" floating="dialog" />', |
| 126 | }, |
| 127 | { |
| 128 | name: "refraction", |
| 129 | type: "boolean", |
| 130 | default: "true", |
| 131 | description: |
| 132 | "Bend the page at the rim. Rim refraction needs SVG filters in backdrop-filter, which only Chromium renders; Safari and Firefox keep the material without the bend either way.", |
| 133 | example: "<LiquidGlass refraction={false} />", |
| 134 | }, |
| 135 | { |
| 136 | name: "samples", |
| 137 | type: "1 | 4", |
| 138 | default: "1", |
| 139 | description: |
| 140 | "Refraction samples per pixel. 4 antialiases the bend, so edges it stretches don't stair-step as the glass moves. It costs four displacement passes, so keep it for small glass with nothing blurring its backdrop, like a lens.", |
| 141 | example: '<LiquidGlass variant="lens" samples={4} />', |
| 142 | }, |
| 143 | ], |
| 144 | }; |
| 145 |
| 146 | export const GEOMETRY_FIELDS: ApiGroup = { |
| 147 | id: "geometry", |
| 148 | title: "Geometry fields", |
| 149 | intro: "Fields of `GlassGeometry`, in CSS pixels unless noted. Pass any subset as `geometry`.", |
| 150 | entries: [ |
| 151 | { name: "bezel", type: "number", default: "17", description: "Width of the refracting rim along the top and bottom. The centre is flat." }, |
| 152 | { name: "thickness", type: "number", default: "24", description: "Glass depth under the top and bottom rims. Deeper glass bends further." }, |
| 153 | { name: "sideBezel", type: "number", default: "27", description: "Width of the refracting rim at the sides and rounded ends." }, |
| 154 | { name: "sideThickness", type: "number", default: "44", description: "Glass depth under the sides and rounded ends." }, |
| 155 | { name: "refractiveIndex", type: "number", default: "1.5", description: "The glass's refractive index, for Snell's law at the rim." }, |
| 156 | { |
| 157 | name: "maxFall", |
| 158 | type: "number (0–1)", |
| 159 | default: "0.8", |
| 160 | description: |
| 161 | "Rim bend: the steepest the rim's shift may fall per pixel inward. Where it falls, the rim magnifies what is beneath by 1 / (1 − maxFall), so lower values bend more gently and keep text crossing the rim from smearing. The tab lens uses 0.3. Past 1 the lens folds content into mirrored echoes.", |
| 162 | }, |
| 163 | { name: "edge", type: "number", default: "0.75", description: "Width of iOS 27's darkened edge, a contour where the glass meets the air, with the hairline just inside it. 0 leaves it out." }, |
| 164 | { |
| 165 | name: "glint", |
| 166 | type: "number (0–1)", |
| 167 | default: "0.4", |
| 168 | description: "Share of the hairline's light that gathers into glints where the light catches the curve, at the top-left and, fainter, the bottom-right. 0 keeps an even band.", |
| 169 | }, |
| 170 | { name: "hairline", type: "number", default: "1", description: "Rim hairline width." }, |
| 171 | { name: "hairlineSides", type: "number (0–1)", default: "0.15", description: "Share of the hairline's strength that reaches the sides." }, |
| 172 | { name: "shadeWidth", type: "number", default: "1", description: "Width of the darkened sides." }, |
| 173 | { name: "glowReach", type: "number", default: "8", description: "How far the inner glow reaches in from the top and bottom rims." }, |
| 174 | { name: "glowBottom", type: "number (0–1)", default: "0.5", description: "Bottom glow strength relative to the top." }, |
| 175 | ], |
| 176 | }; |
| 177 |
| 178 | export const HANDLE_METHODS: ApiGroup = { |
| 179 | id: "handle", |
| 180 | title: "The glass handle", |
| 181 | intro: |
| 182 | "`createLiquidGlass()` and `attachLiquidGlass()` return it; `useLiquidGlass()` returns a ref to it, and `<LiquidGlass glassRef={ref} />` fills one.", |
| 183 | entries: [ |
| 184 | { name: "update(options)", type: "(options: LiquidGlassOptions) => void", description: "Replaces the options. Cheap: the rim only rebuilds when the geometry changes.", example: 'glass.update({ variant: "clear" });' }, |
| 185 | { |
| 186 | name: "magnify(factor)", |
| 187 | type: "(factor: number) => void", |
| 188 | description: "Magnifies what is beneath the glass about its centre, as a raised lens does (1 for none), and sharpens it so it doesn't come out soft. Cheap enough to call every frame. Needs refraction.", |
| 189 | example: "glass.magnify(1.1);", |
| 190 | }, |
| 191 | { |
| 192 | name: "materialize(timing?)", |
| 193 | type: "(timing?: { duration?, easing?, delay?, scale? }) => Promise<void>", |
| 194 | description: |
| 195 | "Grows the glass from clear to its tone, for surfaces that appear. Fade the surface's contents, never the surface: opacity below 1 hides the glass. For a surface that zooms open, scale its contents and pass the same `scale`; the glass grows by its inset instead.", |
| 196 | example: "await glass.materialize({ duration: 240, scale: 0.96 });", |
| 197 | }, |
| 198 | { name: "dematerialize(timing?)", type: "(timing?) => Promise<void>", description: "Fades the glass back to clear and holds it there, for surfaces that leave. Wells on it ease back with it.", example: "await glass.dematerialize({ duration: 160 });" }, |
| 199 | { name: "destroy()", type: "() => void", description: "Removes the glass and its filters." }, |
| 200 | { name: "element", type: "HTMLElement", description: "The glass element, the surface's first child." }, |
| 201 | { name: "appearance", type: '"dark" | "light"', description: "The resolved appearance." }, |
| 202 | ], |
| 203 | }; |
| 204 |
| 205 | export const TABS_OPTIONS: ApiGroup = { |
| 206 | id: "tabs", |
| 207 | title: "Tabs", |
| 208 | intro: |
| 209 | "`useLiquidGlassTabs(trackRef, options)` in React, `attachLiquidGlassTabs(track, options)` without it. The track is the tabs' positioned, rounded container. Your tabs and selection stay yours; the thumb follows the selected tab, lifts into a lens that magnifies the labels, stretches as it travels and squashes as it lands. Dragging the selected tab scrubs across the others. Returns `{ update(), destroy() }`.", |
| 210 | entries: [ |
| 211 | { name: "appearance", type: '"system" | "class" | "dark" | "light"', default: '"system"', description: "As for glass." }, |
| 212 | { name: "orientation", type: '"horizontal" | "vertical"', default: '"horizontal"', description: "Vertical tabs stack and don't scrub." }, |
| 213 | { name: "tabs", type: "string", default: '\'[role="tab"]\'', description: "Selector for the tabs inside the track.", example: "useLiquidGlassTabs(track, { tabs: '[role=\"radio\"]' });" }, |
| 214 | { |
| 215 | name: "isSelected", |
| 216 | type: "(tab: HTMLElement) => boolean", |
| 217 | default: 'aria-selected, aria-checked or data-state="active"', |
| 218 | description: "Whether a tab is selected. The thumb follows changes to the default attributes by itself; call `update()` after changes to anything else.", |
| 219 | }, |
| 220 | { name: "onSelect", type: "(tab: HTMLElement) => void", default: "press and click the tab", description: "Selects the tab the thumb was dragged onto." }, |
| 221 | { |
| 222 | name: "glass", |
| 223 | type: "boolean | LiquidGlassOptions", |
| 224 | default: "glass unless on another glass surface", |
| 225 | description: "The track's own glass, or its options. On another glass surface the track gets none of its own: it becomes a recessed tint and the thumb a well in that glass.", |
| 226 | }, |
| 227 | { name: "refraction", type: "boolean", default: "true", description: "Bend the page at the rims." }, |
| 228 | ], |
| 229 | }; |
| 230 |
| 231 | export const REACT_API: ApiGroup = { |
| 232 | id: "react", |
| 233 | title: "React", |
| 234 | intro: "From `@colineapp/liquid-glass/react`. React 18 or later; the core package has no dependencies.", |
| 235 | entries: [ |
| 236 | { |
| 237 | name: "<LiquidGlass />", |
| 238 | type: "LiquidGlassOptions & { className?, style?, children?, glassRef? }", |
| 239 | description: "Glass behind a surface's content. Render it first inside a positioned, rounded element. `glassRef` receives the handle, for materialize() and magnify().", |
| 240 | example: '<nav className="relative rounded-full px-6 py-3">\n <LiquidGlass interactive />\n …\n</nav>', |
| 241 | }, |
| 242 | { name: "useLiquidGlass(ref, options)", type: "RefObject<LiquidGlass | null>", description: "Turns an element you already render into a glass surface; the glass is inserted as its first child." }, |
| 243 | { name: "useLiquidGlassTabs(ref, options)", type: "RefObject<LiquidGlassTabs | null>", description: "Liquid Glass tabs on a track you render. See Tabs." }, |
| 244 | ], |
| 245 | }; |
| 246 |
| 247 | export const CSS_API: ApiGroup = { |
| 248 | id: "css", |
| 249 | title: "CSS", |
| 250 | intro: "Import `@colineapp/liquid-glass/styles.css` once. The filters themselves are set inline by the script, as literal values Safari can read.", |
| 251 | entries: [ |
| 252 | { name: ".liquid-glass-well", type: "class", description: "For a selected or hovered control on a glass surface: brightens the glass beneath it (dimmer in light), instead of glass on glass.", example: '<button className="liquid-glass-well rounded-full">Today</button>' }, |
| 253 | { name: "--liquid-glass-shadow", type: "custom property", description: "The shadow floating glass casts. Set it on the surface or an ancestor." }, |
| 254 | { name: "--liquid-glass-dialog-shadow", type: "custom property", description: 'The deeper shadow of `floating: "dialog"` glass.' }, |
| 255 | ], |
| 256 | }; |
| 257 |
| 258 | export const RULES = [ |
| 259 | "Put the glass first inside a positioned (relative, absolute or fixed), rounded element. It takes the element's corner radius and follows its size.", |
| 260 | "Paint nothing over it: no background, border or box-shadow on the surface. The glass draws its own edge.", |
| 261 | "No opacity below 1, filter, mask or backdrop-filter on the surface or its ancestors while the glass shows. Each makes a backdrop root, and the glass would see only that element instead of the page.", |
| 262 | "To show or hide a surface, fade and zoom its contents, never the surface itself, and call materialize() and dematerialize() for the glass.", |
| 263 | "Controls on glass get no glass of their own: give the selected or hovered one the liquid-glass-well class.", |
| 264 | "Rim refraction renders in Chromium. Safari and Firefox get the same material, light and motion without the bend.", |
| 265 | ]; |
| 266 |
| 267 | export const GROUPS = [GLASS_OPTIONS, GEOMETRY_FIELDS, HANDLE_METHODS, TABS_OPTIONS, REACT_API, CSS_API]; |
| 268 |
| 269 | // ── Copy-paste examples ───────────────────────────────── |
| 270 |
| 271 | export type Example = { id: string; title: string; description: string; language: "tsx" | "ts" | "html"; code: string }; |
| 272 |
| 273 | export const EXAMPLES: Example[] = [ |
| 274 | { |
| 275 | id: "react", |
| 276 | title: "A glass toolbar in React", |
| 277 | description: "Render <LiquidGlass /> first inside a positioned, rounded element.", |
| 278 | language: "tsx", |
| 279 | code: `import { LiquidGlass } from "${PACKAGE}/react"; |
| 280 | import "${PACKAGE}/styles.css"; |
| 281 |
| 282 | export function Toolbar() { |
| 283 | return ( |
| 284 | <nav className="relative flex gap-1 rounded-full p-1.5"> |
| 285 | <LiquidGlass appearance="class" interactive /> |
| 286 | <button className="liquid-glass-well rounded-full px-4 py-2">Today</button> |
| 287 | <button className="rounded-full px-4 py-2">Week</button> |
| 288 | </nav> |
| 289 | ); |
| 290 | }`, |
| 291 | }, |
| 292 | { |
| 293 | id: "vanilla", |
| 294 | title: "Without React", |
| 295 | description: "createLiquidGlass inserts the glass as the element's first child.", |
| 296 | language: "ts", |
| 297 | code: `import { createLiquidGlass } from "${PACKAGE}"; |
| 298 | import "${PACKAGE}/styles.css"; |
| 299 |
| 300 | const glass = createLiquidGlass(document.querySelector(".toolbar")!, { |
| 301 | interactive: true, |
| 302 | }); |
| 303 |
| 304 | glass.update({ variant: "clear" }); |
| 305 | glass.destroy();`, |
| 306 | }, |
| 307 | { |
| 308 | id: "tabs", |
| 309 | title: "Pill tabs", |
| 310 | description: "Your tabs, your selection; the thumb follows aria-selected.", |
| 311 | language: "tsx", |
| 312 | code: `import { useRef } from "react"; |
| 313 | import { useLiquidGlassTabs } from "${PACKAGE}/react"; |
| 314 |
| 315 | export function Period({ value, onChange }: { value: string; onChange: (value: string) => void }) { |
| 316 | const track = useRef<HTMLDivElement>(null); |
| 317 | useLiquidGlassTabs(track, { appearance: "class" }); |
| 318 | return ( |
| 319 | <div ref={track} role="tablist" className="flex rounded-full p-1"> |
| 320 | {["Day", "Week", "Month"].map((label) => ( |
| 321 | <button key={label} role="tab" aria-selected={label === value} onClick={() => onChange(label)} |
| 322 | className="rounded-full px-4 py-1.5 text-sm font-medium"> |
| 323 | {label} |
| 324 | </button> |
| 325 | ))} |
| 326 | </div> |
| 327 | ); |
| 328 | }`, |
| 329 | }, |
| 330 | { |
| 331 | id: "menu", |
| 332 | title: "A menu that materializes", |
| 333 | description: "Fade the contents, never the surface; the glass grows from clear.", |
| 334 | language: "tsx", |
| 335 | code: `import { useEffect, useRef, useState } from "react"; |
| 336 | import { LiquidGlass, type LiquidGlassHandle } from "${PACKAGE}/react"; |
| 337 |
| 338 | export function Menu({ open, items }: { open: boolean; items: string[] }) { |
| 339 | const glass = useRef<LiquidGlassHandle | null>(null); |
| 340 | const [active, setActive] = useState(-1); |
| 341 | useEffect(() => { |
| 342 | if (open) void glass.current?.materialize({ duration: 240, scale: 0.96 }); |
| 343 | }, [open]); |
| 344 | if (!open) return null; |
| 345 | return ( |
| 346 | <div role="menu" className="absolute right-0 top-12 w-56 rounded-2xl p-1.5"> |
| 347 | <LiquidGlass glassRef={glass} appearance="class" floating /> |
| 348 | {items.map((item, index) => ( |
| 349 | <button key={item} role="menuitem" onPointerEnter={() => setActive(index)} |
| 350 | className={\`w-full rounded-xl px-3 py-2 text-left \${index === active ? "liquid-glass-well" : ""}\`}> |
| 351 | {item} |
| 352 | </button> |
| 353 | ))} |
| 354 | </div> |
| 355 | ); |
| 356 | }`, |
| 357 | }, |
| 358 | { |
| 359 | id: "dialog", |
| 360 | title: "A glass dialog", |
| 361 | description: 'floating="dialog" casts the deeper shadow; the glass materializes as the contents zoom in.', |
| 362 | language: "tsx", |
| 363 | code: `import { useLayoutEffect, useRef } from "react"; |
| 364 | import { LiquidGlass, type LiquidGlassHandle } from "${PACKAGE}/react"; |
| 365 |
| 366 | export function ShareDialog({ onClose }: { onClose: () => void }) { |
| 367 | const glass = useRef<LiquidGlassHandle | null>(null); |
| 368 | useLayoutEffect(() => { |
| 369 | void glass.current?.materialize({ duration: 280, scale: 0.95 }); |
| 370 | }, []); |
| 371 | const close = () => void glass.current?.dematerialize({ duration: 180, scale: 0.97 }).then(onClose); |
| 372 | return ( |
| 373 | <div role="dialog" aria-modal="true" className="fixed left-1/2 top-1/3 w-80 -translate-x-1/2 rounded-[28px] p-5"> |
| 374 | <LiquidGlass glassRef={glass} appearance="class" floating="dialog" /> |
| 375 | <h2 className="font-semibold">Share this album</h2> |
| 376 | <button className="liquid-glass-well mt-4 h-10 w-full rounded-full" onClick={close}>Copy link</button> |
| 377 | </div> |
| 378 | ); |
| 379 | }`, |
| 380 | }, |
| 381 | { |
| 382 | id: "search", |
| 383 | title: "A search field that swells", |
| 384 | description: "interactive lights it under the pointer; swell springs it on press.", |
| 385 | language: "tsx", |
| 386 | code: `<label className="relative flex h-11 items-center gap-2 rounded-full px-4"> |
| 387 | <LiquidGlass appearance="class" interactive swell /> |
| 388 | <SearchIcon /> |
| 389 | <input className="bg-transparent outline-none" placeholder="Search" /> |
| 390 | </label>`, |
| 391 | }, |
| 392 | { |
| 393 | id: "card", |
| 394 | title: "A glass card", |
| 395 | description: "Regular glass on a rounded box: the rim bends what crosses it, and the body keeps the card's text legible.", |
| 396 | language: "tsx", |
| 397 | code: `import { LiquidGlass } from "${PACKAGE}/react"; |
| 398 | import "${PACKAGE}/styles.css"; |
| 399 |
| 400 | export function Card() { |
| 401 | return ( |
| 402 | <div className="relative grid gap-1 rounded-[30px] p-5"> |
| 403 | <LiquidGlass appearance="class" /> |
| 404 | <h3 className="font-semibold">Now playing</h3> |
| 405 | <p className="text-sm opacity-70">Glass takes the box's radius and follows its size.</p> |
| 406 | </div> |
| 407 | ); |
| 408 | }`, |
| 409 | }, |
| 410 | ]; |
| 411 |