# Liquid Glass Gallery

> A material layer of translucent surfaces, backdrop blur, and specular edge rings (edge refraction ships as a static displacement approximation; dynamic fluidity is provided by the optional `glass-fluid.js` runtime — pointer specular highlight + press tightening; see "Dynamic fluidity" on this page).

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const theme = ref('light')
const open = ref(false)
const msg = ref(null)
const popMsg = () => {
  // messages default to 16px below the viewport top—over the docs site's white nav,
  // where no frost can show; offset the message above the stage gradient so a busy
  // backdrop sits behind it (frost perception depends on the backdrop showing through)
  const stage = document.querySelector('.gg-stage')
  const top = stage ? stage.getBoundingClientRect().top : 0
  const offset = Math.max(16, Math.min(top + 120, window.innerHeight - 120))
  msg.value?.success('Glass message: frosted backdrop shows through', { offset })
}
onMounted(async () => {
  document.documentElement.setAttribute('data-glass', '')
  const { message } = await import('@oas-ui/ui')
  msg.value = message
})
onBeforeUnmount(() => {
  // SPA leak guard: the data-glass activated by the gallery must not leave this page (otherwise every page site-wide turns glassy)
  document.documentElement.removeAttribute('data-glass')
})
</script>

<div class="gg-stage" :data-theme="theme">
  <div class="gg-toolbar">
    <button class="sg-btn" :class="{ 'sg-active': theme === 'light' }" @click="theme = 'light'">light</button>
    <button class="sg-btn" :class="{ 'sg-active': theme === 'dark' }" @click="theme = 'dark'">dark</button>
    <oas-button type="primary" @click="open = true">Open dialog</oas-button>
    <oas-button @click="popMsg">Show message</oas-button>
  </div>
  <div class="gg-controls">
    <oas-button type="primary">Primary</oas-button>
    <oas-button>Default</oas-button>
    <oas-switch checked></oas-switch>
    <oas-slider value="45" style="width: 200px"></oas-slider>
    <span class="gg-note">Edge refraction (static displacement approximation): control outlines bulge along the edge normal, center untouched</span>
  </div>
  <div class="gg-cards">
    <div class="gg-card">
      <strong>Card surface</strong>
      <p>Translucent bg + backdrop-filter blur + specular ring. Richer backdrops make the material pop.</p>
      <oas-tag type="primary">Tag</oas-tag>
    </div>
    <div class="gg-card">
      <strong>Text legibility</strong>
      <p style="color: var(--oas-color-text-secondary)">Secondary text uses the token safe tier and stays legible over busy backdrops.</p>
      <oas-input placeholder="Input (solid control)" style="width: 220px"></oas-input>
    </div>
  </div>
  <oas-modal v-if="open" visible no-mask draggable title="Liquid glass dialog" @oas-close="open = false">
    <p>The gradient and buttons behind show through the panel—blur and specular ring form the glass material.</p>
  </oas-modal>
</div>

<style>
.gg-stage {

  margin: 0 0 var(--oas-space-4);
  padding: var(--oas-space-6);
  border-radius: var(--oas-radius-lg);
  background:
    radial-gradient(720px 420px at 24% 8%, rgba(120, 200, 255, 0.85), transparent 68%),
    radial-gradient(680px 400px at 78% 12%, rgba(255, 130, 190, 0.8), transparent 68%),
    radial-gradient(900px 520px at 50% 30%, rgba(255, 200, 120, 0.75), transparent 66%),
    radial-gradient(1000px 620px at 50% 105%, rgba(80, 220, 170, 0.4), transparent 62%),
    linear-gradient(160deg, #2a3550, #4a2a50 55%, #1f4a44);
  /* The modal centers on the viewport — the stage must fill most of the viewport height so the dialog lands on the gradient (on a dark page background the glass effect would be invisible) */
  min-height: 72vh;
}
.gg-stage[data-theme="dark"] {
  /* Dialog translucency tweak (the modal consumes --oas-color-bg; the variable inherits into the shadow DOM — zero impact on the library) */
  --oas-color-bg: rgba(24, 24, 27, 0.5);
}
.gg-controls { display: flex; align-items: center; gap: var(--oas-space-4); margin-bottom: var(--oas-space-4); padding: var(--oas-space-2) 0; }
.gg-note { color: var(--oas-color-text-secondary); font-size: var(--oas-font-size-sm); }
.gg-toolbar { display: flex; gap: var(--oas-space-3); align-items: center; margin-bottom: var(--oas-space-5) }
.gg-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--oas-space-4) }
.gg-card {
  background: var(--oas-color-bg-elevated);
  backdrop-filter: var(--oas-glass-blur, none);
  -webkit-backdrop-filter: var(--oas-glass-blur, none);
  outline: 1px solid var(--oas-glass-ring, transparent);
  outline-offset: -1px;
  border-radius: var(--oas-radius-lg);
  box-shadow: var(--oas-shadow-md);
  padding: var(--oas-space-4);
  color: var(--oas-color-text-primary);
}
.sg-btn {
  padding: var(--oas-space-1) var(--oas-space-3);
  border: 1px solid var(--oas-color-border);
  border-radius: var(--oas-radius-full, 999px);
  background: var(--oas-color-bg-elevated);
  color: var(--oas-color-text-primary);
  cursor: pointer;
}
.sg-btn.sg-active { border-color: var(--oas-color-primary); color: var(--oas-color-primary) }
</style>

## Usage

```html
<link rel="stylesheet" href="https://unpkg.com/@oas-ui/theme@2/glass.css" />
<html data-glass>
```

- `data-glass` stacks on top of `data-theme` / `data-skin`—freely composable;
- the material needs backdrop contrast: it shines over colorful gradients/photos and recedes on flat same-hue backgrounds (text stays legible);
- local opt-out: override `--oas-glass-blur: none; --oas-glass-ring: transparent; --oas-glass-refraction: ;` on any container (the refraction opt-out value must be empty, not `none`—see [Theming](/en/guide/theming#liquid-glass-glass-css); if your build chain strips empty custom properties, use the identity filter `saturate(1)` instead);
- disabled under the `high-contrast` theme (solid accessibility tier wins).

### Dynamic fluidity (pointer specular highlight, optional runtime)

The highlight needs a small runtime (`glass-fluid.js`): a single document `pointermove` listener resolves the surface via `composedPath` and writes the pointer position as element-local CSS variables; **the runtime injects the highlight stylesheet into the matching components' shadow roots** (`adoptedStyleSheets`), so **no glass rule or marker exists in component sources** (only `--oas-glass-*` variable consumption lines). Closing refraction while the app-bar overflow panel is open is done by a neutral in-component marker `data-panel-open` (so the panel is not clipped) and does not depend on the runtime — it works with `glass.css` alone or under reduced motion.

```html
<link rel="stylesheet" href="https://unpkg.com/@oas-ui/theme@2/glass.css" />
<script type="module">
  import 'https://unpkg.com/@oas-ui/theme@2/glass-fluid.js'
</script>
<html data-glass>
```

- Scope matches refraction (9 controls/nav/notification components: button / switch / slider thumb / app-bar / bottom-navigation / message / toast / snackbar / notification); content panels are excluded;
- Guards: zero listeners without `data-glass`; disabled under `prefers-reduced-motion: reduce`; not enabled for coarse pointers (touch); disabled under `high-contrast`; starts/stops automatically as the markers or media queries change;
- Tunable variables (CSS custom properties pierce shadow DOM): `--oas-glass-px` / `--oas-glass-py` (pointer-local coordinates written by the runtime — do not set manually), `--oas-glass-sheen` / `--oas-glass-sheen-size` / `--oas-glass-sheen-press` (sheen color, radius, pressed color; one step each for light and dark, defined in `glass.css`);
- The highlight layer sits above the surface background and below the label text (`isolation: isolate` + negative `z-index`), so the specular reflection never washes out the label;
- Scope and press: the hover sheen applies to all glass surfaces and buttons (including solid semantic/custom-color buttons — primary/success/warning/danger/has-color); **for solid buttons the pressed state uses an edge inset ring (`box-shadow inset`, no fill lightening)** — a white fill wash would push white-text contrast below the floor, while an edge ring leaves the label untouched (matching Apple's rim specular / Fluent's inset pressed cue); glass surfaces keep the stronger pressed sheen (a true enhancement);
- Contrast gate: glass surfaces are gated at ≥60 on hover (composited over the theme page background — light `#fff` / dark `#18181b`) and ≥45 pressed; **solid buttons at ≥45 on hover (transient large-text tier)** with their resting/pressed baseline (no fill lightening) ≥60. For translucent surfaces over extreme host backdrops, ensuring text contrast is the host's responsibility; the switch's in-track label changes background with its checked state and is not part of this gate.
- Boundary: with a still pointer, scrolling won't move the highlight until the pointer moves again; the slider thumb highlight is hidden in overlay states (dragging / focused / `show-tooltip` / custom thumb `data-custom-thumb`), and the press ring is only visible while pressed without moving.
