A music note button on the dashboard bar, left of the paintbrush, opens the
assPod in the hand whose laser pressed it. VLC plays its media library's music
and videos in the background; both sticks turn the click wheel, A and stick
clicks press the centre, B is Menu and the hamburger button puts it away. The
rest of the UI fades out while it is open.
- device/asspod: the pod's OS, screens, VLC engine and scene graph (bundled by
tools/build_asspod.py into device/vr/systemui.asspod.js), with the model
exported from tools/asspod-model
- native/podd: renders the 3D body with real shaders into a box of stereo
portal panels, and the playing video's picture under the screen
- device/shell_ext/asspod.py: VLC, the library scan, art, podd, and video
frames from ffmpeg paced to VLC's clock; videos keep their place across
restarts
- Reposition and Black/White finish on the main menu
- install.sh offers to install VLC when it is missing
A top-level @starting-style on the light layer's ::after (registered
custom properties under the @container style() gate) SEGVs Steam's
Chromium 126 renderer when the theme is injected while the pointer rests
on a hovered element. Launching LiquidAss from + > Launch Program does
exactly that: the laser is still on the row when the stylesheet lands,
so every launch crashed the whole gamepad UI (reproduced 3/3 with real
clicks; CSS-only reproduces too; gone without the rule).
The ramp in from dark is now a keyframe animation (lgs-ill-in) scoped to
the illumination hosts' in-rule, so it no longer reaches Steam's own
::after either.
Home's gamepad focus and blur handlers return when the module is torn
down, so turning the theme off no longer throws in 41-home.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stereo (glassd cap stereo, flag glassStereo, on)
- The window glass was one image both eyes saw on the glass plane, so the room behind it read as a picture
pasted on the glass. glassd now renders each eye's view (SteamVR's eye-to-head offsets, refreshed every
5 s): every region of a stereo surface's texture is a pair, left eye's copy then right eye's, and the
scene graph's panel spans the pair with stereoscopy Parallel.
- Measured on the device with a lab probe (lgs_sg test.stereoProbe): SteamVR splits a Parallel panel at
the middle of the panel's own uv rect, not at the texture's halves; hence the pair layout, and stereo only
where the scene graph shows a surface whole (main, SteamVR's panels). The bar, the frame menu and popups
show crops and stay mono. A slab clipped sideways is left out while it is.
- GPU about 2.9 -> 3.5 ms a frame.
Sidebar without the laser
- frame.menu is no longer laser-only: SteamVR shows it in gamepad mode (laser mouse off) and beside its own
pages; keyed on the gamepad flag its native glass went off and the CSS glass showed. Verified with the
flag forced to gamepad on Home and in VR Settings: the cover stays.
Home -> Library
- The shell stamps the leaving Home view with the moment it starts leaving (only on a way out of Home, kept
across Steam's /library redirect); Home fades on the route exit and the reporter lets its plates go on
that clock. Measured: all Home discs reported leaving in the first report, gone with the page.
- Plates whose element leaves the DOM are reported leaving from the mutation itself (no measuring), not
after a frame-driven tick the new route's render starves.
Glass tuner
- The popup fits Steam's 600 px bar popup cap (rows 76 px: label and value on one line over a 40 px slider);
open()/close() for tests.
Route changes
- Plates fade with their content: the reporter reads a running opacity fade on a plate or its ancestors
(Home -> Friends: the route's 150 ms exit) and reports exitAt/exitMs; glassd fades the plate's coverage
linearly on that clock, optics unchanged (cap exitAt); the daemon keeps a leaving plate for exactly
that fade. A plate whose element left the DOM (Home -> Library stays in one /library route: no fade,
Home unmounts when Library has rendered) goes in 90 ms instead of the 350 ms dematerialize ramp that
left Home's discs over the Library grid.
- The window cover arrives with the route: the shell sets the new route's glass mode in the history
change itself, the reporter reports the mode flip at once (tickNow) with the time it happened, and
glassd springs a kept surface's new cover from then (appearAt now also for main gaining its cover).
- A disc popped at the switch belongs to the page that leaves: it never flips the new page to the crops
layout and goes with the page's fade (lgs_sg.js routeOut).
Sidebar in VR Settings
- The frame menu is a laser-only surface keyed on Steam's gamepad-nav flag, which goes stale on
SteamVR's own pages (the laser works SteamVR's page): the native glass went on and off with every
input Steam saw, its CSS glass in between. On a SteamVR page (the frame's VR Settings action active)
the rule does not apply.
Friends
- The page starts at y 108 (C1a's pad + 40, as Settings' list): the sidebar platter no longer sits
under the search capsule.
Glass tuner (flag glassTune, on)
- A paintbrush circle after the system capsule (its own glass piece and native cover) opens a bar popup
through Steam's own bar popup button: colour (8 swatches) and tint strength, Glass intensity (the
dial), Refraction, Frost, Highlights, Reset to default.
- Daemon action glass.tune (kind ui, its own 40 ms rate, outside the global window): glassd.json at once,
~/.config/glass-shell/tune.json 400 ms after the last change (kept across reboots by request; the
defaults delete it; RT-6 allows exactly this file), the dial file mirrored for the SteamVR pages.
- glassd: tune {refract, frost, light, hue, hueK} applied in drawGlass, eased (40 ms) like the dial now
is; the glass body's neutral moves toward the hue (uHue), per-piece tints stay on top (cap tune).
- The CSS glass follows live: a style per Steam window sets --lgs-dial / --lgs-tune-hue; the material
tints mix the colour in.
- Home discs are never occluders: a plate under a pop was its shadow (glassd x.55, no light, in one
frame), so the hovered disc and the neighbours its name plate or card overlaps snapped dark on every
hover. The pop keeps its own contact shadow.
- The frame menu's tab pitch (and place) are registered lengths that transition: VR Settings' shorter
panel takes a smaller pitch, which changed the bar in one frame while glassd's shape followed a report
(~120 ms) later. Now the bar resizes on the spring the reporter morphs the glass on (shrink fade, grow
snappy); measured: Steam's crop moves over ~250 ms and glassd gets the morph from the first report.
Squeezed by the bar's animating width, Steam's label marquee started mid-open and stayed on (its 24 px of
scroll padding then never fitted). Labels keep their own width while the bar slides (flex-shrink 0, the
bar's edge clips them meanwhile) and Steam's 160 px cap goes. Verified: open 281 px with no clipped label,
collapsed 76 px as before. Seen in a headset-view grab.
The SteamVR theme is wrapped in html.lgs-on { }, so 'html.lgs-native-vr-settings ...' became
'html.lgs-on html.lgs-native-vr-settings ...' and never matched: the window fill (rgb(20 22 30 / .72)) stayed
on top of the native glass. Now '&.lgs-native-vr-*' (settings, frame-control row, More Options). Verified live:
the container's fill is transparent with the class set; the sidebar's .14 stays.
The illumination ::after (04-states.css) painted nothing at rest but was computed for every host (46-93 per route,
~25 custom properties and six gradients each) at every style recalc. The generated host entries (fontkit.py --hooks)
now carry a :where() gate (hover, active, focus, focus-within, pressed, focus-in, .lgs-lit): the layer exists only
while lit. rt/06-states.js holds .lgs-lit for 600 ms after hover, focus or press ends, so the 441 ms fade-out still
plays; a top-level @starting-style (02-motion.nowrap.css) ramps the light in from dark. Verified: ramps unchanged
(press in 0/.66/.98/1, out .86/.45/.03), no layer at rest; route changes ~48 -> ~55 fps.
- reporter: <html data-lgs-plates> on managed native windows; a plate glassd has not drawn for 1.5 s gets
data-lgs-plate-css (CSS plate glass back). The theme drops CSS plate glass on every other plate at once,
instead of after the ack: no CSS-then-native swap, no fill/shadow transition on each Home disc.
- settings: scrolled state from the scroll listener, the hero title measured in rAF (the 300 ms tick
forced the page's layout mid-transition)
- lgs-mat-glass-in/out, lgs-toast-in: scale and opacity only (glassd materializes the glass; the fill,
blur and shadow ramps re-blurred every animating element per frame on Steam's GPU)
- Home: the first grid arrives with the route's own fade (is-entering only for section/page changes)
- shell: the ornament is measured in the frame pass and only when its legends change
- library: section headers checked by style (checkVisibility), not a rect read
- lgs_sg.js reads its parents' crops every animation frame (frameWatch) and
relays out + pushes at once: Steam re-crops the frame menu's panel each frame
while it opens, and the base pieces read only every 0.5 s left the opening
bar cut off behind its cover. Standby surfaces count in the signature.
- Steam's page away (SteamVR Settings / bindings in its frame): lgs_sg.js
takes main's nodes off and back in the same scene-graph update as the page
(SteamVR's FrameStore, read every frame); the daemon keeps main in glassd's
and the scene graph's specs; the reporter keeps a frame-hidden surface's last
layout and glass marks (no CSS glass on the way back).
- SteamVR Settings gets native window glass: lgs_sg.js measures its panel and
glass rect (beat `vr`), glassd draws a "vrsettings" surface (armed while the
page is away), lgs_sg.js puts the cover node right behind #vrsettingspanel the
frame the page mounts and sets html.lgs-native-vr (30-settings.css drops the
CSS window fill).
- 01-font: the every-element :where(:not(code, ..., code *, ...)) rule is two
plain rules (body *, plus a monospace restore for code/pre/kbd/samp/tt):
the :not(X *) ancestor walks ran for every element on every recalc.
- Reporter: measures after the frame (rAF -> task), not in its rAF callback;
transitions/animations away from measured elements no longer trigger a full
recompute nor flush the style-fact cache; plates a route inserts are marked
glassd's at once without measuring (no forced layout of the half-built page),
a glass-mode change marks pending and measures after the paint.
- 41-home: the top row's pill layout effect has dependencies (it read layout in
every Home commit, ~450 ms over six route changes).
- 40-library: the sort pill's visibility via checkVisibility (no layout).
- 60-settings: the hero title width is measured when the title changes, not
every 300 ms.
Phase 1 (no handoff):
- lgs-native on every Steam window while native mode is live (daemon NATIVE_JS
`all`, lgs_core sets it at injection), pooled popups included.
- Covers and plates are marked glassd's as soon as they are reported (optimistic),
CSS glass back only if never acked within 1.5 s.
- Covers/plates that appear while animating are reported at their end state
(seekEnd over all running animations) with appear timing; glassd ramps them
from the page's own start time (appearAt).
- Pooled popups stay in the report while hidden and are measured as their content
mounts ("armed"): glassd draws the glass ahead, lgs_sg builds the nodes in the
same scene-graph update Steam inserts its panel in (panel watch), the hidden
cover elements are pre-marked, glassd clears a surface's texture on hide.
- Plates inserted by a route are reported synchronously, ahead of the scroll
deferral.
Phase 2 (menus):
- While a menu, alert or sheet is open in native mode, the page under it is cut
out of Steam's texture (data-lgs-hole mask, faded on a registered property),
held through the slab's out-ramp; the slab is transparent glass from the open.
- Modal layers are reported at once (no 607 ms settle wait).
- Frame menu: measured expanded width so Steam's width transition animates;
reporter sends cover.morph end shape + start time, glassd morphs the glass
on the same spring (70 ms native lead).
- Main window: glass-mode change reported from the mutation callback and the
cover marked data-lgs-cover="pending" (no opaque CSS window); glassd springs
covers in/out; daemon keeps lgs-native 1.5 s between modes.
- Launcher: the name plate dissolves in place, then the label fades back.
- IVROverlayView reads system overlays (systemui, HeadsetView) but not
Steam's dmabuf-backed panels
- SteamVR systemui scene graph accepts injected nodes (DOM elements with
buildNode; submit via module 5723's scheduler): reparent-to-panel +
panel-anchor + transform + panel shows live crops of Steam's window at
any 3D offset, and our own dmabuf overlays (needs SetOverlayMouseScale)
- hvgrab: headset-view capture for verification
- device/lgs_vr.py + lgs_vr_core.js: theme every SteamVR page (systemui
frame controls / Now Playing / settings, controller bindings, overlays)
from theme/vr/*.css; a transient user unit (lgs-vr) keeps new pages themed
while the theme is on and exits when the Steam side goes off
- lgs on/off/status/dial drive both Steam and SteamVR
- lgs_index reads SteamVR's webpack bundle too, so %{Token}s work there
- lab: vr:<page> surfaces for shot/outline/styles/audit/js/perf, own lock
SteamVR's VRWebHelper/DebuggerEnabled (port 8090) was enabled with the
user's approval; backup at steamvr.vrsettings.pre-glass-shell.