Files
MichaelScottsman cac13f5b1d Library: compatibility badge without backdrop blur (30 blurred layers per library frame)
A denser tint (.46 + blur -> .6) on the small chip over the art. Route changes ~51 -> ~56 fps.
2026-10-09 10:44:40 -04:00

1400 lines
53 KiB
CSS

/* Glass Shell, area "library" (package C2c): the library catalogue.
Routes: /library/tab/* and /library/collection/* (glass mode `window`),
the laser Sort & Filter pill (the toolbar row's trailing group), the
library's ornament members (and the five fixed slots behind
libFixedSlots), the grid's secondary chrome, the letter scrubber and
Library Filters' content.
Concept: docs/phase2/concepts/home-apps.md (HA) §7-§9; PLAN §1.3, §1.4,
§1.10, §1.12; WN §3.4.6. Home (/library/home) is C2a's (41-home.css).
Wrapped as html.lgs-on { ... } by the injector. Steam's geometry is kept
(poster size, columns, virtualized rows, the tab row's horizontal
handlers, the VR sub-filter's place); we move the header rows below the
toolbar row (C1a's --lgs-hdr-pad), restyle, and put Steam's laser Sort &
Filter pill in the toolbar row's trailing side (D-C2c-12).
Hooks this file reads:
#Footer[data-lgs-orn-fixed="library"] C1a T2: fixed slots registered
(flag libFixedSlots, off)
[data-lgs-lib-sort] on the Y label C2c T2: the current sort name
%{Collection}[data-lgs-empty] C2c T2: an empty collection
%{Collection}[data-lgs-dwell] C2c T2: a dwell host (P3)
%{GridWithControls}[data-lgs-scrub] C2c T3: the scrubber is mounted
.lgs-lib-scrub (+ -track, -list, -l, C2c T3: the scrubber's nodes
-dot, -cur) (flag libScrubber)
%{FilterBucket}.lgs-lib-chips C2c T3: a Filters section drawn
as toggle capsules (flag
libFilterChips) */
& {
/* the library header: the tab row and the sub-filter, 80 px boxes each */
--lgs-lib-row: 80px;
--lgs-lib-hdr-top: var(--lgs-hdr-pad, 68px);
/* Steam's gamepad focus grows a poster by its own transform (x1.053:
7 px at each end of a 258 px poster, 4 px on a 186 px collection
tile), and Steam's own focus scroll lands up to 9 px past the scroll
padding on collection pages (R2F-P23); the scroll guard keeps the grown
box inside y 124-612 with that margin */
--lgs-lib-lift-room: 16px;
}
/* ==================================================================== */
/* 1. Header rows below the toolbar row; the grid's scroll guard */
/* ==================================================================== */
/* Steam pins its header (tab row + sub-filter) at the top of the tabbed
page (y 40). The toolbar row's fallback (C1a, lgs-hdr-40) puts the
search capsule at y 22-86, so the header moves down by --lgs-hdr-pad
(68; 0 with lgs-hdr-108) and the grid starts below it (y 270). */
%{GamepadLibrary} %{TabHeaderRowWrapper} {
top: var(--lgs-lib-hdr-top);
}
/* Steam's pinned band (shown once the grid scrolls under the header)
becomes the scroll edge, reaching up under the toolbar row */
%{GamepadLibrary} %{TabHeaderRowWrapper}::before {
top: calc(-40px - var(--lgs-lib-hdr-top));
background: var(--lgs-scroll-band-top);
backdrop-filter: blur(12px);
-webkit-mask-image: linear-gradient(180deg, #000 72%, transparent 100%);
mask-image: linear-gradient(180deg, #000 72%, transparent 100%);
box-shadow: none;
}
/* the grid starts under the two 80 px rows (Steam: 118 for 58 + 46) */
%{GamepadLibrary} %{TabContentsScroll} {
padding-top: calc(var(--lgs-lib-hdr-top) + 154px) !important;
/* room after the last row, so it can scroll above the ornament guard
like every other row (Steam ends the grid 40 px above the window
bottom; the Collections tab could not scroll at all): the scroller
ends at y 720, so 720 - 612 + the focus growth (VP P-23, PLAN R2-11) */
padding-bottom: calc(720px - var(--lgs-guard-bottom, 612px) + var(--lgs-lib-lift-room)) !important;
/* focused posters stay below the header rows (window y 270) and above
the ornament guard (612) with Steam's focus growth counted: VP P-23,
C1a's guard variables */
scroll-padding-top: calc(var(--lgs-lib-hdr-top) + 162px + var(--lgs-lib-lift-room));
scroll-padding-bottom: calc(720px - var(--lgs-guard-bottom, 612px) + var(--lgs-lib-lift-room));
}
/* Tabs without the sub-filter row (Collections, Non-Steam, Soundtracks,
collection pages) keep the same grid top: rows land where they do on All
Games, so the ornament never covers part of a row's 80 px hit band
without covering a quarter of it (P-08's obscured rule). Review R2 m7
asked for the grid under the tab row; measured in the R2 fix pass, the
second row then sat with 19 % of its hit band under the ornament and
failed P-08 on the VR collection (gates 19:33), so the band stays. */
/* ==================================================================== */
/* 2. Tabs: Steam's tab row as a segmented control (CTL §8.3, E-SEG) */
/* ==================================================================== */
/* A 64 px recessed track; segments contiguous, 60 visible and >= 140 wide
inside 80 px boxes (10 px transparent borders, so the hit is 80); 22 px
Semibold in Steam's own case; selected = white .94 with a dark label. */
%{GamepadLibrary} %{TabRow} {
height: var(--lgs-lib-row);
min-height: var(--lgs-lib-row);
align-items: center;
}
%{GamepadLibrary} %{FixCenterAlignScroll} {
height: var(--lgs-lib-row);
align-items: center;
/* contiguous segments (Steam leaves 2 px between tabs) */
gap: 0;
anchor-name: --lgs-lib-tabscroll;
}
%{GamepadLibrary} %{FixCenterAlignScroll} > :nth-child(1 of %{GamepadTabbedPage>Tab}) {
anchor-name: --lgs-lib-tab-first;
}
%{GamepadLibrary} %{FixCenterAlignScroll} > :nth-last-child(1 of %{GamepadTabbedPage>Tab}) {
anchor-name: --lgs-lib-tab-last;
}
%{GamepadLibrary} %{FixCenterAlignScroll} > :nth-child(1 of %{GamepadTabbedPage>Tab}):nth-last-child(1 of %{GamepadTabbedPage>Tab}) {
anchor-name: --lgs-lib-tab-first, --lgs-lib-tab-last;
}
/* the track: our ::before on Steam's tab row (Steam draws none there),
held inside the tab scroller's box when the row overflows. (Not the
wrapper's ::after: Phase 1's legacy layer rule "tabs" pops that pseudo
forward in native mode, and this track is recessed, PLAN §1.7.) */
%{GamepadLibrary} %{TabHeaderRowWrapper}::after {
content: none;
}
%{GamepadLibrary} %{TabRow} {
position: relative;
isolation: isolate;
}
%{GamepadLibrary} %{TabRow}::before {
content: "";
position: absolute;
z-index: -1;
top: calc(anchor(--lgs-lib-tab-first top, 0px) + 8px);
height: var(--lgs-seg);
left: max(calc(anchor(--lgs-lib-tab-first left, 100%) - 2px), anchor(--lgs-lib-tabscroll left, 0px));
right: max(calc(anchor(--lgs-lib-tab-last right, 0px) - 2px), anchor(--lgs-lib-tabscroll right, 0px));
border-radius: var(--lgs-r-capsule);
background: var(--lgs-fill-thick);
box-shadow: var(--lgs-recessed-shadow);
pointer-events: none;
}
%{GamepadLibrary} %{GamepadTabbedPage>Tab} {
position: relative;
isolation: isolate;
box-sizing: border-box;
height: var(--lgs-lib-row);
max-height: none;
min-width: var(--lgs-seg-min-w);
width: max-content !important;
flex: 0 0 auto !important;
gap: 6px;
margin: 0;
padding: 0 16px;
border: 10px solid transparent;
border-inline-width: 0;
background-clip: padding-box;
justify-content: center;
border-radius: var(--lgs-r-capsule);
background-color: transparent;
color: var(--lgs-text-1);
box-shadow: none !important;
--lgs-ill: raised;
--lgs-ill-inset: 10px 0;
--lgs-ill-z: -1;
transition: background-color var(--lgs-motion-snappy), color var(--lgs-motion-fade);
}
%{GamepadLibrary} %{GamepadTabbedPage>Tab} %{GamepadTabbedPage>TabTitle} {
font: 600 22px/28px var(--lgs-font);
text-transform: none;
letter-spacing: 0;
}
%{GamepadLibrary} %{GamepadTabbedPage>Tab}:hover {
background-color: transparent;
color: var(--lgs-text-1);
}
%{GamepadLibrary} %{GamepadTabbedPage>Tab} %{TabCount} {
font: 500 22px/28px var(--lgs-font);
letter-spacing: 0;
text-transform: none;
margin: 0;
color: var(--lgs-text-2);
}
%{GamepadLibrary} %{GamepadTabbedPage>Tab}%{GamepadTabbedPage>Selected},
%{GamepadLibrary} %{GamepadTabbedPage>Tab}%{GamepadTabbedPage>Selected}:hover {
background-color: var(--lgs-selected-fill) !important;
color: var(--lgs-text-on-selected) !important;
box-shadow: none !important;
--lgs-ill: white;
}
%{GamepadLibrary} %{GamepadTabbedPage>Tab}%{GamepadTabbedPage>Selected} %{TabCount} {
color: var(--lgs-text-on-selected-2) !important;
opacity: 1;
}
/* Gamepad focus on a segment (tabs, sub-filter; D-C2c-16). A white .94
selection leaves no room for an additive focus light, so focus is the
tvOS look: the focused segment is the brightest thing in the control
(opaque white, dark label) with a strong outer glow (P-16), and while
focus is inside a control its selected-but-unfocused segment recedes to
the navigation-selected look (white .18 + arc), so focus and selection
are never the same look (P-15). Steam selects a tab when it takes focus,
so a focused tab is also the selected one. */
&:not(.lgs-input-laser) %{GamepadLibrary} %{GamepadTabbedPage>Tab}.gpfocus {
background-color: rgb(255 255 255) !important;
background-clip: padding-box !important;
color: var(--lgs-text-on-selected) !important;
box-shadow: none !important;
--lgs-ill: white;
--lgs-white-glow-spread: 14px;
--lgs-white-glow-a: .70;
}
&:not(.lgs-input-laser) %{GamepadLibrary} %{GamepadTabbedPage>Tab}.gpfocus %{TabCount} {
color: var(--lgs-text-on-selected-2) !important;
opacity: 1;
}
/* Steam's previous / next tab arrows: 60 px plain circles in 80 px boxes */
%{GamepadLibrary} %{TabHeaderRowWrapper} %{Arrows} {
position: relative;
isolation: isolate;
box-sizing: border-box;
width: 80px;
height: 80px;
min-width: 80px;
padding: 16px;
border: 10px solid transparent;
border-radius: 50%;
background: var(--lgs-raised-bg);
background-clip: padding-box;
box-shadow: none;
color: var(--lgs-text-1);
--lgs-ill: raised;
--lgs-ill-inset: 10px;
--lgs-ill-z: -1;
transition: background var(--lgs-motion-fade);
}
%{GamepadLibrary} %{TabHeaderRowWrapper} %{Arrows} svg {
width: 28px;
height: 28px;
}
&:not(.lgs-input-pad) %{GamepadLibrary} %{TabHeaderRowWrapper} %{Arrows}:is(:hover, :active),
&:not(.lgs-input-laser) %{GamepadLibrary} %{TabHeaderRowWrapper} %{Arrows}.gpfocus {
background: var(--lgs-raised-bg) !important;
background-clip: padding-box !important;
box-shadow: none !important;
color: var(--lgs-text-1) !important;
}
/* ==================================================================== */
/* 3. The VR sub-filter (All · VR · Non-VR), in Steam's place */
/* ==================================================================== */
/* The same segmented control, 3 x 140. The radio group is wider than its
segments (Steam centres them), so the track is the group's ::after
anchored to the first and last segment. (C4a's %{Group} primitive skips
centred pills; this one is the library's.) */
%{GamepadLibrary} %{VRSubTabFilterContainer} {
height: var(--lgs-lib-row);
min-height: var(--lgs-lib-row);
transform-style: preserve-3d;
}
%{GamepadLibrary} %{VRSubTabFilterContainer} > %{VRLibraryFilterGroup} {
transform-style: preserve-3d;
}
%{VRLibraryFilterGroup} > :nth-child(1 of %{Group>Button}) {
anchor-name: --lgs-vrsub-first;
}
%{VRLibraryFilterGroup} > :nth-last-child(1 of %{Group>Button}) {
anchor-name: --lgs-vrsub-last;
}
%{VRLibraryFilterGroup} > :nth-child(1 of %{Group>Button}):nth-last-child(1 of %{Group>Button}) {
anchor-name: --lgs-vrsub-first, --lgs-vrsub-last;
}
%{VRLibraryFilterGroup}%{CenteredPill} {
background: none;
box-shadow: none;
border: 0;
gap: 0;
}
%{VRLibraryFilterGroup}%{CenteredPill}::after {
content: "";
position: absolute;
top: calc(anchor(--lgs-vrsub-first top, 0px) + 8px);
height: var(--lgs-seg);
left: calc(anchor(--lgs-vrsub-first left, 100%) - 2px);
right: calc(anchor(--lgs-vrsub-last right, 0px) - 2px);
z-index: -1;
border-radius: var(--lgs-r-capsule);
background: var(--lgs-fill-thick);
box-shadow: var(--lgs-recessed-shadow);
pointer-events: none;
}
%{VRLibraryFilterGroup}%{CenteredPill} > %{Group>Button} {
position: relative;
isolation: isolate;
box-sizing: border-box;
height: var(--lgs-lib-row);
min-height: var(--lgs-lib-row);
min-width: var(--lgs-seg-min-w);
margin: 0;
padding: 0 20px;
border: 10px solid transparent;
border-inline-width: 0;
background-clip: padding-box;
border-radius: var(--lgs-r-capsule);
background-color: transparent;
font: 600 22px/28px var(--lgs-font);
text-transform: none;
letter-spacing: 0;
text-shadow: none;
color: var(--lgs-text-1);
--lgs-ill: raised;
--lgs-ill-inset: 10px 0;
--lgs-ill-z: -1;
transition: background-color var(--lgs-motion-snappy), color var(--lgs-motion-fade);
/* Steam's 1 px dividers */
&::before { background: transparent !important; }
}
/* the focused segment's glow paints over its neighbours */
&:not(.lgs-input-laser) %{VRLibraryFilterGroup}%{CenteredPill} > %{Group>Button}.gpfocus,
&:not(.lgs-input-laser) %{GamepadLibrary} %{GamepadTabbedPage>Tab}.gpfocus {
z-index: 2;
}
/* Steam's own hover and focus fills go back to rest (the light hook and
the focus rule below carry those states) */
%{VRLibraryFilterGroup}%{CenteredPill} > %{Group>Button}:not(%{Group>Active}, [aria-checked="true"]):hover,
&.lgs-input-laser %{VRLibraryFilterGroup}%{CenteredPill} > %{Group>Button}:not(%{Group>Active}, [aria-checked="true"]).gpfocus {
background-color: transparent !important;
color: var(--lgs-text-1);
}
%{VRLibraryFilterGroup}%{CenteredPill} > %{Group>Button}:is(%{Group>Active}, [aria-checked="true"]),
%{VRLibraryFilterGroup}%{CenteredPill} > %{Group>Button}:is(%{Group>Active}, [aria-checked="true"]):hover {
background-color: var(--lgs-selected-fill) !important;
background-clip: padding-box !important;
color: var(--lgs-text-on-selected) !important;
box-shadow: none !important;
--lgs-ill: white;
}
/* focus inside the sub-filter on another segment: the selection recedes
to the navigation-selected look (D-C2c-16; P-15) */
&:not(.lgs-input-laser) %{VRLibraryFilterGroup}%{CenteredPill}:has(> .gpfocus) > %{Group>Button}:is(%{Group>Active}, [aria-checked="true"]):not(.gpfocus) {
background-color: var(--lgs-fill-nav) !important;
color: var(--lgs-text-1) !important;
box-shadow: var(--lgs-sel-arc) !important;
--lgs-ill: raised;
}
/* gamepad focus on a sub-filter segment: as on the tabs above (D-C2c-16) */
&:not(.lgs-input-laser) %{VRLibraryFilterGroup}%{CenteredPill} > %{Group>Button}.gpfocus {
background-color: rgb(255 255 255) !important;
background-clip: padding-box !important;
color: var(--lgs-text-on-selected) !important;
box-shadow: none !important;
--lgs-ill: white;
--lgs-white-glow-spread: 14px;
--lgs-white-glow-a: .70;
}
/* ==================================================================== */
/* 4. Posters (Steam's geometry: 172 x 258, 6 per row) */
/* ==================================================================== */
/* Content: opaque art, radius 20, no ring. Steam's focus outline (grow,
fade, 20 pulses) and its shine sweep go, so nothing runs at rest
(PLAN §1.5, P-42, P-52). The attention target lifts: x1.05 on the
independent `scale` (Steam's transform untouched), the +15 mm shadow
and a soft glow; gamepad focus at once, laser after the 80 ms dwell. */
%{GamepadLibrary} %{LibraryItemBox} {
border-radius: var(--lgs-r-card);
outline: none !important;
animation: none !important;
/* gamepad focus already grows by Steam's own focus transform (about
x1.07, kept); the laser's dwell lifts by x1.05 here */
scale: 1;
/* Steam's 300 ms transform transition is replaced by MO tokens */
transition-property: filter, box-shadow, transform, scale !important;
transition-duration: 0s, var(--lgs-d-hover-in), var(--lgs-d-fade), var(--lgs-d-fade) !important;
transition-timing-function: linear, var(--lgs-ease-b0), var(--lgs-ease-b0), var(--lgs-ease-b0) !important;
}
%{GamepadLibrary} %{LibraryItemBox} > %{PortraitImage>Container},
%{GamepadLibrary} %{LibraryItemBox} > %{PortraitImage>Container} > img {
border-radius: var(--lgs-r-card);
}
/* a poster without art (Steam's title card) is content, so it is opaque:
a neutral gradient tile, not a tint over the window (in native mode the
window's CSS tint is gone and a translucent tile let the room through
its title: 1.4 : 1, gates 19:50 with another agent's native session) */
%{GamepadLibrary} %{LibraryItemBox} > %{PortraitImage>Container}%{GreyBackground} {
background: linear-gradient(160deg, rgb(64 68 82), rgb(34 36 46));
}
%{GamepadLibrary} %{LibraryItemBox}::after {
border-radius: inherit;
}
%{GamepadLibrary} %{LibraryItemBoxShine} {
animation: none !important;
opacity: 0 !important;
}
/* at rest and on a plain laser hover (before the dwell): no ring; also on
a stale .gpfocus that Steam leaves under the laser (both input modes are
named, so the rule reads as deliberate, VP P-01) */
%{GamepadLibrary} %{LibraryItemBox}:is(.Focusable:focus, :hover),
&.lgs-input-laser %{GamepadLibrary} %{LibraryItemBox}.gpfocus,
&:not(.lgs-input-laser) %{GamepadLibrary} %{LibraryItemBox}.gpfocus {
box-shadow: none !important;
}
/* lifted (gamepad: focus; laser: after the dwell; no input runtime: hover) */
&:not(.lgs-input-laser) %{GamepadLibrary} %{LibraryItemBox}.gpfocus.gpfocus,
&.lgs-input-laser %{GamepadLibrary} %{LibraryItemBox}:is(.lgs-dwell:hover, .lgs-attend-80),
&:not(.lgs-input-laser):not(.lgs-input-pad) %{GamepadLibrary} %{PortraitImage>HoversEnabled} %{LibraryItemBox}:hover {
box-shadow: var(--lgs-shadow-15mm), 0 0 30px 2px rgb(255 255 255 / .16) !important;
--lgs-ill: card;
}
&.lgs-input-laser %{GamepadLibrary} %{LibraryItemBox}:is(.lgs-dwell:hover, .lgs-attend-80):not(.gpfocus) {
scale: var(--lgs-card-lift);
}
/* Steam's compatibility badge (shown on hover and focus): a dark chip */
/* (a denser tint, no backdrop blur: on 30 capsules at once the blur was 30
backdrop layers in every library frame, ~2 fps of route changes) */
%{GamepadLibrary} %{LibraryItemBox} %{SteamDeckCompatInfo} {
background: rgb(0 0 0 / .6);
box-shadow: none;
border: 0;
border-radius: var(--lgs-r-capsule);
}
/* missing art: Steam's title card, title 22 Bold */
%{GamepadLibrary} %{LibraryItemBox} %{PortraitImage>Title} {
font: 700 22px/1.25 var(--lgs-font);
letter-spacing: 0;
text-transform: none;
color: var(--lgs-text-1);
text-shadow: var(--lgs-text-shadow);
}
/* uninstalled / download bar: capsule track, white fill */
%{GamepadLibrary} %{PortraitImage>UninstalledBar} {
background-color: var(--lgs-fill-thick);
border-radius: var(--lgs-r-capsule);
}
%{GamepadLibrary} %{PortraitImage>UninstalledBar} %{UninstalledProgressBar} {
background-color: var(--lgs-text-1);
border-radius: var(--lgs-r-capsule);
}
%{GamepadLibrary} %{FriendsBar} %{SummaryCount} {
color: var(--lgs-text-1);
text-shadow: var(--lgs-text-shadow);
}
/* ==================================================================== */
/* 5. Grid chrome: the filter notice, section headers, fast scroll */
/* ==================================================================== */
/* "N apps hidden due to filter": a 60 px quiet capsule (white .08, Callout
Medium white .70); Steam's two 1 px rules go (P-43). The button variant
(clears the filter) is a 60 px text button with an 80 px hit. */
%{GamepadLibrary} %{AppGridFilterHeader} {
box-sizing: border-box;
min-height: 80px;
border: 10px solid transparent;
background-clip: padding-box;
border-radius: var(--lgs-r-capsule);
background-color: rgb(255 255 255 / .08);
box-shadow: none !important;
}
%{GamepadLibrary} %{AppGridFilterHeader}::before,
%{GamepadLibrary} %{AppGridFilterHeader}::after {
display: none;
}
%{GamepadLibrary} %{AppGridFilterHeader} %{AppGridFilterText} {
font: 500 22px/1.3 var(--lgs-font);
letter-spacing: 0;
text-transform: none;
color: var(--lgs-text-2);
text-shadow: none;
}
%{GamepadLibrary} %{AppGridFilterHeader}%{AppGridFilterHeaderAsButton} {
background-color: var(--lgs-raised-bg, rgb(255 255 255 / .14));
isolation: isolate;
--lgs-ill: raised;
--lgs-ill-inset: 10px 0;
--lgs-ill-z: -1;
}
%{GamepadLibrary} %{AppGridFilterHeader}%{AppGridFilterHeaderAsButton} %{AppGridFilterText} {
font: 600 24px/1.3 var(--lgs-font);
color: var(--lgs-text-1);
}
/* sort sections ("Over 10 hours" ...): Title 3 in Steam's own case; the
rule goes; the sticky band becomes the scroll edge */
%{GamepadLibrary} %{AppGridSectionHeader} %{AppGridSectionLabel} {
font: var(--lgs-type-title3);
letter-spacing: 0;
text-transform: none;
color: var(--lgs-text-1);
text-shadow: none;
}
%{GamepadLibrary} %{AppGridSectionHeader} %{LibraryImageBackgroundGlow>Rule} {
display: none;
}
%{GamepadLibrary} %{AppGridSectionHeader}::after {
background: var(--lgs-scroll-band-top);
box-shadow: none;
}
/* Steam's fast-scroll letter (gamepad): a thick glass circle */
%{FastScrollOverlay}%{FastScrollOverlay>Visible} {
background: var(--lgs-mat-thick-bg);
backdrop-filter: var(--lgs-mat-thick-blur);
box-shadow: var(--lgs-shadow-10mm);
border: 0;
border-radius: var(--lgs-r-capsule);
color: var(--lgs-text-1);
text-shadow: none;
}
/* empty grid message */
%{GamepadLibrary} %{EmptyGridMessageContainer},
%{GamepadLibrary} %{EmptyGridMessageContainer} %{EmptyGridMessageLine} {
font: var(--lgs-type-callout);
letter-spacing: 0;
text-transform: none;
color: var(--lgs-text-2);
}
/* ==================================================================== */
/* 6. Collections tab and collection pages */
/* ==================================================================== */
/* Steam's tiles (3D display case kept), radius 30; label 20 Semibold in
Steam's case; count Medium .70; empty collections dimmed (T2 tag). The
tile lifts like a poster. */
%{GamepadLibrary} %{Collection} {
border-radius: var(--lgs-r-platter);
outline: none !important;
box-shadow: none !important;
scale: calc(1 + (var(--lgs-card-lift) - 1) * var(--lgs-lift));
transition: scale var(--lgs-motion-fade), box-shadow var(--lgs-motion-hover-in), opacity var(--lgs-motion-fade);
}
%{GamepadLibrary} %{Collection} :is(%{CollectionBG}, %{CollectionImage}) {
border-radius: inherit;
}
/* Steam's hover and focus fill (a light blue, rgb(98 120 163)) goes: the
tile keeps its rest face, and the dwell (or
gamepad focus) lifts it, as on posters (P-19, P-89). T2 makes every tile
a dwell host (data-lgs-dwell), since Steam's tile is a role=link div that
P3's default dwell selector does not match, and its count's tooltip
source covers the tile (D-C2c-15). */
%{GamepadLibrary} %{Collection}:is(:hover, :focus),
&.lgs-input-laser %{GamepadLibrary} %{Collection}.gpfocus,
&:not(.lgs-input-laser) %{GamepadLibrary} %{Collection}.gpfocus {
background-color: rgb(49 61 83) !important;
}
&:not(.lgs-input-laser) %{GamepadLibrary} %{Collection}.gpfocus.gpfocus,
&.lgs-input-laser %{GamepadLibrary} %{Collection}:is(.lgs-dwell:hover, .lgs-attend-80),
&:not(.lgs-input-laser):not(.lgs-input-pad) %{GamepadLibrary} %{Collection}:hover {
box-shadow: var(--lgs-shadow-15mm), 0 0 30px 2px rgb(255 255 255 / .16) !important;
}
/* empty: the tile's face dims, its label keeps full contrast (P-39) */
%{GamepadLibrary} %{Collection}[data-lgs-empty] > %{CollectionImage} {
opacity: .62;
}
/* the 3D display case's mini capsules: Steam's hairline border goes */
%{GamepadLibrary} %{Collection} %{Container>CapsuleImage} {
border: 0 !important;
}
%{GamepadLibrary} %{Collection} %{CollectionLabel} {
font: 600 20px/1.25 var(--lgs-font);
letter-spacing: 0;
text-transform: none;
color: var(--lgs-text-1);
text-shadow: var(--lgs-text-shadow);
}
%{GamepadLibrary} %{Collection} %{CollectionLabel} %{CollectionLabelCount} {
font: 500 20px/1.25 var(--lgs-font);
letter-spacing: 0;
color: var(--lgs-text-2);
}
/* Steam's count is a 20 px tall focusable tooltip source inside the tile;
it is spread over the whole tile (its text stays under the name), so
every point of the tile takes the laser and the tile's click (P-08,
P-80). The label fills the tile and stacks name and count at its foot. */
%{GamepadLibrary} %{Collection} > %{CollectionLabel} {
inset: 0 !important;
width: auto !important;
height: auto !important;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
/* Steam's name column: the tile less 21 px a side (same line breaks) */
padding: 0 21px 16px;
text-align: center;
}
%{GamepadLibrary} %{Collection} > %{CollectionLabel} > :not(.tool-tip-source) {
margin-bottom: 26px;
}
%{GamepadLibrary} %{Collection} > %{CollectionLabel} > .tool-tip-source {
position: absolute;
inset: 0;
box-sizing: border-box;
display: flex;
align-items: flex-end;
justify-content: center;
padding: 0 21px 16px;
border-radius: inherit;
}
%{GamepadLibrary} %{Collection} > %{CollectionLabel} > .tool-tip-source > * {
flex: 1 1 auto;
text-align: center;
}
/* collection page title and the Collections header (Steam's own case) */
%{GamepadLibrary} :is(%{CollectionHeader}, %{CollectionsHeader}) {
font: var(--lgs-type-title3);
letter-spacing: 0;
text-transform: none;
color: var(--lgs-text-1);
text-shadow: none;
}
%{GamepadLibrary} %{CollectionsHeaderHR} {
display: none;
}
/* ==================================================================== */
/* 7. Steam's laser Sort & Filter pill: toolbar trailing items */
/* ==================================================================== */
/* Steam renders the pill only in laser mode, inside the page, at the
bottom right (y 628-685). The window's glass clip (C1a, y 656) cuts
page content there, so the pill could not show or take clicks in the
ornament (D-C2c-12). It becomes the toolbar row's trailing group, as
visionOS places a window's sort and filter: one 60 px capsule (raised
fill) holding Steam's two buttons, 80 px hits, right-aligned 24 px from
the glass edge, in the toolbar row (y 24-84). Steam's current sort and
"Filter" stay Steam's text, in Steam's case (P-84). The node, its
handlers and its laser-only rendering are untouched (no D-pad change). */
%{GamepadLibrary} %{SortAndFilterContainer} {
position: fixed;
z-index: 3;
box-sizing: border-box;
/* fixed inside Steam's transformed tabbed page (it starts at y 40, or
at 108 under lgs-hdr-108), so the toolbar row's y is reached from it */
top: calc(var(--lgs-hdr-pad, 68px) - 94px);
bottom: auto;
left: auto;
right: 24px;
height: 80px;
/* >= 20 px clear of the search capsule's visible right edge (x 900 on
section roots): 1256 - 920 = 336 (PLAN R2-2); the sort name ellipsizes
first, the inset and the gap never shrink */
max-width: 336px;
margin: 0;
padding: 0;
gap: 0;
display: flex;
align-items: stretch;
border: 0;
border-radius: 0;
background: none;
backdrop-filter: none;
box-shadow: none;
pointer-events: none;
isolation: isolate;
--lgs-edge: none;
}
/* the group's capsule: 60 visible inside the 80 box */
%{GamepadLibrary} %{SortAndFilterContainer}::before {
content: "";
position: absolute;
inset: 10px 0;
z-index: -1;
border-radius: var(--lgs-r-capsule);
background: var(--lgs-raised-bg);
pointer-events: none;
}
%{SortAndFilterButton} {
position: relative;
isolation: isolate;
box-sizing: border-box;
height: 80px;
min-width: 0;
margin: 0;
padding: 0 18px;
gap: 8px;
border: 10px solid transparent;
border-inline-width: 0;
background-clip: padding-box !important;
/* square hit box (the capsule is the group's ::before and the light
layer's radius), so the corners take the laser too (P-08) */
border-radius: 0;
box-shadow: none !important;
justify-content: center;
align-items: center;
color: var(--lgs-text-1);
text-shadow: none;
pointer-events: auto;
--lgs-ill: raised;
--lgs-ill-inset: 10px 0;
--lgs-ill-r: var(--lgs-r-capsule);
--lgs-ill-z: -1;
}
/* Steam's own fills go (the group's capsule is the rest look), except
while the button is the source of its open menu: then C1c's white .94
with the dark label shows (PLAN §1.4; review R2 Q4), drawn as a 60 px
capsule inside the 80 px box (border-radius 30 / 40 leaves a 30 px
radius on the 60 px padding box) */
%{SortAndFilterButton}:not([data-lgs-menu-source]) {
background: none !important;
}
%{SortAndFilterButton}[data-lgs-menu-source] {
border-radius: 30px / 40px;
}
%{SortAndFilterButton},
%{SortAndFilterButton} * {
font-family: var(--lgs-font);
font-size: var(--lgs-fs-callout);
font-weight: 600;
letter-spacing: 0;
text-transform: none;
}
%{SortAndFilterButton} :is(div, span):not(%{CompatFooterIcons}) {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* the sort shrinks (its name ellipsizes); Filter keeps its width */
:nth-child(1 of %{SortAndFilterButton}) {
flex: 0 1 auto;
}
:nth-child(2 of %{SortAndFilterButton}) {
flex: none;
}
/* collection pages: the 640 search capsule ends at x 960, so the group
is at most 276 wide (x 980-1256); both buttons show Steam's label only
(the glyphs are decorations; the names stay), 14 px padding */
%{BasicUiRoot}[data-lgs-search="nested"] %{GamepadLibrary} %{SortAndFilterContainer} {
max-width: 276px;
}
%{BasicUiRoot}[data-lgs-search="nested"] %{SortAndFilterButton} {
padding: 0 14px;
}
%{BasicUiRoot}[data-lgs-search="nested"] %{SortAndFilterButton} svg {
display: none;
}
/* Steam's account-alert circle (#header_profile, C1a: a 60 px circle at
the 24 px inset) stays outermost; the group sits 20 px to its left */
&:has(#header_profile) %{GamepadLibrary} %{SortAndFilterContainer} {
right: 104px;
max-width: 256px;
}
&:has(#header_profile) %{BasicUiRoot}[data-lgs-search="nested"] %{GamepadLibrary} %{SortAndFilterContainer} {
max-width: 196px;
}
%{SortAndFilterButton} svg {
flex: none;
width: 22px;
height: 22px;
opacity: .70;
}
%{CompatFooterIcons} {
background-color: rgb(255 255 255 / .16);
box-shadow: none;
border-radius: var(--lgs-r-capsule);
}
/* ==================================================================== */
/* 8. The ornament on library routes (PLAN §1.10, WN §3.4) */
/* ==================================================================== */
/* Default: C1a's capsule hugs Steam's legends in Steam's order (Filter,
Sort By, Options, Select, Back in gamepad mode; Options, Select, Back
under the laser), so it never shows empty slots when Steam drops its
X / Y / menu legends (focus on the header rows, laser mode). The Y
legend carries the current sort ("Sort By · Alphabetical", thin spaces
round the dot): at most 334 px, so "Alphabetical" fits whole and the
capsule stays <= 960 with all five members in the regular style (957);
a longer sort name ellipsizes with "…" inside
the label (review R2 Q1). Steam's label is a flex box, which never
draws an ellipsis (its text and our ::after are flex items), so this
label becomes a single line box. A capsule that would pass 960 turns
compact (C1a's T2: 20 px labels, 16 px padding) before anything is cut
further. */
%{BasicUiRoot} #Footer%{BasicFooter} [data-lgs-btn="OPTIONS"] {
max-width: 334px;
min-width: 0;
}
%{BasicUiRoot} #Footer%{BasicFooter} [data-lgs-btn="OPTIONS"] :is(%{ActionButtonLabel}, %{CompatFooterDescription}) {
display: block;
flex: 0 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* "Sort By · <current sort>" (T2 copies Steam's sort name into the
attribute; no new string) */
%{BasicUiRoot} #Footer%{BasicFooter} [data-lgs-btn="OPTIONS"] [data-lgs-lib-sort]::after {
content: "\2009·\2009" attr(data-lgs-lib-sort);
font-weight: 500;
color: var(--lgs-text-2);
}
/* Flag libFixedSlots (off): PLAN §1.10's five fixed slots. C1a's T2 holds
the capsule at x 200-1080 (--lgs-orn-x, 880 wide); slots 300 · 130 ·
156 · 134 · 120, gap 4, padding 12; each member is its slot plus a 2 px
share of each gap, so neighbours' hit boxes abut (P-07). */
%{BasicUiRoot} #Footer%{BasicFooter}[data-lgs-orn-fixed="library"] %{ActionButtonLegend} {
position: absolute;
/* C1a lifts the ornament row (and its capsule) by --lgs-c1a-orn-lift */
top: calc(-1 * var(--lgs-c1a-orn-lift, 0px));
box-sizing: border-box;
height: var(--lgs-ornament, 84px);
margin: 0;
padding: 0 18px;
justify-content: center;
min-width: 0;
max-width: none;
--lgs-ill-inset: 12px 2px;
}
%{BasicUiRoot} #Footer%{BasicFooter}[data-lgs-orn-fixed="library"] [data-lgs-btn="OPTIONS"] {
left: calc(var(--lgs-orn-x, 200px) + 10px);
width: 304px;
}
%{BasicUiRoot} #Footer%{BasicFooter}[data-lgs-orn-fixed="library"] [data-lgs-btn="SECONDARY"] {
left: calc(var(--lgs-orn-x, 200px) + 314px);
width: 134px;
}
%{BasicUiRoot} #Footer%{BasicFooter}[data-lgs-orn-fixed="library"] [data-lgs-btn="MENU"] {
left: calc(var(--lgs-orn-x, 200px) + 448px);
width: 160px;
}
%{BasicUiRoot} #Footer%{BasicFooter}[data-lgs-orn-fixed="library"] [data-lgs-btn="OK"] {
left: calc(var(--lgs-orn-x, 200px) + 608px);
width: 138px;
}
%{BasicUiRoot} #Footer%{BasicFooter}[data-lgs-orn-fixed="library"] [data-lgs-btn="CANCEL"] {
left: calc(var(--lgs-orn-x, 200px) + 746px);
width: 124px;
}
%{BasicUiRoot} #Footer%{BasicFooter}[data-lgs-orn-fixed="library"] %{ActionButtonLegend} :is(%{ActionButtonLabel}, %{CompatFooterDescription}) {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
%{BasicUiRoot} #Footer%{BasicFooter}[data-lgs-orn-fixed="library"] %{ActionButtonLegend} + %{ActionButtonLegend}:is([data-lgs-btn="OK"], [data-lgs-btn="CANCEL"]) {
margin-left: 0;
}
/* ==================================================================== */
/* 9. Library Filters (Steam's dialog in C1c's sheet frame), T1 */
/* ==================================================================== */
/* C1c draws the sheet frame (thick glass, <= 960, under the toolbar row,
the close circle at (24, 24)). HA §9's T1: the sheet is 960 wide; a title
row pinned at its top holds the close circle and "Library Filters"
centred (review R2 Q2); the content scrolls under it in its own body
(the card stops scrolling), with scroll-edge fades; the compatibility
rows are 80 px single-choice rows in one platter with a trailing blue
check and Steam's icons; each FilterBucket is a platter of 80 px check
rows; Reset and Save are 60 px capsules at the end. Nothing is moved out
of its parent or hidden. */
/* the card: Steam's scroller (%{DialogWrapper}) is not a direct .Panel
child of ModalPosition, so C1c's generic sheet rule does not reach it
(REQ-10); it takes the same sheet look here. Steam's navy fill goes. */
%{DialogWrapper}:has(> %{DialogWrapper>CompatFilterDialog}) {
box-sizing: border-box;
width: var(--lgs-sheet-max-w, 960px) !important;
max-width: 100%;
/* C1c's modal box holds the card to 488 (552 on window-full); the card
is a column: the pinned title row, then the scrolling body */
display: flex;
flex-direction: column;
overflow: hidden;
border: 0;
border-radius: var(--lgs-r-sheet, 44px);
/* Steam's modal layer is the backdrop root here, so the blur cannot
reach the page: a denser tint under the material keeps the content
legible over posters (P-39) */
background: linear-gradient(rgb(20 22 30 / .62), rgb(20 22 30 / .62)), var(--lgs-mat-thick-bg) !important;
backdrop-filter: var(--lgs-mat-thick-blur);
box-shadow: var(--lgs-mat-thick-shade), var(--lgs-shadow-10mm);
--lgs-edge: thick;
animation: lgs-sheet-in var(--lgs-motion-sheet-in) backwards;
}
%{DialogWrapper>CompatFilterDialog} {
/* glass before content on entry (G-MOTION): the content waits for the
card's first quarter, as in C1c's sheets */
animation: lgs-sheet-content-in var(--lgs-motion-sheet-in) backwards;
box-sizing: border-box;
flex: 1 1 auto;
min-height: 0;
display: flex;
flex-direction: column;
margin: 0;
padding: 24px 0 0;
border: 0;
border-radius: 0;
background: transparent !important;
box-shadow: none;
/* Body for Steam's unstyled text (section labels such as "Steam Frame
Compatibility:"): >= 18 px, >= 500 (P-38) */
font: 500 22px/1.3 var(--lgs-font);
letter-spacing: 0;
}
/* the title row: the close circle's row (y 24-84 of the card), the title
centred and clear of the circle (24 + 60 + 12 on each side) */
%{DialogWrapper>CompatFilterDialog} > .DialogHeader {
box-sizing: border-box;
flex: none;
height: 60px;
margin: 0;
padding: 0 96px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
border: 0;
}
%{DialogWrapper>CompatFilterDialogTitle} {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font: var(--lgs-type-title2);
letter-spacing: 0;
text-transform: none;
color: var(--lgs-text-1);
text-shadow: none;
}
/* the body scrolls under the title row; 12 px and 56 px scroll-edge fades
(HA §9); the focused row is kept clear of both (P-23's intent) */
%{DialogWrapper>CompatFilterDialog} > %{DialogWrapper>DialogBody} {
box-sizing: border-box;
flex: 1 1 auto;
min-height: 0;
margin: 0;
padding: 12px 24px 32px;
overflow-x: hidden;
overflow-y: auto;
scroll-padding-block: 16px 56px;
-webkit-mask-image: linear-gradient(180deg, transparent 0, #000 12px, #000 calc(100% - 56px), transparent 100%);
mask-image: linear-gradient(180deg, transparent 0, #000 12px, #000 calc(100% - 56px), transparent 100%);
}
%{DialogWrapper>CompatFilterDialogDescription} {
margin: 0 0 16px;
font: var(--lgs-type-callout);
letter-spacing: 0;
color: var(--lgs-text-2);
}
/* "Steam Frame Compatibility:": a section label (CTL §10) */
%{DialogWrapper>CompatFilterDialog} > %{DialogWrapper>DialogBody} > div:not([class]) {
margin: 0 0 8px 24px;
font: 600 22px/1.3 var(--lgs-font);
color: var(--lgs-text-2);
}
%{CompatFilterOptions} {
display: flex;
flex-direction: column;
gap: 0;
border: 0;
border-radius: 0;
background: none;
overflow: visible;
}
/* the four compatibility rows: one platter of contiguous 80 px rows, 2 px
apart (CTL §10); the label and Steam's description on two lines */
%{CompatFilterOptions} %{DialogWrapper>CompatFilterDialogRow} {
position: relative;
isolation: isolate;
box-sizing: border-box;
min-height: 80px;
margin: 0 0 2px;
padding: 10px 24px;
gap: 16px;
align-items: center;
border: 0;
border-radius: 0;
background-color: rgb(0 0 0 / .14);
color: var(--lgs-text-1);
font: 500 24px/1.3 var(--lgs-font);
box-shadow: none !important;
--lgs-ill: row;
--lgs-ill-z: -1;
}
%{CompatFilterOptions} %{DialogWrapper>CompatFilterDialogRow}:first-child {
border-radius: var(--lgs-r-platter) var(--lgs-r-platter) 0 0;
}
%{CompatFilterOptions} %{DialogWrapper>CompatFilterDialogRow}:last-child {
margin-bottom: 0;
border-radius: 0 0 var(--lgs-r-platter) var(--lgs-r-platter);
}
/* Steam's own hover and focus fills go back to the platter (the light
hook carries both states) */
%{CompatFilterOptions} %{DialogWrapper>CompatFilterDialogRow}:hover,
&:not(.lgs-input-laser) %{CompatFilterOptions} %{DialogWrapper>CompatFilterDialogRow}.gpfocus {
background-color: rgb(0 0 0 / .14) !important;
color: var(--lgs-text-1) !important;
}
%{DialogWrapper>CompatFilterLabel} {
font: 500 24px/1.3 var(--lgs-font);
letter-spacing: 0;
text-transform: none;
}
%{DialogWrapper>CompatFilterDialogRow} %{DialogWrapper>CompatFilterDescription} {
font: var(--lgs-type-footnote);
letter-spacing: 0;
color: var(--lgs-text-2);
}
/* on the focus light the secondary line keeps 4.5 : 1 (P-39) */
&:not(.lgs-input-laser) %{DialogWrapper>CompatFilterDialogRow}.gpfocus %{DialogWrapper>CompatFilterDescription},
&:not(.lgs-input-laser) %{Filters>Container} %{FilterArea>Row}.gpfocuswithin %{Filters>Description} {
color: var(--lgs-text-1);
}
/* the row's grid: the label and Steam's icons on the first line, the
description across the row on the second, the check trailing (so every
description fits one line and the four rows are even, review R2 m4).
Steam's wrapper of label and description is a plain div, so it lets its
two lines take the grid's cells (display: contents); the radio becomes
the trailing check of a single-choice row (HA §9 T1; review R2 m8):
empty when not chosen, a blue check when chosen (a decoration: the row
is the control). Nothing focusable moves (D2 §12) */
%{CompatFilterOptions} %{DialogWrapper>CompatFilterDialogRow} {
display: grid;
grid-template-columns: minmax(0, 1fr) auto 28px;
grid-template-areas: "label icons check" "desc desc check";
column-gap: 16px;
row-gap: 2px;
}
%{DialogWrapper>CompatFilterDialogRow} > div:not([class]) {
display: contents;
}
%{DialogWrapper>CompatFilterDialogRow} %{DialogWrapper>CompatFilterLabel} {
grid-area: label;
min-width: 0;
}
%{DialogWrapper>CompatFilterDialogRow} %{DialogWrapper>CompatFilterDescription} {
grid-area: desc;
min-width: 0;
}
%{DialogWrapper>CompatFilterDialogRow} %{DialogWrapper>CompatFilterDialogIcons} {
grid-area: icons;
}
%{DialogWrapper>CompatFilterDialogRow} %{DialogWrapper>RadioButton} {
grid-area: check;
flex: none;
width: 28px;
height: 28px;
margin: 0;
border: 0;
border-radius: 0;
background: none;
box-shadow: none;
}
%{DialogWrapper>CompatFilterDialogRow}%{DialogWrapper>Active} %{DialogWrapper>RadioButton} {
background: var(--lgs-blue);
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 14.5l5.5 5.5L22 8.5'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 14.5l5.5 5.5L22 8.5'/%3E%3C/svg%3E") center / contain no-repeat;
}
/* Steam's divider under the rows: room only */
%{CompatFilterOptions} %{FilterSummary} {
margin: 16px 0 0;
border: 0;
background: none;
}
/* advanced filter buckets: platters with their section label */
%{Filters>Container} %{FilterBucket} {
margin: 0 0 16px;
padding: 8px 0;
border: 0;
border-radius: var(--lgs-r-platter);
background-color: rgb(0 0 0 / .14);
box-shadow: none;
}
%{Filters>Container} %{FilterBucket} %{FilterBucketLabel},
%{Filters>Container} %{SearchBucketLabel} {
margin: 0;
padding: 8px 24px 4px;
font: 600 22px/1.3 var(--lgs-font);
letter-spacing: 0;
text-transform: none;
color: var(--lgs-text-2);
}
%{Filters>Container} %{FilterBucket} %{FilterBucketBoxes} %{FilterArea>Row} {
box-sizing: border-box;
min-height: 80px;
margin: 0 8px;
padding: 0;
border-radius: var(--lgs-r-row);
color: var(--lgs-text-1);
font: 500 24px/1.3 var(--lgs-font);
box-shadow: none !important;
transition: background-color var(--lgs-motion-fade);
}
&:not(.lgs-input-laser) %{Filters>Container} %{FilterBucket} %{FilterBucketBoxes} %{FilterArea>Row}.gpfocuswithin {
background-color: rgb(255 255 255 / var(--lgs-focus-add, .32)) !important;
color: var(--lgs-text-1) !important;
}
/* Steam's check line (its focusable tooltip source) fills its 80 px row,
so the row's whole width and height take the laser (P-08, P-80); the
label is Body 24 Medium. (C4a draws the check box itself.) */
%{Filters>Container} %{FilterArea>Row} > .tool-tip-source {
box-sizing: border-box;
flex: 1 1 auto;
width: 100%;
min-width: 0;
}
%{Filters>Container} %{FilterArea>Row} .DialogCheckbox_Container {
box-sizing: border-box;
width: 100%;
min-height: 80px;
margin: 0;
padding: 0 16px;
align-items: center;
}
%{Filters>Container} %{FilterArea>Row} .DialogCheckbox_Container :is(span, label, div):not(.DialogCheckbox, .DialogCheckbox *) {
font: 500 24px/1.3 var(--lgs-font);
letter-spacing: 0;
text-transform: none;
}
%{Filters>Container} %{FilterBucket} %{FilterBucketBoxes} %{FilterArea>Row} %{Filters>Description} {
font: 500 20px/1.3 var(--lgs-font);
color: var(--lgs-text-2);
}
/* the drop-downs (Gamepad Support, Language) and the two text fields sit
in their platters at the 24 px inset */
%{Filters>Container} %{FilterBucket} %{CompatDropDown},
%{Filters>Container} %{SearchBoxes} .DialogInput_Wrapper {
margin: 8px 24px;
}
%{Filters>Container} %{SearchBoxes} {
margin: 0 0 16px;
}
/* tag search results drop-down (only while typing): a recessed list in
the sheet, not a second glass (P-45, review R2 m2) */
%{Filters>Container} %{FilterBucket}%{SearchResultsBox} %{FilterBucketBoxes} {
background: var(--lgs-fill-thick);
backdrop-filter: none;
box-shadow: none;
border-radius: var(--lgs-r-row);
}
/* Reset and Save: 60 px capsules at the end, in Steam's order */
%{Filters>Container} %{Filters>Buttons} {
gap: 12px;
margin: 8px 0 0;
}
/* a focused section (Steam's FilterBucket is itself a focusable group: the
D-pad moves from section to section and A enters one, as on stock) lights
as one platter with P4's light (focus + white .32, the spot and the arc),
instead of the generic FocusRing plate with its 24 px corners */
%{Filters>Container} %{FilterBucket} {
position: relative;
isolation: isolate;
--lgs-ill: raised;
--lgs-ill-r: var(--lgs-r-platter);
--lgs-ill-z: -1;
}
/* T3 (flag libFilterChips, 40-library.js): the section's options as toggle
capsules that wrap into lines (HA §9; D-C2c-17). The patch gives the
section flow-children "grid", so the D-pad follows what is drawn (D2
§12), and C4a's data-lgs-noprim (the capsule draws its own check). Each
capsule: Steam's row and its focusable check line as one 80 px hit box
(they abut across the 12 px gaps), a 60 px visible capsule (24 px
padding, 24 px Semibold); off = the raised fill, on = white .94 with a
dark label and a leading check. Without the patch the rows above stay. */
%{FilterBucket}.lgs-lib-chips %{FilterBucketBoxes} {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
gap: 0;
padding: 0 18px 4px;
}
%{FilterBucket}.lgs-lib-chips %{FilterBucketBoxes} %{FilterArea>Row} {
flex: 0 0 auto;
width: auto;
min-width: 0;
min-height: 0;
margin: 0;
padding: 0;
border-radius: 0;
background: none !important;
}
%{FilterBucket}.lgs-lib-chips %{FilterArea>Row} > .tool-tip-source {
position: relative;
isolation: isolate;
box-sizing: border-box;
flex: none;
width: auto;
height: 80px;
padding: 0;
border: 10px solid transparent;
border-inline-width: 6px;
/* at least 168 wide: a short label keeps 85 % of Steam's 552 x 28 row
(G-AUD's SHRUNK); the label centres in it */
min-width: 168px;
justify-content: center;
border-radius: 36px / 40px;
background: var(--lgs-raised-bg) padding-box;
box-shadow: none;
color: var(--lgs-text-1);
transition: background-color var(--lgs-motion-fade), color var(--lgs-motion-fade);
}
%{FilterBucket}.lgs-lib-chips %{FilterArea>Row} > .tool-tip-source:has(.DialogCheckbox.Active) {
background: var(--lgs-selected-fill) padding-box;
color: var(--lgs-text-on-selected);
}
/* the capsule's light (hover, gamepad focus, press) is on Steam's check
line, the element that takes focus and fills the 60 px capsule; white
capsules show focus as the outer glow (P-16). Being a light host also
turns P4's FocusRing plate off for it */
%{FilterBucket}.lgs-lib-chips %{FilterArea>Row} .DialogCheckbox_Container {
position: relative;
isolation: isolate;
border-radius: 30px;
--lgs-ill: raised;
--lgs-ill-r: 30px;
--lgs-ill-z: -1;
}
%{FilterBucket}.lgs-lib-chips %{FilterArea>Row} .DialogCheckbox_Container:has(.DialogCheckbox.Active) {
--lgs-ill: white;
}
%{FilterBucket}.lgs-lib-chips %{FilterArea>Row} .DialogCheckbox_Container {
box-sizing: border-box;
width: 100%;
justify-content: center;
min-height: 60px;
height: 60px;
margin: 0;
padding: 0 24px;
gap: 8px;
align-items: center;
color: inherit;
}
%{FilterBucket}.lgs-lib-chips %{FilterArea>Row} .DialogCheckbox_Container :is(span, label, div):not(.DialogCheckbox, .DialogCheckbox *) {
font: 600 24px/1.2 var(--lgs-font);
letter-spacing: 0;
text-transform: none;
white-space: nowrap;
color: inherit;
}
/* the check: Steam's own 22 px check box, unscaled (C4a's 40 px circle and
its 80 px hit extender are for rows; the capsule is the hit here): a
faint disc while off, Steam's check drawn dark on the white capsule
while on; no light of its own (the capsule's light carries the states) */
%{FilterBucket}.lgs-lib-chips .DialogCheckbox {
flex: none;
scale: 1;
width: 22px;
height: 22px;
margin: 0;
border: 0;
border-radius: 50%;
background: rgb(255 255 255 / .16) !important;
box-shadow: none;
--lgs-ill: none;
&::before { inset: 0; }
}
%{FilterBucket}.lgs-lib-chips .DialogCheckbox.Active {
background: none !important;
--lgs-ill: none;
}
%{FilterBucket}.lgs-lib-chips .DialogCheckbox .SVGIcon_DialogCheck path {
stroke: var(--lgs-text-on-selected);
}
/* the row-focus fill of the T1 rows does not apply to capsules (their own
light carries focus) */
&:not(.lgs-input-laser) %{Filters>Container} %{FilterBucket}.lgs-lib-chips %{FilterBucketBoxes} %{FilterArea>Row}.gpfocuswithin {
background: none !important;
}
%{FilterBucket}.lgs-lib-chips %{FilterArea>Row} %{Filters>Description} {
display: block;
font: 500 18px/1.2 var(--lgs-font);
color: inherit;
opacity: .70;
}
/* ==================================================================== */
/* 10. The letter scrubber (T3, flag libScrubber; HA §7) */
/* ==================================================================== */
/* While 40-library.js mounts the scrubber (Alphabetical, 4 rows or more)
the grid's columns step in by 72 px, so posters end at x 1172 and the
scrubber has its own column (D-C2c-2; the module unmounts it if Steam's
grid then loses a column) */
%{GamepadLibrary} %{GridWithControls}[data-lgs-scrub] {
padding-inline-end: 72px !important;
}
/* our node: an 80 px wide hit column at x 1186-1266, y 270-610, a 60 px
recessed capsule inside (black .14, the inset shade), letters 20 px
Semibold .70 with 6 px dots when they do not all fit, the current letter
in a 40 px white circle that travels on snappy. It is docked in a zero-
height sticky wrapper at the end of the grid's scroller, held at the
scroller's bottom edge (y 720), so it composites with the scrolling
content instead of over it (G-PERF) */
& .lgs-lib-scrub-dock {
position: sticky;
bottom: 0;
z-index: 4;
display: block;
width: 100%;
height: 0;
margin: 0;
padding: 0;
pointer-events: none;
}
& .lgs-lib-scrub {
position: absolute;
right: var(--lgs-scrub-right, 14px);
bottom: var(--lgs-scrub-bottom, 110px);
box-sizing: border-box;
width: 80px;
height: 340px;
touch-action: none;
user-select: none;
cursor: default;
pointer-events: auto;
--lgs-maxside: 340;
animation: lgs-mat-glass-in var(--lgs-motion-mat-in) backwards;
}
& .lgs-lib-scrub-track {
position: absolute;
inset: 0 10px;
border-radius: var(--lgs-r-capsule);
background: var(--lgs-fill-regular);
box-shadow: var(--lgs-recessed-shadow);
pointer-events: none;
transition: background-color var(--lgs-motion-fade);
}
&:not(.lgs-input-pad) .lgs-lib-scrub:is(:hover, .is-active) .lgs-lib-scrub-track {
background-color: rgb(255 255 255 / var(--lgs-hover-add, .08));
}
& .lgs-lib-scrub-list {
position: absolute;
inset: 20px 10px;
pointer-events: none;
}
/* each letter at its own share of the column (T3 writes --lgs-scrub-at) */
& .lgs-lib-scrub-list > * {
position: absolute;
left: 50%;
top: calc(var(--lgs-scrub-at, 0) * 100%);
translate: -50% -50%;
}
& .lgs-lib-scrub-l {
display: block;
height: 20px;
font: 600 20px/20px var(--lgs-font);
letter-spacing: 0;
color: var(--lgs-text-2);
}
& .lgs-lib-scrub-dot {
display: block;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--lgs-text-3);
}
& .lgs-lib-scrub-at {
display: block;
width: 0;
height: 0;
}
& .lgs-lib-scrub-cur {
position: absolute;
left: 50%;
top: 20px;
box-sizing: border-box;
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--lgs-selected-fill);
box-shadow: var(--lgs-shadow-5mm);
color: var(--lgs-text-on-selected);
font: 600 22px/40px var(--lgs-font);
text-align: center;
/* the column's 300 px of letters */
translate: -50% calc(var(--lgs-scrub-at, 0) * 300px - 20px);
pointer-events: none;
transition: translate var(--lgs-motion-snappy);
}
& .lgs-lib-scrub-cur:empty {
opacity: 0;
}
/* ==================================================================== */
/* 11. Reduce Motion, High Contrast */
/* ==================================================================== */
@media (prefers-reduced-motion: reduce) {
%{GamepadLibrary} :is(%{LibraryItemBox}, %{Collection}) {
scale: 1;
}
}
@media (prefers-contrast: more) {
%{GamepadLibrary} %{TabHeaderRowWrapper}::before {
background: var(--lgs-mat-thick-bg);
backdrop-filter: none;
-webkit-mask-image: none;
mask-image: none;
}
%{GamepadLibrary} %{SortAndFilterContainer}::before {
background: var(--lgs-mat-thick-bg);
}
}