Files

13 KiB
Raw Permalink Blame History

Glass Shell design spec

Goal: re-skin every surface and sub-menu of the Steam Frame's VR interface (Steam gamepadui as SteamVR overlays) into Apple's glass design language, with zero functional change.

  • visionOS window glass for the window.
  • Liquid Glass for the floating controls.

Source material is the project's design bible (../bible/, published at https://claude.ai/artifact/T4AqVM5FQrQzn3kW4UTYe9) and its rulebook ../.claude/skills/run-liquid-glass-frame/references/design-language.md. This spec translates them to CSS inside Steam's CEF.

1. What the platform allows

  • Overlays have per-pixel alpha. Every surface is a quad composited over the room (passthrough, mono or colour) or over the running game.
    • Transparent pixels show the world.
    • Translucent pixels tint it.
  • backdrop-filter only sees the page's own content, never the room. So:
    • Window glass over the room = translucent smoky tint + specular rim + sheen. It cannot blur the room, so the tint must carry legibility on its own (--lgs-window-alpha ≈ 0.72 by default; the user dial moves it from 0.60 to 0.84).
    • Liquid Glass over in-page content = backdrop-filter blur + saturation (+ lensing later). Examples: header capsules over scrolling library art, side menus and sheets over the window, context menus over a page.
  • Each popup is its own quad.
    • The bar segments, bar popups, frame menus, tooltip, volume HUD, footer, toasts and keyboard float alone over the room. They use panel glass: tint + rim + sheen, no backdrop blur (there is nothing in-page behind them).
    • Their surroundings are transparent, so rounded corners are real.
  • The Adreno GPU is shared with the compositor.
    • Steam's CEF runs GPU compositing and GPU raster (ANGLE on zink/Turnip). It repaints on change, so static decoration is nearly free.
    • Stock Steam already has about 100 elements with backdrop-filter on library home and scrolls at about 85 fps, so blur is affordable. It is still not free: blurred backdrops re-render while what's behind them moves.
    • Budget, measured: python glass.py perf main --route R scrolls the page with the theme off, then on. Themed must keep the same fps (within 5%) and add no long frames (>34 ms).
    • No animated filters, no infinite animations.

2. Materials (all defined in theme/00-tokens.nowrap.css)

Material Use for Recipe (copy these declarations)
Window glass The dashboard window's root background, i.e. whatever paints the opaque page background today background: var(--lgs-window-sheen), var(--lgs-window-bg); box-shadow: var(--lgs-window-rim); border-radius: var(--lgs-r-window);
Panel glass Standalone overlays: bar segments, bar popups, frame menus, toasts, tooltip, HUDs, footer, keyboard background: var(--lgs-panel-sheen), var(--lgs-panel-bg); box-shadow: var(--lgs-panel-rim); border-radius: var(--lgs-r-panel) (or --lgs-r-capsule / --lgs-r-menu by shape)
Liquid Glass (regular) Floating controls over in-page content: header and search capsule, segmented tab bars, floating action capsules background: var(--lgs-glass-bg); backdrop-filter: var(--lgs-glass-blur); box-shadow: var(--lgs-glass-rim), var(--lgs-glass-shadow); border-radius: var(--lgs-r-capsule);
Thick glass Sheets, modals, side menus, context and dropdown menus inside the main window background: var(--lgs-panel-sheen), var(--lgs-thick-bg); backdrop-filter: var(--lgs-thick-blur); box-shadow: var(--lgs-glass-rim), var(--lgs-glass-shadow); border-radius: var(--lgs-r-sheet) (menus --lgs-r-menu). Modal scrim: --lgs-scrim
Fills (content on glass) Grouped settings sections, list rows at rest, input wells, chips, non-selected segments --lgs-fill-1/2/3, --lgs-fill-sunken for wells; separators --lgs-separator. Never glass on glass: anything inside a glass container uses fills, not another blur

Rim and sheen are the Liquid Glass signature: a crisp bright top edge, faint side edges and a soft top-down sheen. Light comes from above, the same direction everywhere. Don't add outer drop shadows on standalone overlays, which have no room for them; inside the main window, floating glass may use --lgs-glass-shadow.

Content is not glass. Game art, capsules, screenshots, hero images, avatars and store pages stay opaque content.

  • Round their corners to the concentric radius (--lgs-r-card for capsules, --lgs-r-small for small art).
  • Never tint or blur them.

3. Interaction states (must stay unmistakable)

State Treatment
Rest Transparent, or --lgs-fill-1 for rows and grouped items; labels --lgs-text-1, secondary --lgs-text-2
Hover (laser) --lgs-hover-fill, transition var(--lgs-t-fast) var(--lgs-ease)
Focus (.gpfocus, controller and laser focus) background: var(--lgs-focus-fill); box-shadow: var(--lgs-focus-ring);. It "illuminates from within". Must be visible on every focusable control in every surface. For content like game capsules, use --lgs-focus-ring-outer around the art and keep Steam's scale-up if it has one
Selected (active tab, current nav item, chosen segment) White fill --lgs-selected-fill + dark label --lgs-text-on-selected. Reserved: nothing else is white-filled
Pressed Brighter fill; optional scale: var(--lgs-pressed-scale), using the independent property, never transform
Disabled opacity: var(--lgs-disabled-opacity) on the control, or --lgs-text-disabled for labels
Primary action Only the one primary action per screen is tinted: Play is --lgs-tint-play, a dialog's confirm is --lgs-tint-primary, destructive is --lgs-tint-danger. Tint goes into the whole capsule fill with a white label, never into thin text

4. Components

  • Buttons (DialogButton family, bar small buttons): capsule (--lgs-r-capsule) for text buttons, circle for icon buttons.
    • Rest: --lgs-fill-2 with an inner top rim inset 0 1px 0 rgba(255,255,255,.18).
    • Focus: per §3.
    • Keep sizes; only round and recolour them.
  • Toggles:
    • Track: capsule, off --lgs-fill-3, on --lgs-toggle-on (whole fill).
    • Knob: white with a soft shadow 0 1px 3px rgba(0,0,0,.3).
    • Focused toggle row gets the focus treatment.
  • Sliders:
    • Track: --lgs-fill-3 capsule.
    • Fill: rgba(255,255,255,.92).
    • Knob: white, with a subtle ring.
    • Notches dim.
  • Dropdown buttons look like capsule buttons. Opened dropdown menus are thick-glass menus.
  • Text inputs / search: a sunken capsule well (--lgs-fill-sunken with inset 0 1px 2px rgba(0,0,0,.25)), white text, --lgs-text-3 placeholder. Focus: ring.
  • Field rows (settings and dialogs): transparent rows inside a grouped section card (--lgs-fill-1, --lgs-r-card).
    • Separators --lgs-separator, inset from the left.
    • The focused row gets focus fill + ring with --lgs-r-row.
  • Tabs / segmented controls: a Liquid Glass capsule holding the segments; the selected segment is the white pill.
    • Steam's home tabs (WHAT'S NEW / FRIENDS / RECOMMENDED) and library tabs map to this.
    • Keep their text case and size.
  • Menus (context menus, dropdowns, bar popups, frame menus): thick or panel glass, --lgs-r-menu, 6–8 px inner padding.
    • Rows --lgs-r-row.
    • Headers in --lgs-text-2, small caps-free title case.
    • Separators as hairlines.
    • Materialize with animation: lgs-materialize var(--lgs-t) var(--lgs-spring) both;, only on the menu container and only if Steam doesn't already animate it with opacity/transform.
  • Modals / sheets: thick glass, --lgs-r-sheet, scrim --lgs-scrim.
  • Toasts and notifications: panel glass rounded rect, --lgs-r-panel; icon left, title --lgs-text-1, body --lgs-text-2.
  • Scrollbars: thin capsule thumbs, rgba(255,255,255,.28), if Steam shows any.
  • The dashboard bar (ornament):
    • Each BarSurface segment becomes a panel-glass capsule or rounded slab.
    • Icons are white.
    • The active tab gets a white-ish selected fill or an underglow dot.
    • The + and small buttons are circular glass hit areas with the focus treatment.
    • The clock uses --lgs-text-1.
    • The avatar has a rounded square.
  • The keyboard: a panel-glass slab.
    • Keys are fills (--lgs-fill-2, --lgs-r-small) with white labels; special keys --lgs-fill-3.
    • The focused or touched key is bright white fill with a dark label (visionOS keyboard style).
    • Enter is tinted --lgs-tint-primary.

5. Type and colour

  • Keep Steam's font (Motiva Sans) and its sizes. Don't change font sizes or weights in a way that can reflow and clip text; a +100 weight on small labels is fine where space allows.
  • Labels are white at vibrancy levels: --lgs-text-1/2/3.
    • Map Steam's greys: near-white → --lgs-text-1, mid grey → --lgs-text-2, dim grey → --lgs-text-3.
    • Add text-shadow: var(--lgs-text-shadow) on text that sits directly on window or panel glass.
  • Colour only in whole fills: the green Play capsule, the green toggle-on track, blue primary confirm, red destructive, badges.
  • Keep Steam's semantic colours where they carry meaning (online green, in-game blue), but as fills or dots.

6. Shape

Concentric: inner radius = outer radius − padding.

Element Radius
Window 32
Sheets 28
Panels 24
Menus 20
Cards and capsule art 16
Rows 12
Small 8
Controls capsule

7. Motion

  • Only on state changes, never at rest.
  • Hover and focus fills transition over 140 ms.
  • Popups and menus materialize in 220 ms with a spring.
  • No shimmer, no pulsing, no oscillation.
  • prefers-reduced-motion sets the durations to 1 ms.

8. Functionality guardrails (from docs/LAB.md, repeated because they matter most)

Never:

  • hide, collapse, zero-opacity or disable pointer events on Steam elements;
  • change layout geometry (position, size, overflow, flex/grid) of containers;
  • override transforms Steam uses for positioning, carousels or virtualization;
  • cover controls with pseudo-elements that take pointer events.

Run python glass.py audit SURF --route R on every screen you style. It must report 0 GONE / HIDDEN / SHRUNK / UNCLICKABLE and 0 CONTRAST.

Every focusable control must show the focus state in screenshots taken with that control focused (.gpfocus).

9. SteamVR's own pages

SteamVR draws a few pieces of the Frame UI itself; see LAB.md, "SteamVR's own pages". They follow the same language:

Piece Treatment
Window grab bar under the dashboard window A Liquid Glass pill: a translucent white fill with a rim, brighter on hover or focus
Resize corners Thin white strokes at --lgs-text-2
Frame controls (keyboard, pop-out, recenter, more) Panel-glass capsule segments, white icons, circular hit areas, obvious focus and hover. They sit as their own quads over the room
Controller status A panel-glass card. Battery levels as whole-fill colour (green, orange, red)
Now Playing Window glass like the dashboard. The game art is content. Resume Game is the one tinted primary (--lgs-tint-play); the other actions are fill capsules; Exit Game gets a destructive tint only on focus
Controller bindings / SteamVR settings pages Window glass with the same primitives look as Steam's settings: fills, capsules, white selected, focus ring
Message overlays and toasts Panel glass, like Steam's toasts

systemui is an atlas page: everything outside the panels is transparent, and each panel becomes its own quad. Don't paint the page or vsg-app backgrounds.

10. File ownership

File Owner
theme/00-tokens.nowrap.css, theme/defs.svg Foundation (changes go through it)
theme/10-primitives.css Foundation: generic Steam components used everywhere (DialogButton, gamepaddialog fields, toggles, sliders, dropdowns, text inputs, context menus, modals, generic focus)
theme/20-shell.css Window chrome: root window glass, header and search, side menus, page transitions, in-window footer
theme/30-bar.css Dashboard bar, bar popups, frame menus
theme/35-hud.css Tooltip, volume HUD, floating footer, notification toasts, keyboard
theme/40-library.css Library home, library tabs, collections, capsules, search results
theme/50-appdetails.css Game pages, play bar, game menus, properties, running-app overview
theme/60-settings.css Settings pages (layout-level styling; controls come from primitives)
theme/70-social.css Friends and chat, profile, notifications, media, downloads, store chrome, power menu, other routes
theme/vr/*.css SteamVR's own pages: systemui (frame controls, grab bar, Now Playing, settings panels), controller bindings, overlays

Area files may refine primitives inside their own containers. Generic primitive changes belong in 10-primitives.css.