# 磨砂玻璃画廊

> 半透明 surface + backdrop 模糊 + 高光折光边的材质层（不依赖浏览器专属滤镜，Chromium / Firefox 同构；动态流动感由可选运行时 `glass-fluid.js` 提供——指针镜面高光 + 按压收紧，详见本页「动态流动感」）。

<script setup>
import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
import { useData } from 'vitepress'
const { isDark } = useData()
const theme = computed(() => (isDark.value ? 'dark' : 'light'))
const setTheme = (next) => {
  isDark.value = next === 'dark'
}
const open = ref(false)
const msg = ref(null)
const popMsg = () => {
  // 消息默认贴视口顶 16px——落在文档站白色导航区上，磨砂无从显现；
  // 把偏移量算到舞台渐变区上方，让 busy 背景垫在消息背后（磨砂感知依赖背景透出）
  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('玻璃材质消息：背景透出磨砂质感', { offset })
}
onMounted(async () => {
  document.documentElement.setAttribute('data-glass', '')
  const { message } = await import('@oas-ui/ui')
  msg.value = message
})
onBeforeUnmount(() => {
  // SPA 泄漏防护：画廊激活的 data-glass 不得带出本页（否则全站页面都变玻璃）
  document.documentElement.removeAttribute('data-glass')
})
</script>

<div class="gg-stage">
  <div class="gg-toolbar">
    <button class="sg-btn" :class="{ 'sg-active': theme === 'light' }" @click="setTheme('light')">light</button>
    <button class="sg-btn" :class="{ 'sg-active': theme === 'dark' }" @click="setTheme('dark')">dark</button>
    <oas-button type="primary" @click="open = true">打开对话框</oas-button>
    <oas-button @click="popMsg">弹出消息</oas-button>
  </div>
  <div class="gg-controls">
    <oas-button type="primary">主按钮</oas-button>
    <oas-button>次按钮</oas-button>
    <oas-switch checked></oas-switch>
    <oas-slider value="45" style="width: 200px"></oas-slider>
    <span class="gg-note">半透明 + 模糊 + 折光边跨浏览器一致；控件尺寸不随浏览器变化</span>
  </div>
  <div class="gg-cards">
    <div class="gg-card">
      <strong>卡片 surface</strong>
      <p>半透明底 + backdrop-filter 模糊 + 折光边。背景渐变更丰富时材质感更强。</p>
      <oas-tag type="primary">标签</oas-tag>
    </div>
    <div class="gg-card">
      <strong>文字可读性</strong>
      <p style="color: var(--oas-color-text-secondary)">次要文字走 token 安全档，busy 背景上仍保持可读。</p>
      <oas-input placeholder="输入框（实心控件）" style="width: 220px"></oas-input>
    </div>
  </div>
  <oas-modal v-if="open" visible no-mask draggable title="磨砂玻璃对话框" @oas-close="open = false">
    <p>面板背景透出背后的渐变与按钮——blur + 高光边共同构成玻璃材质。</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);
  /* modal 按视口居中——舞台须占满大部分视口高，弹窗才落在渐变之上（否则弹在深色页面上看不出玻璃感） */
  min-height: 72vh;
}
.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);
  -webkit-backdrop-filter: var(--oas-glass-blur, none);
  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>

## 用法

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

- `data-glass` 叠加在 `data-theme` / `data-skin` 之上，三者自由组合；
- 材质依赖背景反差——给页面一个有色彩层次的背景（渐变/图片）时质感最强，纯色同色相背景上减弱（文字仍可读）；
- 照片/视频等不可控 busy 背景不保证文字对比度；必要时提高 surface alpha 或在局部关闭玻璃层；
- 暗色档单独标定：`--oas-glass-ring` / `-sheen` / `-press` 比浅色低但不再压到不可感知（边缘高光、透光层次可见），模糊强度略增；亮/暗切换无需改用法；
- 局部降级：任意容器覆盖 `--oas-glass-blur: none; --oas-glass-ring: transparent`；
- `high-contrast` 主题下不启用（实心可访问性档优先）；
- 系统 `prefers-reduced-transparency: reduce` 下回退实心 surface（关闭模糊、折光边和动态高光，保留圆角/阴影）。

### 动态流动感（指针镜面高光，可选运行时）

镜面高光需要一小段运行时（`glass-fluid.js`）——单文档 `pointermove` 监听 + `composedPath` 命中 surface，把指针位置写成元素本地坐标变量；**高光样式表由运行时按注册表注入**对应组件的 shadow 根（`adoptedStyleSheets`），因此**组件源码里没有任何玻璃规则体或标记属性**（只有 `--oas-glass-*` 变量消费行）。

```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>
```

- 动态高光消费范围（9 个控件/导航/通知组件：button / switch / slider 把手 / app-bar / bottom-navigation / message / toast / snackbar / notification）；内容面板不接；
- 守卫：无 `data-glass` 零监听；`prefers-reduced-motion: reduce` 不启用；粗指针（触屏）不接；`high-contrast` 不启用；标记/媒体查询变化会自动起停；
- 可调变量（CSS 自定义属性可穿透 shadow 覆写）：`--oas-glass-px` / `--oas-glass-py`（运行时写入的指针本地坐标，勿手改）、`--oas-glass-sheen` / `--oas-glass-sheen-size` / `--oas-glass-sheen-press`（高光色、半径、按压色；明暗各一档，定义在 `glass.css`）；
- 高光层落在「surface 背景之上、文字之下」（`isolation: isolate` + 负 `z-index`），镜面反射不冲淡标签文字；
- 作用域与按压：hover 镜面高光对所有玻璃面与按钮生效（含**实心语义色/自定义色按钮** primary/success/warning/danger/has-color）；**实心按钮的按压改用边缘内描边（box-shadow inset，不提亮铺底）**——白高光铺底会把白字对比度压到门限下，边缘描边不冲淡文字（与 Apple 镜面描边 / Fluent 内凹的按压常态一致）；玻璃面的按压仍是更强的高光色（真增强）；
- 对比度门禁：玻璃面 hover 按主题页面底（light `#fff` / dark `#18181b`）合成取最坏 ≥60、按压 ≥45；**实心按钮 hover（瞬态大字档）≥45**，其静止/按压基线（底不被提亮）≥60；半透明表面若落在极端宿主底色上，文字对比度由宿主负责；switch 轨道内文案随选中态换底，其组合矩阵不在该门禁内。
- 边界：指针静止时滚动页面，高光坐标不随元素位移更新（移动指针即恢复）；slider 把手高光在 overlay 态（拖动中/聚焦/`show-tooltip`/自定义把手 `data-custom-thumb`）不显示，按压环也仅在「按下未移动」时可见。
