Files
MichaelScottsman--LiquidAss…/theme/10-primitives.css
T

1335 lines
62 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Glass Shell primitives (package C4a, Phase 2 T1).
Concept: docs/phase2/concepts/controls.md (CTL) §4-§10, §13; PLAN §1.3,
§1.4, §1.16; tokens and hooks: docs/phase2/contracts/tokens.md (P4);
motion tokens and keyframes: docs/phase2/contracts/motion.md (P5).
Wrapped as html.lgs-on { ... } by the bundler ("&" is html.lgs-on).
What this file does: it opts Steam's shared primitives into P4's hooks
(--lgs-ill: the light layer on ::after; --lgs-edge: the specular arc on
::before; the state numbers --lgs-hover/-focus/-within/-press/-dis),
gives them visionOS sizes, overrides Steam's own focus fills and rings,
and adds what is specific to each control (rows and the control inside
them, switch knob and track, slider fill and knob, check circles).
Rules for areas (CTL §0.1): areas place these primitives and never restyle
them; a subtree that draws its own control forms opts out with
[data-lgs-noprim] on its root. Every look is keyed on P3's input mode:
laser looks on :hover (html:not(.lgs-input-pad)), gamepad looks on
.gpfocus (html:not(.lgs-input-laser)); rules that name .gpfocus without a
mode only put a control back to its rest look (Steam paints focus white).
Sections
1. Buttons (DialogButton family) 6. Pop-up buttons (dropdowns)
2. Rows and platters (G>Field) 7. Segmented controls
3. Switches (G>Toggle) 8. Text fields
4. Check circles (.DialogCheckbox) 9. Progress bars
5. Sliders 10. Pop-up labels in C1c's menus
11. Markup classes (.lgs-btn, ...) 12. High Contrast
Menus, alerts, sheets and the scrim are C1c's (22-presentations.css). */
/* ======================================================================
1. Buttons: button.DialogButton + G>Button (CTL §5)
Capsule 60 (label 24 Semibold, padding 24), icon-only circle 60.
Rest = raised fill + lit lip; the light layer (::after, under the label)
and the specular arc (::before) are P4's hooks. Press swells glass
controls by min(1.06, 1 + 6 / longest side) on the independent `scale`.
====================================================================== */
button.DialogButton%{*GamepadDialogContent>Button}:not([data-lgs-noprim] *) {
min-height: var(--lgs-btn);
padding: 0 var(--lgs-btn-pad);
border: 0;
border-radius: var(--lgs-r-capsule);
font: var(--lgs-type-headline);
letter-spacing: var(--lgs-track-headline);
text-transform: none;
text-shadow: none;
background: var(--lgs-raised-bg);
box-shadow: var(--lgs-raised-shadow);
color: var(--lgs-text-1);
/* the light layer under the label (VP P-10), the arc on ::before */
isolation: isolate;
--lgs-ill: raised;
--lgs-ill-z: -1;
--lgs-edge: control;
/* a softer pointer spot on a capsule keeps laser hover within VP P-03's
+10..+25 L (P4's .12 measured +26.3 on /zoo/buttons; C4a-D16) */
--lgs-hover-spot: .10;
/* press swell: longest side of a typical capsule (CTL C-D12) */
--lgs-c4a-side: 160;
scale: calc(1 + (min(var(--lgs-press-max), 1 + var(--lgs-press-grow) / var(--lgs-c4a-side)) - 1) * var(--lgs-press));
transition:
scale var(--lgs-d-snappy) var(--lgs-ease-b15),
background-color var(--lgs-d-fade) var(--lgs-ease-b0),
color var(--lgs-d-fade) var(--lgs-ease-b0);
}
/* press in on `interactive`, release on `snappy` (CTL §4.4) */
button.DialogButton%{*GamepadDialogContent>Button}:not([data-lgs-noprim] *):is(:active, .lgs-pressed) {
transition:
scale var(--lgs-d-interactive) var(--lgs-ease-b15),
background-color var(--lgs-d-fade) var(--lgs-ease-b0),
color var(--lgs-d-fade) var(--lgs-ease-b0);
}
/* Steam paints focus, hover and press white (and Primary blue on hover):
back to the rest look in every mode, so the light layer carries every
state (tokens.md §3.2). Neutral only: no look keys on .gpfocus here. */
button.DialogButton%{*GamepadDialogContent>Button}:not([data-lgs-noprim] *):is(.gpfocus, :hover, :active, :focus, .Primary, .Destructive) {
background: var(--lgs-raised-bg) !important;
box-shadow: var(--lgs-raised-shadow) !important;
color: var(--lgs-text-1) !important;
}
/* glyph buttons darken their icon for Steam's white focus; ours is not white */
button.DialogButton%{*GamepadDialogContent>Button}.Glyph:not([data-lgs-noprim] *) {
filter: none !important;
}
/* icon-only buttons: 60 px circles, 26 px symbol (CTL §5, VP P-83) */
button.DialogButton%{*GamepadDialogContent>Button}:is(%{ShowKeyboardButton}, %{BuiltInLayoutButton}, %{BackButtonContent>BackButton}):not([data-lgs-noprim] *) {
width: var(--lgs-btn);
height: var(--lgs-btn);
min-width: var(--lgs-btn);
padding: 0;
border-radius: 50%;
display: inline-grid;
place-items: center;
--lgs-c4a-side: 60;
& svg {
width: var(--lgs-symbol);
height: var(--lgs-symbol);
}
}
/* the one tinted action: a dialog's confirm. Steam marks other buttons
Primary too (row actions such as Disconnect): those stay raised, so a
screen keeps a single tinted capsule (CTL §5 roles). White on blue. */
:is(.DialogFooter, %{ModalButtonRow}, .DialogTwoColLayout) button.DialogButton%{*GamepadDialogContent>Button}.Primary:not(.Disabled, [disabled]):not([data-lgs-noprim] *) {
background: linear-gradient(180deg, rgb(255 255 255 / .16), rgb(255 255 255 / 0) 55%), var(--lgs-tint-primary) !important;
box-shadow: inset 0 1.5px 1.5px -1px rgb(255 255 255 / .35), 0 3px 8px rgb(0 0 0 / .22) !important;
color: var(--lgs-text-on-tint) !important;
--lgs-ill: white;
}
/* destructive confirm inside a dialog: red whole fill, dark label (C-D21) */
:is(.DialogFooter, %{ModalButtonRow}, .DialogTwoColLayout) button.DialogButton%{*GamepadDialogContent>Button}.Destructive:not(.Disabled, [disabled]):not([data-lgs-noprim] *) {
background: linear-gradient(180deg, rgb(255 255 255 / .16), rgb(255 255 255 / 0) 55%), var(--lgs-fill-danger) !important;
box-shadow: inset 0 1.5px 1.5px -1px rgb(255 255 255 / .35), 0 3px 8px rgb(0 0 0 / .22) !important;
color: var(--lgs-text-on-color) !important;
--lgs-ill: white;
}
/* Play: green, dark label and glyph (C-D21, CC §7) */
button.DialogButton.GreenPlay:not(.Disabled, [disabled]):not([data-lgs-noprim] *) {
background: linear-gradient(180deg, rgb(255 255 255 / .16), rgb(255 255 255 / 0) 55%), var(--lgs-tint-play) !important;
box-shadow: inset 0 1.5px 1.5px -1px rgb(255 255 255 / .35), 0 3px 8px rgb(0 0 0 / .22) !important;
color: var(--lgs-text-on-color) !important;
--lgs-ill: white;
& svg { color: var(--lgs-text-on-color); fill: currentColor; }
}
/* a destructive action outside a dialog footer (<= 2 per screen): a red
Semibold label at rest and under the laser; gamepad focus turns it into a
red whole fill with a dark label and the outer glow (PLAN §1.12) */
button.DialogButton%{*GamepadDialogContent>Button}.Destructive:not(.Disabled, [disabled]):not(:is(.DialogFooter, %{ModalButtonRow}, .DialogTwoColLayout) *):not([data-lgs-noprim] *) {
color: var(--lgs-text-danger) !important;
}
&:not(.lgs-input-laser) button.DialogButton%{*GamepadDialogContent>Button}.Destructive.gpfocus:not(.Disabled, [disabled]):not(:is(.DialogFooter, %{ModalButtonRow}, .DialogTwoColLayout) *):not([data-lgs-noprim] *) {
background: var(--lgs-fill-danger) !important;
color: var(--lgs-text-on-color) !important;
--lgs-ill: white;
}
/* disabled (CTL §4.7): label white .40, fill white .04, no lip. Steam's
opacity .4 is lifted so the focus layers are not dimmed; Steam keeps
disabled buttons focusable, and P4's hook shows disabled + focus */
button.DialogButton%{*GamepadDialogContent>Button}:is(.Disabled, [disabled]):not([data-lgs-noprim] *) {
opacity: 1 !important;
background: rgb(255 255 255 / .04) !important;
box-shadow: none !important;
/* .55, not PLAN §1.4's .40: Steam's own disabled label measures 3.8:1,
and G-AUD forbids less over the bright room (.40 gave 2.9:1; C4a-D11) */
color: rgb(255 255 255 / .55) !important;
& svg { opacity: .55; }
}
/* ======================================================================
2. Rows and platters: G>Field, non-Classic (CTL §10, §4.3)
Rows with Background form one platter (black .14, radius 30) of
contiguous 80 px rows; the lit pill is inset 6 (P4's `row` hook, drawn
under the content); a 2 px separator inset 26 sits between rows and hides
next to a lit row. Classic rows (desktop, Quick Access) are left to their
areas.
====================================================================== */
%{*GamepadDialogContent>Field}:not(%{*GamepadDialogContent>Classic}):not([data-lgs-noprim] *) {
box-sizing: border-box;
min-height: var(--lgs-row-platter);
padding-block: 10px;
padding-inline: calc(var(--lgs-sep-inset) + var(--indent-level, 0) * 20px) var(--lgs-sep-inset);
color: var(--lgs-text-1);
isolation: isolate;
--lgs-ill: row;
--lgs-ill-z: -1;
& %{*GamepadDialogContent>FieldLabel} {
font: var(--lgs-type-body);
color: var(--lgs-text-1);
}
& %{*GamepadDialogContent>FieldDescription} {
font: var(--lgs-type-subhead);
color: var(--lgs-text-2);
margin-top: 4px;
}
& %{*GamepadDialogContent>LabelFieldValue} {
font: var(--lgs-type-callout);
color: var(--lgs-text-2);
}
& %{*GamepadDialogContent>FieldIcon} {
color: var(--lgs-text-1);
}
/* the label column keeps at least 30 % of the row next to a long value */
& > %{*GamepadDialogContent>FieldLeftColumn} {
min-width: 30%;
}
/* controls below the label (Below layouts, sliders) keep 10 px clear of the
label and the note, so their 80 px hit box stays theirs (P-08) */
& > %{*GamepadDialogContent>FieldLeftColumn} > %{*GamepadDialogContent>FieldChildrenWithIcon} {
margin-block: 10px;
}
/* plain value text in the control column: Callout 22, text-2 (CTL §10) */
& %{*GamepadDialogContent>FieldChildrenInner} > div:not([class]) {
font: var(--lgs-type-callout);
color: var(--lgs-text-2) !important;
}
}
/* the control column sits on the row's centre line, like the label */
%{*GamepadDialogContent>Field}%{*GamepadDialogContent>VerticalAlignCenter}:not(%{*GamepadDialogContent>Classic}):not([data-lgs-noprim] *) > %{*GamepadDialogContent>FieldRightColumn} {
align-self: center;
}
/* the platter: consecutive rows with Background, contiguous */
%{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:not(%{*GamepadDialogContent>Classic}):not([data-lgs-noprim] *) {
background: var(--lgs-fill-regular);
border-radius: 0;
}
%{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:not(%{*GamepadDialogContent>Classic}) + %{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:not(%{*GamepadDialogContent>Classic}):not([data-lgs-noprim] *) {
margin-top: 0;
}
/* first row of a run */
:not(%{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}) + %{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:not(%{*GamepadDialogContent>Classic}):not([data-lgs-noprim] *),
%{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:not(%{*GamepadDialogContent>Classic}):first-child:not([data-lgs-noprim] *) {
border-start-start-radius: var(--lgs-r-platter);
border-start-end-radius: var(--lgs-r-platter);
}
/* last row of a run */
%{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:not(%{*GamepadDialogContent>Classic}):not(:has(+ %{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background})):not([data-lgs-noprim] *) {
border-end-start-radius: var(--lgs-r-platter);
border-end-end-radius: var(--lgs-r-platter);
}
/* separators: 2 px white .08, inset 26, on the top edge of a following row */
%{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:not(%{*GamepadDialogContent>Classic}) + %{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:not(%{*GamepadDialogContent>Classic}):not([data-lgs-noprim] *)::before {
content: "";
position: absolute;
top: -1px;
inset-inline: var(--lgs-sep-inset);
height: 2px;
border-radius: 1px;
background: var(--lgs-separator);
pointer-events: none;
transition: opacity var(--lgs-d-fade) var(--lgs-ease-b0);
}
/* hidden on both sides of a lit row (laser hover, gamepad focus) */
&:not(.lgs-input-pad) %{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:hover::before,
&:not(.lgs-input-pad) %{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:hover + %{*GamepadDialogContent>Field}::before,
&:not(.lgs-input-laser) %{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:is(.gpfocus, .gpfocuswithin)::before,
&:not(.lgs-input-laser) %{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:is(.gpfocus, .gpfocuswithin) + %{*GamepadDialogContent>Field}::before {
opacity: 0;
}
/* Steam's focus fill is a `forwards` animation on the row: back to rest */
%{*GamepadDialogContent>Field}:not(%{*GamepadDialogContent>Classic})%{*GamepadDialogContent>HighlightOnFocus}:is(.gpfocus, .gpfocuswithin):not([data-lgs-noprim] *) {
background: transparent !important;
color: var(--lgs-text-1) !important;
}
%{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:not(%{*GamepadDialogContent>Classic})%{*GamepadDialogContent>HighlightOnFocus}:is(.gpfocus, .gpfocuswithin):not([data-lgs-noprim] *) {
background: var(--lgs-fill-regular) !important;
}
/* Focus inside a row (CTL C-D14). Steam focuses the control and marks the
row .gpfocuswithin. A single-control row carries the focus (+ the full
focus light, its arc, the 60 % first frame) and its control is "lifted"
(a partial light, below); a multi-control row only warms (P4's row hook,
.06) and its control carries the focus. T2 tags rows data-lgs-ctl="1|n";
without the tag, rows holding Steam's toggle pairs or check columns are
multi-control. */
&:not(.lgs-input-laser) %{*GamepadDialogContent>Field}:not(%{*GamepadDialogContent>Classic}).gpfocuswithin:not([data-lgs-ctl="n"]):not(:has(%{SettingTogglesCtn}, %{Toggles})):not([data-lgs-noprim] *) {
--lgs-focus: 1;
&::after {
animation: lgs-focus-in var(--lgs-ill-d-in) var(--lgs-ill-ease) backwards;
}
/* the lifted control: + .10 of light and its own arc instead of the full
focus look, so one thing is lit, not two (VP P-13); a slider is lifted
by its knob alone */
& button.DialogButton%{*GamepadDialogContent>Button}.gpfocus,
& %{*GamepadDialogContent>Toggle}.gpfocus > %{*GamepadDialogContent>ToggleRail} {
--lgs-focus: calc(.10 / var(--lgs-focus-add));
}
& :is(%{*SliderControlAndNotches>SliderControlPanelGroup}, %{*SliderControlPanelGroup>SliderControlAndNotches}).gpfocus %{*SliderControlPanelGroup>SliderTrack} {
--lgs-focus: 0;
}
}
/* laser: when the pointer is on the control inside a row, the control is
the one lit thing; the row lights only while the pointer is elsewhere on
it (VP P-13; the row light under a translucent control doubled the hover
to +42 L, over VP P-03's +25) */
&:not(.lgs-input-pad) %{*GamepadDialogContent>Field}:not(%{*GamepadDialogContent>Classic}):has(:is(button.DialogButton, %{*GamepadDialogContent>Toggle}, %{*SliderControlAndNotches>SliderControlPanelGroup}, .DialogCheckbox, input, textarea, %{Group>Button}):hover):not([data-lgs-noprim] *) {
--lgs-hover: 0;
}
/* disabled rows: content at 40 % (CTL §4.7) */
%{*GamepadDialogContent>Field}%{*GamepadDialogContent>Disabled}:not(%{*GamepadDialogContent>Classic}):not([data-lgs-noprim] *) {
& :is(%{*GamepadDialogContent>FieldLabel}, %{*GamepadDialogContent>FieldDescription}, %{*GamepadDialogContent>LabelFieldValue}, %{*GamepadDialogContent>FieldIcon}) {
color: rgb(255 255 255 / var(--lgs-dis-content));
}
}
/* P3 tags a focused disabled control; its row shows disabled + focus */
%{*GamepadDialogContent>Field}:not(%{*GamepadDialogContent>Classic}):has(.lgs-focus-disabled):not([data-lgs-noprim] *) {
--lgs-dis: 1;
}
/* section headers above a platter: 22 px Semibold, text-2, title case */
.SettingsDialogSubHeader:not([data-lgs-noprim] *) {
font-size: var(--lgs-fs-callout);
font-weight: 600;
line-height: 1.3;
padding-block: 4px 12px;
padding-inline-start: var(--lgs-sep-inset);
color: var(--lgs-text-2);
text-transform: none;
letter-spacing: 0;
}
/* free text between platters ("Flash window when I receive…") and the
small explanatory notes: Subhead 20 and Footnote 18, text-2 (VP P-38) */
%{SettingsDialogBodyText}:not([data-lgs-noprim] *) {
font: var(--lgs-type-subhead);
color: var(--lgs-text-2);
}
%{SettingsDialogDescriptionText}:not([data-lgs-noprim] *) {
font: var(--lgs-type-footnote);
color: var(--lgs-text-2);
}
/* a standalone separator: the row separator */
%{*GamepadDialogContent>StandaloneFieldSeparator} {
height: 2px;
background: var(--lgs-separator);
margin-inline: var(--lgs-sep-inset);
}
/* ======================================================================
3. Switches: G>Toggle > ToggleRail (+ Steam's ::before on-fill) +
ToggleSwitch (CTL §6.1). scale 1.75 → 66.5 x 38.5 (visionOS 51 x 31 pt);
the transparent ::before extender (-12px -6px before scaling) makes the
hit 87.5 x 80.5 (E-SWITCH, P-C3). Steam's transforms (knob position, fill
position) are never touched; only their timing and colours change.
The light layer lives on the rail's ::after (above the green fill, under
the knob), fed with the switch's state numbers.
====================================================================== */
%{*GamepadDialogContent>Toggle}:not([data-lgs-noprim] *) {
scale: 1.75;
margin: 8px 14px;
border-radius: 11px;
box-shadow: none !important;
transition: box-shadow var(--lgs-d-fade) var(--lgs-ease-b0);
&:dir(rtl) { scale: -1.75 1.75; }
/* the hit extender (free pseudo-element, P-C3) */
&::before {
content: "";
position: absolute;
inset: -12px -6px;
border-radius: 16px;
}
/* Steam's grey focus halo becomes the rail light (below) */
&::after {
content: none !important;
}
}
%{*GamepadDialogContent>Toggle}:not([data-lgs-noprim] *) > %{*GamepadDialogContent>ToggleRail} {
background: rgb(255 255 255 / .16);
box-shadow: inset 0 .6px 1.7px rgb(0 0 0 / .22);
--lgs-ill: raised;
--lgs-ill-r: 11px;
&::before {
/* Steam's on-fill: visionOS green with a top sheen, cross-fading at t50
(MO §4.15); off, it is gone so no sliver shows around the knob */
background: linear-gradient(180deg, rgb(255 255 255 / .14), rgb(255 255 255 / 0) 60%), var(--lgs-toggle-on) !important;
opacity: 0;
transition: opacity var(--lgs-d-interactive) linear var(--lgs-d-interactive), transform var(--lgs-d-snappy) var(--lgs-ease-b15) !important;
}
}
%{*GamepadDialogContent>Toggle}%{*GamepadDialogContent>On}:not([data-lgs-noprim] *) > %{*GamepadDialogContent>ToggleRail}::before {
opacity: 1;
}
/* the knob: a white disc drawn ~3 px inside the track (a gradient, not
`scale`: Steam positions the knob with `transform`, and an independent
scale would scale that offset too); its shadow follows the disc
(drop-shadow). Travel on `snappy` instead of Steam's overshoot */
%{*GamepadDialogContent>Toggle}:not([data-lgs-noprim] *) > %{*GamepadDialogContent>ToggleSwitch} {
--lgs-c4a-knob: #fff;
background: radial-gradient(circle closest-side, var(--lgs-c4a-knob) calc(100% - 1.8px), rgb(255 255 255 / 0) calc(100% - 1.1px)) !important;
border: 0 !important;
margin: 0 !important;
box-shadow: none;
filter: drop-shadow(0 1.1px 1.4px rgb(0 0 0 / .32));
transition: transform var(--lgs-d-snappy) var(--lgs-ease-b15), box-shadow var(--lgs-d-fade) var(--lgs-ease-b0) !important;
}
/* press: the knob lifts into clear glass (P5's keyframe swells it; the
disc turns to white .5 glass while pressed; the laser's :active, gamepad A
through C4a's T2 trigger class) */
%{*GamepadDialogContent>Toggle}:is(:active, .lgs-knob-lift):not(%{*GamepadDialogContent>Disabled}):not([data-lgs-noprim] *) > %{*GamepadDialogContent>ToggleSwitch} {
--lgs-c4a-knob: rgb(255 255 255 / .5);
animation: lgs-knob-lift var(--lgs-d-snappy) linear;
}
/* the switch's state numbers, carried to the rail's light layer */
&:not(.lgs-input-pad) %{*GamepadDialogContent>Toggle}:hover:not([data-lgs-noprim] *) > %{*GamepadDialogContent>ToggleRail} { --lgs-hover: 1; }
&:not(.lgs-input-laser) %{*GamepadDialogContent>Toggle}.gpfocus:not([data-lgs-noprim] *) > %{*GamepadDialogContent>ToggleRail} {
--lgs-focus: 1;
&::after {
transition: --lgs-hover var(--lgs-ill-d-in) var(--lgs-ill-ease), --lgs-focus 0s, --lgs-press var(--lgs-ill-d-press) var(--lgs-ill-ease-press);
animation: lgs-focus-in var(--lgs-ill-d-in) var(--lgs-ill-ease) backwards;
}
}
& %{*GamepadDialogContent>Toggle}:is(:active, .lgs-pressed):not([data-lgs-noprim] *) > %{*GamepadDialogContent>ToggleRail} { --lgs-press: 1; }
& %{*GamepadDialogContent>Toggle}%{*GamepadDialogContent>Disabled}:not([data-lgs-noprim] *) > %{*GamepadDialogContent>ToggleRail} { --lgs-dis: 1; }
/* gamepad focus: a soft glow on the knob (0 0 14px 3px white .42 after
scaling) */
&:not(.lgs-input-laser) %{*GamepadDialogContent>Toggle}.gpfocus:not([data-lgs-noprim] *) > %{*GamepadDialogContent>ToggleSwitch} {
box-shadow: 0 0 8px 1px rgb(255 255 255 / .42);
}
/* on + focus: green cannot get brighter; the outer glow (C-D22), authored
before scaling (÷ 1.75), and the inner light capped */
&:not(.lgs-input-laser) %{*GamepadDialogContent>Toggle}%{*GamepadDialogContent>On}.gpfocus:not([data-lgs-noprim] *) {
box-shadow: 0 0 calc(var(--lgs-white-glow-blur) / 1.75) calc(var(--lgs-white-glow-spread) / 1.75) rgb(255 255 255 / var(--lgs-white-glow-a)) !important;
& > %{*GamepadDialogContent>ToggleRail} { --lgs-focus: calc(.04 / var(--lgs-focus-add)); }
}
/* disabled (CTL §4.7): track white .07, knob white .42, no shadow */
%{*GamepadDialogContent>Toggle}%{*GamepadDialogContent>Disabled}:not([data-lgs-noprim] *) {
opacity: 1 !important;
& > %{*GamepadDialogContent>ToggleRail} {
background: rgb(255 255 255 / .07) !important;
&::before { opacity: 0 !important; }
}
& > %{*GamepadDialogContent>ToggleSwitch} {
--lgs-c4a-knob: rgb(255 255 255 / .42);
box-shadow: none !important;
filter: none;
}
}
/* ======================================================================
4. Check circles: .DialogCheckbox (CTL §6.2). scale 1.82 → 40 px circle;
the ::before extender (-11px before scaling) makes an 80 x 80 hit
(E-CHECK); cells widen 50 → 80. Off: a shallow well with the "circle"
symbol (the one spread shadow allowed on a control, C5). On: blue whole
fill, white check. Focus is P4's FocusRing plate; on a blue fill the
outer glow with a 12 px spread instead. Values before scaling are ÷ 1.82.
====================================================================== */
.DialogCheckbox:not([data-lgs-noprim] *) {
position: relative;
scale: 1.82;
margin: 9px;
border-radius: 50%;
background: rgb(0 0 0 / .22);
box-shadow: inset 0 0 0 1.4px rgb(255 255 255 / .34), inset 0 1.1px 2.2px rgb(0 0 0 / .30);
isolation: isolate;
--lgs-ill: raised;
--lgs-ill-z: -1;
--lgs-ill-r: 50%;
--lgs-hover-spot: .07;
transition: background-color var(--lgs-d-fade) var(--lgs-ease-b0);
&::before {
content: "";
position: absolute;
inset: -11px;
border-radius: 50%;
}
}
.DialogCheckbox.Active:not([data-lgs-noprim] *) {
background: linear-gradient(180deg, rgb(255 255 255 / .16), rgb(255 255 255 / 0) 60%), var(--lgs-check-on);
box-shadow: inset 0 .8px .8px -.5px rgb(255 255 255 / .35), 0 .5px 1.1px rgb(0 0 0 / .18);
--lgs-ill: white;
--lgs-white-glow-blur: calc(22px / 1.82);
--lgs-white-glow-spread: calc(12px / 1.82);
& .SVGIcon_DialogCheck path { stroke: #fff; }
}
/* Steam's own hover and focus paint (a near-black well) back to rest */
.DialogCheckbox:not(.Active):not([data-lgs-noprim] *):is(:hover, :focus, .gpfocus),
.DialogCheckbox_Container:hover .DialogCheckbox:not(.Active):not([data-lgs-noprim] *) {
background: rgb(0 0 0 / .22) !important;
}
.DialogCheckbox.Disabled:not([data-lgs-noprim] *) {
opacity: 1 !important;
background: rgb(0 0 0 / .09);
box-shadow: inset 0 0 0 1.4px rgb(255 255 255 / .14);
&.Active { background: rgb(0 145 255 / .34); }
}
/* the check matrix: 80 px cells and headers (CTL §6.2) */
%{CheckboxColumn}:not([data-lgs-noprim] *) {
width: var(--lgs-hit);
min-width: var(--lgs-hit);
flex: 0 0 var(--lgs-hit);
justify-content: center;
}
%{CheckboxHeaders} %{CheckboxColumn} {
font: var(--lgs-type-caption);
color: var(--lgs-text-1);
text-transform: none;
text-align: center;
}
%{CheckboxHeaders} %{NotifyViaHeader} {
font: var(--lgs-type-subhead);
font-style: normal;
color: var(--lgs-text-2);
}
/* ======================================================================
5. Sliders (CTL §7). A 64 px recessed capsule; Steam's handle widened to
64 (knob centre = value, P-C2); our white fill from Steam's origin to the
value on the track's free ::after, invisible at |v - o| = 0; the knob a
58 px white disc on the handle's free ::after, shown on hover, focus and
drag and always on stepped sliders; notch cells 64 px and the default
tick 20 px in (P-C8). The light layer is painted into the track's own
background, under the fill. Steam's inline variables, the handle
container's transform and the ::before fill geometry are untouched.
====================================================================== */
:is(%{*SliderControlPanelGroup>SliderControlAndNotches}, %{*SliderControlPanelGroup>SliderControl}):not([data-lgs-noprim] *) {
--slider-handle-width: var(--lgs-slider);
}
%{*SliderControlAndNotches>SliderControlPanelGroup}:not([data-lgs-noprim] *) {
padding-block: 8px;
}
%{*SliderControlPanelGroup>SliderControl}:not([data-lgs-noprim] *) {
height: var(--lgs-slider);
}
%{*SliderControlPanelGroup>SliderTrack}:not([data-lgs-noprim] *) {
height: var(--lgs-slider);
border-radius: calc(var(--lgs-slider) / 2);
--left-track-color: transparent;
--default-value-track-color: transparent;
box-shadow: var(--lgs-recessed-shadow), inset 0 -2px 3px -1px rgb(255 255 255 / .10);
background:
radial-gradient(circle at var(--hx, 50%) var(--hy, 30%),
rgb(255 255 255 / calc(var(--lgs-hover-spot) * var(--lgs-hover) * (1 - var(--lgs-dis)))) 0, rgb(255 255 255 / 0) 75%),
radial-gradient(circle at 50% 30%,
rgb(255 255 255 / calc(var(--lgs-focus-spot) * var(--lgs-focus) * (1 - var(--lgs-dis)))) 0, rgb(255 255 255 / 0) 75%),
linear-gradient(
rgb(255 255 255 / calc(var(--lgs-hover-add) * var(--lgs-hover) * (1 - var(--lgs-dis)) + var(--lgs-focus-add) * var(--lgs-focus) * (1 - var(--lgs-dis)) + var(--lgs-dis-focus-add) * var(--lgs-focus) * var(--lgs-dis))),
rgb(255 255 255 / calc(var(--lgs-hover-add) * var(--lgs-hover) * (1 - var(--lgs-dis)) + var(--lgs-focus-add) * var(--lgs-focus) * (1 - var(--lgs-dis)) + var(--lgs-dis-focus-add) * var(--lgs-focus) * var(--lgs-dis)))),
var(--lgs-fill-thick);
transition:
--lgs-hover var(--lgs-ill-d-out) var(--lgs-ill-ease),
--lgs-focus var(--lgs-ill-d-out) var(--lgs-ill-ease);
/* Steam's own fill stays (geometry), colourless */
&::before {
background-color: transparent !important;
background-image: none !important;
}
/* our fill: from min(origin, value) to max(origin, value), the knob's
width included, so it ends under the knob's far edge */
&::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
inset-inline-start: calc(min(var(--normalized-slider-origin, 0), var(--normalized-slider-value, 0)) * (100% - var(--slider-handle-width)));
width: calc(var(--slider-handle-width) + (max(var(--normalized-slider-origin, 0), var(--normalized-slider-value, 0)) - min(var(--normalized-slider-origin, 0), var(--normalized-slider-value, 0))) * (100% - var(--slider-handle-width)));
border-radius: inherit;
background: linear-gradient(180deg, rgb(255 255 255 / .92), rgb(255 255 255 / .84));
opacity: clamp(0, max(var(--normalized-slider-value, 0) - var(--normalized-slider-origin, 0), var(--normalized-slider-origin, 0) - var(--normalized-slider-value, 0)) * 40, 1);
pointer-events: none;
}
}
/* the slider's state numbers on its track (light layer above) */
&:not(.lgs-input-pad) %{*SliderControlAndNotches>SliderControlPanelGroup}:hover %{*SliderControlPanelGroup>SliderTrack}:not([data-lgs-noprim] *) {
--lgs-hover: 1;
transition: --lgs-hover var(--lgs-ill-d-in) var(--lgs-ill-ease), --lgs-focus var(--lgs-ill-d-out) var(--lgs-ill-ease);
}
&:not(.lgs-input-laser) :is(%{*SliderControlAndNotches>SliderControlPanelGroup}, %{*SliderControlPanelGroup>SliderControlAndNotches}).gpfocus %{*SliderControlPanelGroup>SliderTrack}:not([data-lgs-noprim] *) {
--lgs-focus: 1;
transition: --lgs-hover var(--lgs-ill-d-in) var(--lgs-ill-ease), --lgs-focus 0s;
}
& %{*SliderControlPanelGroup>SliderControlAndNotches}%{*SliderControlPanelGroup>Disabled} %{*SliderControlPanelGroup>SliderTrack}:not([data-lgs-noprim] *) {
--lgs-dis: 1;
&::after { background: rgb(255 255 255 / .30); }
}
/* stepped sliders: the fill ends at the knob's centre (R5) */
%{*SliderControlPanelGroup>SliderControlAndNotches}:has(%{*SliderControlPanelGroup>SliderNotchContainer}) %{*SliderControlPanelGroup>SliderTrack}:not([data-lgs-noprim] *)::after {
width: calc(var(--slider-handle-width) / 2 + (max(var(--normalized-slider-origin, 0), var(--normalized-slider-value, 0)) - min(var(--normalized-slider-origin, 0), var(--normalized-slider-value, 0))) * (100% - var(--slider-handle-width)));
}
/* two-colour range sliders: Steam's own fill kept, recoloured (CTL §7.2) */
%{*SliderControlPanelGroup>DefaultValueIsColorRange} %{*SliderControlPanelGroup>SliderTrack}:not([data-lgs-noprim] *),
%{*SliderControlPanelGroup>SliderTrack}%{*SliderControlPanelGroup>DefaultValueIsColorRange}:not([data-lgs-noprim] *) {
--left-track-color: rgb(255 255 255 / .86);
--default-value-track-color: rgb(255 255 255 / .40);
&::before { background-color: var(--left-track-color) !important; background-image: revert !important; }
&::after { content: none; }
}
/* the knob: Steam's 64 x 64 handle, transparent; a 58 px white disc on its
::after, shown on hover, focus, drag and in a focused row */
%{*SliderControlPanelGroup>SliderHandle}:not([data-lgs-noprim] *) {
/* Steam positions it (absolute, at its container's end): never touched */
width: var(--slider-handle-width);
height: var(--slider-handle-width);
background: transparent !important;
box-shadow: none !important;
border: 0 !important;
margin: 0 !important;
animation: none !important;
&::after {
content: "";
position: absolute;
inset: 3px;
border-radius: 50%;
background: #fff;
box-shadow: 0 2px 8px rgb(0 0 0 / .32);
opacity: 0;
pointer-events: none;
transition:
opacity var(--lgs-d-fade) var(--lgs-ease-b0),
scale var(--lgs-d-snappy) var(--lgs-ease-b15);
}
}
&:not(.lgs-input-pad) %{*SliderControlAndNotches>SliderControlPanelGroup}:hover %{*SliderControlPanelGroup>SliderHandle}:not([data-lgs-noprim] *)::after,
&:not(.lgs-input-laser) :is(%{*SliderControlAndNotches>SliderControlPanelGroup}, %{*SliderControlPanelGroup>SliderControlAndNotches}).gpfocus %{*SliderControlPanelGroup>SliderHandle}:not([data-lgs-noprim] *)::after,
&:not(.lgs-input-laser) %{*GamepadDialogContent>Field}.gpfocuswithin %{*SliderControlPanelGroup>SliderHandle}:not([data-lgs-noprim] *)::after,
& %{*SliderControlAndNotches>SliderControlPanelGroup}:active %{*SliderControlPanelGroup>SliderHandle}:not([data-lgs-noprim] *)::after,
& %{*SliderControlPanelGroup>SliderControlAndNotches}:has(%{*SliderControlPanelGroup>SliderNotchContainer}) %{*SliderControlPanelGroup>SliderHandle}:not([data-lgs-noprim] *)::after {
opacity: 1;
transition:
opacity var(--lgs-d-hover-in) var(--lgs-ease-b0),
scale var(--lgs-d-interactive) var(--lgs-ease-b15);
}
/* stepped: a 50 px disc with a stronger shadow */
%{*SliderControlPanelGroup>SliderControlAndNotches}:has(%{*SliderControlPanelGroup>SliderNotchContainer}) %{*SliderControlPanelGroup>SliderHandle}:not([data-lgs-noprim] *)::after {
inset: 7px;
box-shadow: 0 2px 8px rgb(0 0 0 / .38);
}
/* drag: the knob grows x1.25 as a bead of clear glass */
& %{*SliderControlAndNotches>SliderControlPanelGroup}:active %{*SliderControlPanelGroup>SliderHandle}:not([data-lgs-noprim] *)::after {
scale: 1.25;
background: radial-gradient(circle at 50% 30%, rgb(255 255 255 / .30), rgb(255 255 255 / .04) 70%);
backdrop-filter: blur(2px) saturate(1.8) brightness(.86);
box-shadow: 0 6px 16px rgb(0 0 0 / .30), inset 0 1.5px 1.5px -1px rgb(255 255 255 / .55);
}
/* disabled: the knob dimmed */
%{*SliderControlPanelGroup>SliderControlAndNotches}%{*SliderControlPanelGroup>Disabled} %{*SliderControlPanelGroup>SliderHandle}:not([data-lgs-noprim] *)::after {
background: rgb(255 255 255 / .45);
box-shadow: none;
}
/* notches: 64 px cells so every tick sits at its value (P-C8); ticks
become 8 px dots, labels 18 px Semibold title case (CTL §7.3) */
%{*SliderControlPanelGroup>SliderNotch}:not([data-lgs-noprim] *) {
width: var(--slider-handle-width);
min-width: var(--slider-handle-width);
flex-basis: var(--slider-handle-width);
}
/* the notch row rises into the track so its 8 px dots sit on the track's
centre line, above the fill and under the knob; its labels sit below the
track. Steam's notch row is a focus scope with no click handler of its
own (probed 2026-10-07: onContextMenu only), so a press on it still
reaches the slider group; 80 tall, so its own hit box is all its own
(P-80, P-08) */
%{*SliderControlPanelGroup>SliderNotchContainer}:not([data-lgs-noprim] *) {
position: relative;
z-index: 1;
box-sizing: border-box;
min-height: var(--lgs-hit);
margin-top: calc(-1 * var(--lgs-slider) / 2 - 4px);
}
%{*SliderControlPanelGroup>SliderHandleContainer}:not([data-lgs-noprim] *) {
z-index: 2;
}
%{*SliderControlPanelGroup>SliderNotchTick}:not([data-lgs-noprim] *) {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgb(255 255 255 / .42);
}
%{*SliderControlPanelGroup>SliderNotchTick}%{*SliderControlPanelGroup>TickActive}:not([data-lgs-noprim] *) {
background-color: rgb(13 14 18 / .28);
}
%{*SliderControlPanelGroup>SliderNotchLabel}:not([data-lgs-noprim] *) {
font: 600 var(--lgs-fs-footnote)/1.2 var(--lgs-font);
text-transform: none;
letter-spacing: 0;
color: var(--lgs-text-2);
padding-top: calc(var(--lgs-slider) / 2 + 12px);
}
%{*SliderControlPanelGroup>SliderNotchTick} + %{*SliderControlPanelGroup>SliderNotchLabel}:not([data-lgs-noprim] *) {
padding-top: calc(var(--lgs-slider) / 2 + 4px);
}
/* the default-value tick: back on its value with the 64 px handle (P-C8) */
%{*SliderControlPanelGroup>DefaultValueTick}:not([data-lgs-noprim] *) {
inset-inline-end: calc((var(--slider-handle-width) - 24px) / 2);
& > svg { color: rgb(255 255 255 / .72); }
}
/* T2 (wp.c4a): a full-width slider whose row shows no value gets it as
decorative text in a 72 px trailing column (CTL §7.2; --lgs-c4a-val is
written by device/rt/10-controls.js, never focusable or clickable) */
%{*SliderControlAndNotches>SliderControlPanelGroup}[data-lgs-val]:not([data-lgs-noprim] *) {
position: relative;
padding-inline-end: 84px;
&::after {
content: var(--lgs-c4a-val, "");
position: absolute;
inset-inline-end: 0;
top: calc(8px + var(--lgs-slider) / 2);
translate: 0 -50%;
width: 72px;
text-align: end;
font: 600 var(--lgs-fs-callout)/1.3 var(--lgs-font);
font-variant-numeric: tabular-nums;
color: var(--lgs-text-2);
pointer-events: none;
}
}
/* value text and the value box */
%{*SliderControlPanelGroup>DescriptionValue}:not([data-lgs-noprim] *) {
font: 600 var(--lgs-fs-callout)/1.3 var(--lgs-font);
font-variant-numeric: tabular-nums;
color: var(--lgs-text-2);
}
%{*SliderControlPanelGroup>EditableValue}:not([data-lgs-noprim] *) {
box-sizing: border-box;
min-height: var(--lgs-btn);
min-width: 96px;
padding: 0 20px;
border: 0;
border-radius: var(--lgs-r-capsule);
font: 600 var(--lgs-fs-callout)/1.3 var(--lgs-font);
font-variant-numeric: tabular-nums;
background: var(--lgs-fill-thick) !important;
box-shadow: var(--lgs-recessed-shadow) !important;
color: var(--lgs-text-1) !important;
caret-color: var(--lgs-blue);
}
&:not(.lgs-input-laser) %{*SliderControlPanelGroup>EditableValue}.gpfocus:not([data-lgs-noprim] *),
& %{*SliderControlPanelGroup>EditableValue}:focus:not([data-lgs-noprim] *) {
box-shadow: var(--lgs-recessed-shadow), var(--lgs-field-focus) !important;
}
/* the inert glyph before a slider (speaker, sun): drawn inside the fill's
leading end; the difference blend makes it dark on the white fill and
light on the bare track, so level 0 still reads (C-D13). Never a target */
%{*GamepadDialogContent>FieldChildrenWithIcon}:has(%{*SliderControlAndNotches>SliderControlPanelGroup}):not([data-lgs-noprim] *) > %{*GamepadDialogContent>FieldIcon}:not(%{ClickableSliderIcon}, :has(%{ClickableSliderIcon})) {
position: relative;
z-index: 1;
box-sizing: border-box;
width: var(--lgs-slider);
min-width: var(--lgs-slider);
margin-inline-end: calc(-1 * var(--lgs-slider));
padding: 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
mix-blend-mode: difference;
pointer-events: none;
& svg { width: 26px; height: 26px; }
}
/* the mute glyph (Steam's clickable slider icon, not on this build's pages,
P-C9) is never inert: its own 64 px round zone before the track, raised,
lit like a button (CTL C-D13). T2's data-lgs-mute="muted" turns the zone
white with the dimmed level beside it */
%{*GamepadDialogContent>FieldChildrenWithIcon}:not([data-lgs-noprim] *) > %{*GamepadDialogContent>FieldIcon}:is(%{ClickableSliderIcon}, :has(%{ClickableSliderIcon})) {
position: relative;
box-sizing: border-box;
width: var(--lgs-slider);
height: var(--lgs-slider);
min-width: var(--lgs-slider);
margin-inline-end: 8px;
padding: 0;
display: grid;
place-items: center;
border-radius: 50%;
background: var(--lgs-raised-bg);
box-shadow: var(--lgs-raised-shadow);
color: var(--lgs-text-1);
isolation: isolate;
--lgs-ill: raised;
--lgs-ill-z: -1;
& svg { width: 26px; height: 26px; }
}
[data-lgs-mute="muted"] > %{*GamepadDialogContent>FieldIcon} {
background: var(--lgs-selected-fill) !important;
color: var(--lgs-text-on-selected) !important;
--lgs-ill: white;
}
[data-lgs-mute="muted"] %{*SliderControlPanelGroup>SliderTrack}::after {
background: rgb(255 255 255 / .24);
}
/* ======================================================================
6. Pop-up buttons: %{DropDownControlButton} (CTL §8.1). A 60 px capsule
(rich two-line: 76, radius 30) with the value at 22 px Medium and Steam's
caret; white .94 with dark text while its menu is open. It is also a
DialogButton, so section 1 gives it the states.
====================================================================== */
button%{DropDownControlButton}.DialogButton:not(%{SortingDropDownControlButton}):not([data-lgs-noprim] *) {
padding: 0 20px 0 24px;
font: var(--lgs-type-callout);
--lgs-c4a-side: 250;
& %{*GamepadDialogContent>DropDownControlButtonContents} {
gap: 14px;
align-items: center;
}
& %{*GamepadDialogContent>DropDownControlButtonContents} > svg {
width: 22px;
height: 22px;
color: var(--lgs-text-2);
}
& %{DropDownLabelTitle} {
font: var(--lgs-type-callout);
color: var(--lgs-text-1);
}
& :is(%{DropDownLabelDescription}, %{DropDownLabelUpperDescription}) {
font: var(--lgs-type-footnote);
color: var(--lgs-text-2);
}
}
/* rich (two-line) pop-ups: 76 px, radius 30; the title first, then Steam's
upper description and description joined on one line ("UTC −04:00 ·
New York", CTL §8.1) by `order` on Steam's own text nodes */
button%{DropDownControlButton}.DialogButton:has(%{DropDownLabelContainer}):not([data-lgs-noprim] *) {
min-height: 76px;
border-radius: 30px;
& %{DropDownLabelTextColumn} {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-items: baseline;
row-gap: 2px;
}
& %{DropDownLabelTitle} { order: 0; flex: 1 0 100%; }
& :is(%{DropDownLabelUpperDescription}, %{DropDownLabelDescription}) { margin-inline: 0; padding-inline: 0; text-indent: 0; }
& %{DropDownLabelUpperDescription} { order: 1; }
& %{DropDownLabelUpperDescription}:not(:empty)::after { content: "\00a0\00b7\00a0"; }
& %{DropDownLabelDescription} { order: 2; }
}
/* Game Recording's quality options: Steam dims both parts for its white focus */
button%{DropDownControlButton}.DialogButton %{BitrateOption} {
color: var(--lgs-text-2) !important;
& %{BitrateSetting>Emphasis} { color: var(--lgs-text-1) !important; }
}
/* open: the source of an open menu is white (CTL §8.1; tokens §3.3) */
button%{DropDownControlButton}.DialogButton[aria-expanded="true"]:not([data-lgs-noprim] *) {
background: var(--lgs-selected-fill) !important;
box-shadow: 0 2px 6px rgb(0 0 0 / .22) !important;
color: var(--lgs-text-on-selected) !important;
--lgs-ill: white;
& %{*GamepadDialogContent>DropDownControlButtonContents} > svg { color: var(--lgs-text-on-selected-2); }
& %{DropDownLabelTitle} { color: var(--lgs-text-on-selected); }
& :is(%{DropDownLabelDescription}, %{DropDownLabelUpperDescription}) { color: var(--lgs-text-on-selected-2); }
}
/* ======================================================================
7. Segmented controls: %{Group} (Shared_Radio_Group) (CTL §8.3).
A 64 px recessed track, padding 2; segments contiguous, 60 tall, ≥ 140
wide, one-line 22 px Semibold labels; the visible fill inset 2 px; the
selected segment a white .94 pill with a dark label. Focus is P4's
FocusRing plate (radios) plus the light layer; Steam's dividers go.
====================================================================== */
%{Group}:not(%{CenteredPill}):not([data-lgs-noprim] *) {
box-sizing: border-box;
min-height: var(--lgs-seg);
padding: 2px;
gap: 0;
border-radius: var(--lgs-r-capsule);
background: var(--lgs-fill-thick);
box-shadow: var(--lgs-recessed-shadow), inset 0 -2px 3px -1px rgb(255 255 255 / .10);
color: var(--lgs-text-1);
& > %{Group>Button} {
box-sizing: border-box;
min-height: 60px;
min-width: var(--lgs-seg-min-w);
margin: 0;
padding: 2px 20px;
background-clip: content-box;
border-radius: 30px;
font: 600 var(--lgs-fs-callout)/1.2 var(--lgs-font);
white-space: nowrap;
text-shadow: none;
text-transform: none;
color: var(--lgs-text-1);
display: flex;
align-items: center;
justify-content: center;
isolation: isolate;
--lgs-ill: raised;
--lgs-ill-z: -1;
--lgs-ill-inset: 2px;
--lgs-ill-r: 28px;
transition: background-color var(--lgs-d-fade) var(--lgs-ease-b0), color var(--lgs-d-swap) linear;
/* Steam's 1 px dividers */
&::before { background: transparent !important; }
}
/* Steam's hover fill: back to rest (the light layer carries hover) */
& > %{Group>Button}:not(%{Group>Active}):is(:hover, .gpfocus) {
background-color: transparent !important;
}
& > %{Group>Button}%{Group>Active} {
background: var(--lgs-selected-fill) !important;
background-clip: content-box !important;
color: var(--lgs-text-on-selected) !important;
--lgs-ill: white;
}
}
%{Group}%{CenteredPill}:not([data-lgs-noprim] *) > %{Group>Button}%{Group>Active} {
background: var(--lgs-selected-fill);
color: var(--lgs-text-on-selected);
}
/* T2 (wp.c4a): a decorative div.lgs-pill takes over the selected fill and
travels on `snappy`, clear glass for its first 180 ms (CTL §8.3). Strips
of other areas registered with rt.controls.pill(el) get the same pill */
[data-lgs-pill]:not([data-lgs-noprim] *) {
position: relative;
}
%{Group}[data-lgs-pill]:not([data-lgs-noprim] *) {
& > %{Group>Button} { position: relative; z-index: 1; }
& > %{Group>Button}%{Group>Active} {
background: transparent !important;
box-shadow: none !important;
}
}
& .lgs-pill {
position: absolute;
left: 0;
top: 0;
z-index: 0;
pointer-events: none;
border-radius: 28px;
background: var(--lgs-selected-fill);
box-shadow: 0 2px 6px rgb(0 0 0 / .22);
transition:
translate var(--lgs-d-snappy) var(--lgs-ease-b15),
width var(--lgs-d-snappy) var(--lgs-ease-b15),
scale var(--lgs-d-interactive) var(--lgs-ease-b15),
background-color var(--lgs-d-interactive) var(--lgs-ease-b0),
box-shadow var(--lgs-d-interactive) var(--lgs-ease-b0);
&.lgs-pill-still { transition: none; }
/* lifted while it travels: a bead of clear glass */
&.lgs-pill-lift {
scale: 1.06 1.12;
background: linear-gradient(180deg, rgb(255 255 255 / .42), rgb(255 255 255 / .18));
backdrop-filter: blur(1.5px) saturate(1.8);
box-shadow: 0 4px 12px rgb(0 0 0 / .28), inset 0 1.5px 1.5px -1px rgb(255 255 255 / .55);
}
}
/* Reduce Motion: the pill does not travel or lift; it cross-fades */
@media (prefers-reduced-motion: reduce) {
& .lgs-pill {
transition: none;
&.lgs-pill-lift { scale: none; backdrop-filter: none; background: var(--lgs-selected-fill); animation: lgs-fade-in var(--lgs-d-reduce, 180ms) linear; }
}
}
/* ======================================================================
8. Text fields (CTL §9). A 64 px recessed capsule (padding 0 12 0 24),
Body 24 Medium, upright placeholder .55, a blue caret. Focus is the one
ring in the system (--lgs-field-focus, VP P-17): on gamepad focus, and in
laser mode while the field has DOM focus (the caret's field), never on
hover. Steam's solid white focused input is overridden.
====================================================================== */
input%{*GamepadDialogContent>BasicTextInput}:not([data-lgs-noprim] *),
textarea.DialogTextInputBase:not([data-lgs-noprim] *) {
box-sizing: border-box;
min-height: var(--lgs-field);
padding: 0 12px 0 24px;
border: 0;
border-radius: calc(var(--lgs-field) / 2);
font: var(--lgs-type-body);
color: var(--lgs-text-1) !important;
caret-color: var(--lgs-blue);
background: var(--lgs-fill-thick) !important;
box-shadow: var(--lgs-recessed-shadow), inset 0 -2px 3px -1px rgb(255 255 255 / .10) !important;
transition: box-shadow var(--lgs-d-fade) var(--lgs-ease-b0), background-color var(--lgs-d-fade) var(--lgs-ease-b0);
}
textarea.DialogTextInputBase:not([data-lgs-noprim] *) {
padding: 18px 24px;
border-radius: 30px;
line-height: 1.3;
}
/* a bare textarea placed in a row (Steam's zoo, multi-line notes): the
multi-line field */
%{*GamepadDialogContent>FieldChildrenInner} > textarea:not([class*="DialogTextInputBase"]):not([data-lgs-noprim] *) {
box-sizing: border-box;
min-height: var(--lgs-field);
padding: 18px 24px;
border: 0;
border-radius: 30px;
font: var(--lgs-type-body);
line-height: 1.3;
color: var(--lgs-text-1);
caret-color: var(--lgs-blue);
background: var(--lgs-fill-thick);
box-shadow: var(--lgs-recessed-shadow), inset 0 -2px 3px -1px rgb(255 255 255 / .10);
resize: none;
outline: none;
&:focus { box-shadow: var(--lgs-recessed-shadow), var(--lgs-field-focus); }
}
/* a free-standing field (not in a row) keeps 8 px clear above and below,
so its 80 px hit box stays its own (P-08) */
.DialogInput_Wrapper:not(%{*GamepadDialogContent>Field} *):not([data-lgs-noprim] *) {
padding-block: 8px;
}
:is(input%{*GamepadDialogContent>BasicTextInput}, input.DialogTextInputBase, textarea.DialogTextInputBase)::placeholder {
color: rgb(255 255 255 / .55);
font-style: normal;
}
/* laser hover: a little light on the well */
&:not(.lgs-input-pad) :is(input%{*GamepadDialogContent>BasicTextInput}, textarea.DialogTextInputBase):hover:not(:focus):not([data-lgs-noprim] *) {
background: linear-gradient(rgb(255 255 255 / var(--lgs-hover-add)), rgb(255 255 255 / var(--lgs-hover-add))), var(--lgs-fill-thick) !important;
}
/* focus: the ring, and only .08 more light so typed text keeps its contrast */
&:not(.lgs-input-laser) :is(input%{*GamepadDialogContent>BasicTextInput}, textarea.DialogTextInputBase).gpfocus:not([data-lgs-noprim] *),
& :is(input%{*GamepadDialogContent>BasicTextInput}, textarea.DialogTextInputBase):focus:not([data-lgs-noprim] *) {
background: linear-gradient(rgb(255 255 255 / .08), rgb(255 255 255 / .08)), var(--lgs-fill-thick) !important;
box-shadow: var(--lgs-recessed-shadow), var(--lgs-field-focus) !important;
color: var(--lgs-text-1) !important;
}
input%{*GamepadDialogContent>BasicTextInput}:is(:disabled, :read-only, .disabled):not([data-lgs-noprim] *) {
color: rgb(255 255 255 / var(--lgs-dis-content)) !important;
}
&:not(.lgs-input-laser) %{*GamepadDialogContent_InnerWidth>GamepadDialogContent} ._DialogInputContainer.gpfocus,
& %{*GamepadDialogContent_InnerWidth>GamepadDialogContent} ._DialogInputContainer.Active {
background-color: var(--lgs-fill-thick) !important;
box-shadow: var(--lgs-recessed-shadow), var(--lgs-field-focus) !important;
color: var(--lgs-text-1) !important;
}
/* labels above a field: 20 px Semibold, title case */
.DialogLabel:not([data-lgs-noprim] *) {
font-size: var(--lgs-fs-subhead);
font-weight: 600;
line-height: 1.3;
text-transform: none;
letter-spacing: 0;
color: var(--lgs-text-2);
}
/* ======================================================================
9. Progress bars (ProgressBar: downloads, updates, zoo): the slider's
language, a recessed capsule with a white fill. Steam's indeterminate
sweep is left as is.
====================================================================== */
/* doubled class: beats Steam's focused-row variant without naming .gpfocus */
%{ProgressBarIndeterminate>ProgressBar}%{ProgressBarIndeterminate>ProgressBar} {
background: var(--lgs-fill-thick);
box-shadow: var(--lgs-recessed-shadow);
border-radius: var(--lgs-r-capsule);
/* the fill paints inline "background: var(--gpColor-Blue, #1a9fff)" */
--gpColor-Blue: rgb(255 255 255 / .92);
}
%{ProgressBarIndeterminate>ProgressBar} %{Indeterminate>Percent} {
border-radius: var(--lgs-r-capsule);
}
%{ProgressBarFieldStatus} %{ProgressBar>TextContainer} %{OperationText} {
color: var(--lgs-text-2);
}
/* ======================================================================
10. Labels of C4a's pop-ups inside C1c's menus, and dialog sub-headers.
The menu slab, rows, scrim and sheets are C1c's (theme/22-presentations.css;
Phase 1's menu and modal sections were removed here on REQ C1c->C4a,
PLAN §6). Kept: a rich pop-up's two-line label follows its menu row's
colour (dark on the white selected row), and Steam's in-page sub-headers.
====================================================================== */
%{*BasicContextMenuModal>contextMenuItem} %{DropDownLabelTitle} {
color: inherit;
}
%{*BasicContextMenuModal>contextMenuItem} :is(%{DropDownLabelDescription}, %{DropDownLabelUpperDescription}) {
color: inherit;
opacity: .74;
}
:is(.DialogSubHeader, .DialogControlsSectionHeader):not(%{*GamepadDialogContent>ModalPosition} *) {
color: var(--lgs-text-2);
border-color: transparent;
}
/* ======================================================================
11. Markup classes for our own elements (T2/T3 markup; the interface in
docs/phase2/wp/C4a.md Status). The same looks as Steam's primitives:
.lgs-btn + .lgs-btn-circle | .lgs-btn-capsule; sizes .lgs-btn-mini (38),
-sm (44), default 60, -lg (70), -xl (86); roles .lgs-btn-primary,
-play, -danger (red label), -danger-fill (red confirm), -plain
(borderless in ornaments and menus); [aria-pressed="true"] = selected
.lgs-switch[aria-checked] (with a child .lgs-switch-knob)
.lgs-check[aria-checked] (svg check inside)
.lgs-seg > [aria-pressed]
.lgs-field, .lgs-platter > .lgs-row (+ .lgs-row-nav), .lgs-sep
Elements we create are buttons or carry role and tabindex themselves;
states come from P4's numbers like Steam's (:hover, .gpfocus, :active,
.lgs-pressed, :disabled / [aria-disabled]).
====================================================================== */
& .lgs-btn {
position: relative;
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 10px;
height: var(--lgs-btn);
min-width: var(--lgs-btn);
padding: 0 var(--lgs-btn-pad);
border: 0;
border-radius: var(--lgs-r-capsule);
font: var(--lgs-type-headline);
letter-spacing: var(--lgs-track-headline);
white-space: nowrap;
color: var(--lgs-text-1);
background: var(--lgs-raised-bg);
box-shadow: var(--lgs-raised-shadow);
cursor: pointer;
isolation: isolate;
--lgs-ill: raised;
--lgs-ill-z: -1;
--lgs-edge: control;
--lgs-c4a-side: 160;
scale: calc(1 + (min(var(--lgs-press-max), 1 + var(--lgs-press-grow) / var(--lgs-c4a-side)) - 1) * var(--lgs-press));
transition: scale var(--lgs-d-snappy) var(--lgs-ease-b15), background-color var(--lgs-d-fade) var(--lgs-ease-b0), color var(--lgs-d-fade) var(--lgs-ease-b0);
& svg { width: var(--lgs-symbol); height: var(--lgs-symbol); flex: none; }
&:is(:active, .lgs-pressed) {
transition: scale var(--lgs-d-interactive) var(--lgs-ease-b15), background-color var(--lgs-d-fade) var(--lgs-ease-b0), color var(--lgs-d-fade) var(--lgs-ease-b0);
}
&.lgs-btn-circle { width: var(--lgs-btn); padding: 0; border-radius: 50%; --lgs-c4a-side: 60; }
&.lgs-btn-mini { height: var(--lgs-btn-mini); min-width: var(--lgs-btn-mini); --lgs-c4a-side: 38; & svg { width: 18px; height: 18px; } }
&.lgs-btn-mini.lgs-btn-circle { width: var(--lgs-btn-mini); }
&.lgs-btn-sm { height: var(--lgs-btn-sm); min-width: var(--lgs-btn-sm); --lgs-c4a-side: 44; & svg { width: 20px; height: 20px; } }
&.lgs-btn-sm.lgs-btn-circle { width: var(--lgs-btn-sm); }
/* mini and small circles stand alone: an 80 x 80 hit on ::before (E-MINI),
so they carry no arc */
&:is(.lgs-btn-mini, .lgs-btn-sm).lgs-btn-circle { --lgs-edge: none; }
&.lgs-btn-mini.lgs-btn-circle::before { content: ""; position: absolute; inset: calc((var(--lgs-btn-mini) - var(--lgs-hit)) / 2); border-radius: 50%; }
&.lgs-btn-sm.lgs-btn-circle::before { content: ""; position: absolute; inset: calc((var(--lgs-btn-sm) - var(--lgs-hit)) / 2); border-radius: 50%; }
&.lgs-btn-lg { height: var(--lgs-btn-lg); min-width: var(--lgs-btn-lg); padding: 0 28px; font-size: 26px; --lgs-c4a-side: 180; & svg { width: 30px; height: 30px; } }
&.lgs-btn-lg.lgs-btn-circle { width: var(--lgs-btn-lg); padding: 0; --lgs-c4a-side: 70; }
&.lgs-btn-xl { height: var(--lgs-btn-xl); min-width: var(--lgs-btn-xl); padding: 0 34px; font-size: 28px; --lgs-c4a-side: 200; & svg { width: 36px; height: 36px; } }
&.lgs-btn-xl.lgs-btn-circle { width: var(--lgs-btn-xl); padding: 0; --lgs-c4a-side: 86; }
&.lgs-btn-primary { background: linear-gradient(180deg, rgb(255 255 255 / .16), rgb(255 255 255 / 0) 55%), var(--lgs-tint-primary); color: var(--lgs-text-on-tint); --lgs-ill: white; }
&.lgs-btn-play { background: linear-gradient(180deg, rgb(255 255 255 / .16), rgb(255 255 255 / 0) 55%), var(--lgs-tint-play); color: var(--lgs-text-on-color); --lgs-ill: white; }
&.lgs-btn-danger-fill { background: linear-gradient(180deg, rgb(255 255 255 / .16), rgb(255 255 255 / 0) 55%), var(--lgs-fill-danger); color: var(--lgs-text-on-color); --lgs-ill: white; }
&:is(.lgs-btn-primary, .lgs-btn-play, .lgs-btn-danger-fill) { box-shadow: inset 0 1.5px 1.5px -1px rgb(255 255 255 / .35), 0 3px 8px rgb(0 0 0 / .22); }
&.lgs-btn-danger { color: var(--lgs-text-danger); font-weight: 600; }
&.lgs-btn-plain { background: transparent; box-shadow: none; --lgs-edge: none; }
&[aria-pressed="true"] { background: var(--lgs-selected-fill); color: var(--lgs-text-on-selected); box-shadow: 0 2px 6px rgb(0 0 0 / .22); --lgs-ill: white; }
&:is(:disabled, [aria-disabled="true"]) { background: rgb(255 255 255 / .04); box-shadow: none; color: rgb(255 255 255 / var(--lgs-dis-content)); cursor: default; }
}
&:not(.lgs-input-laser) .lgs-btn.lgs-btn-danger.gpfocus {
background: var(--lgs-fill-danger);
color: var(--lgs-text-on-color);
--lgs-ill: white;
}
& .lgs-switch {
position: relative;
box-sizing: border-box;
flex: none;
width: var(--lgs-switch-w);
height: var(--lgs-switch-h);
border: 0;
padding: 0;
border-radius: calc(var(--lgs-switch-h) / 2);
background: rgb(255 255 255 / .16);
box-shadow: inset 0 1px 3px rgb(0 0 0 / .22);
cursor: pointer;
--lgs-ill: raised;
transition: background-color var(--lgs-d-interactive) linear var(--lgs-d-interactive);
/* the 88 x 80 hit (E-SWITCH) */
&::before { content: ""; position: absolute; inset: -20px -11px; border-radius: 30px; }
& > .lgs-switch-knob {
position: absolute;
top: 3px;
inset-inline-start: 3px;
width: calc(var(--lgs-switch-h) - 6px);
height: calc(var(--lgs-switch-h) - 6px);
border-radius: 50%;
background: #fff;
box-shadow: 0 2px 5px rgb(0 0 0 / .30);
z-index: 1;
transition: translate var(--lgs-d-snappy) var(--lgs-ease-b15), box-shadow var(--lgs-d-fade) var(--lgs-ease-b0);
}
&[aria-checked="true"] {
background: linear-gradient(180deg, rgb(255 255 255 / .14), rgb(255 255 255 / 0) 60%), var(--lgs-toggle-on);
--lgs-ill: white;
& > .lgs-switch-knob { translate: calc(var(--lgs-switch-w) - var(--lgs-switch-h)) 0; }
&:dir(rtl) > .lgs-switch-knob { translate: calc(var(--lgs-switch-h) - var(--lgs-switch-w)) 0; }
}
&:is(:active, .lgs-knob-lift) > .lgs-switch-knob { animation: lgs-knob-lift var(--lgs-d-snappy) linear; }
&:is(:disabled, [aria-disabled="true"]) { background: rgb(255 255 255 / .07); & > .lgs-switch-knob { background: rgb(255 255 255 / .42); box-shadow: none; } }
}
&:not(.lgs-input-laser) .lgs-switch.gpfocus > .lgs-switch-knob {
box-shadow: 0 2px 5px rgb(0 0 0 / .30), 0 0 14px 3px rgb(255 255 255 / .42);
}
& .lgs-check {
position: relative;
box-sizing: border-box;
flex: none;
display: inline-grid;
place-items: center;
width: var(--lgs-check);
height: var(--lgs-check);
margin: calc((var(--lgs-hit) - var(--lgs-check)) / 2);
padding: 0;
border: 0;
border-radius: 50%;
background: rgb(0 0 0 / .22);
box-shadow: inset 0 0 0 2.5px rgb(255 255 255 / .34), inset 0 2px 4px rgb(0 0 0 / .30);
cursor: pointer;
isolation: isolate;
--lgs-ill: raised;
--lgs-ill-z: -1;
--lgs-hover-spot: .07;
&::before { content: ""; position: absolute; inset: calc((var(--lgs-check) - var(--lgs-hit)) / 2); border-radius: 50%; }
& svg { width: 24px; height: 24px; color: #fff; opacity: 0; }
&[aria-checked="true"] {
background: linear-gradient(180deg, rgb(255 255 255 / .16), rgb(255 255 255 / 0) 60%), var(--lgs-check-on);
box-shadow: inset 0 1.5px 1.5px -1px rgb(255 255 255 / .35), 0 1px 2px rgb(0 0 0 / .18);
--lgs-ill: white;
--lgs-white-glow-spread: 12px;
& svg { opacity: 1; }
}
}
& .lgs-seg {
box-sizing: border-box;
display: inline-flex;
min-height: var(--lgs-seg);
padding: 2px;
border-radius: var(--lgs-r-capsule);
background: var(--lgs-fill-thick);
box-shadow: var(--lgs-recessed-shadow), inset 0 -2px 3px -1px rgb(255 255 255 / .10);
& > [aria-pressed] {
position: relative;
box-sizing: border-box;
flex: 1 0 auto;
min-width: var(--lgs-seg-min-w);
min-height: 60px;
padding: 2px 20px;
border: 0;
border-radius: 30px;
background: transparent;
background-clip: content-box;
font: 600 var(--lgs-fs-callout)/1.2 var(--lgs-font);
white-space: nowrap;
color: var(--lgs-text-1);
cursor: pointer;
isolation: isolate;
--lgs-ill: raised;
--lgs-ill-z: -1;
--lgs-ill-inset: 2px;
--lgs-ill-r: 28px;
transition: color var(--lgs-d-swap) linear;
}
& > [aria-pressed="true"] {
background: var(--lgs-selected-fill);
background-clip: content-box;
color: var(--lgs-text-on-selected);
--lgs-ill: white;
}
}
& .lgs-field {
box-sizing: border-box;
display: flex;
align-items: center;
gap: 14px;
min-height: var(--lgs-field);
padding: 0 12px 0 24px;
border: 0;
border-radius: calc(var(--lgs-field) / 2);
font: var(--lgs-type-body);
color: var(--lgs-text-1);
caret-color: var(--lgs-blue);
background: var(--lgs-fill-thick);
box-shadow: var(--lgs-recessed-shadow), inset 0 -2px 3px -1px rgb(255 255 255 / .10);
transition: box-shadow var(--lgs-d-fade) var(--lgs-ease-b0);
&::placeholder { color: rgb(255 255 255 / .55); font-style: normal; }
&:focus, &:focus-within { box-shadow: var(--lgs-recessed-shadow), var(--lgs-field-focus); }
}
& .lgs-platter {
border-radius: var(--lgs-r-platter);
background: var(--lgs-fill-regular);
}
& .lgs-row {
position: relative;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 18px;
min-height: var(--lgs-row-platter);
padding: 0 var(--lgs-sep-inset);
font: var(--lgs-type-body);
color: var(--lgs-text-1);
isolation: isolate;
--lgs-ill: row;
--lgs-ill-z: -1;
&.lgs-row-nav { min-height: var(--lgs-row); --lgs-ill: nav; }
&[aria-current="true"], &[aria-selected="true"].lgs-row-nav { background: var(--lgs-fill-nav); box-shadow: var(--lgs-sel-arc); font-weight: var(--lgs-nav-weight); border-radius: var(--lgs-r-row); }
}
& .lgs-sep {
height: 2px;
margin-inline: var(--lgs-sep-inset);
border-radius: 1px;
background: var(--lgs-separator);
}
/* ======================================================================
12. High Contrast (CTL §4.6; prefers-contrast: more). Opaque fills;
P4's hooks draw the one allowed 2 px stroke for focus.
====================================================================== */
@media (prefers-contrast: more) {
button.DialogButton%{*GamepadDialogContent>Button}:not([data-lgs-noprim] *),
button.DialogButton%{*GamepadDialogContent>Button}:not([data-lgs-noprim] *):is(.gpfocus, :hover, :active, :focus, .Primary, .Destructive) {
background: rgb(255 255 255 / .22) !important;
}
%{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:not(%{*GamepadDialogContent>Classic}):not([data-lgs-noprim] *),
%{*GamepadDialogContent>Field}%{*GamepadDialogContent>Background}:not(%{*GamepadDialogContent>Classic})%{*GamepadDialogContent>HighlightOnFocus}:is(.gpfocus, .gpfocuswithin):not([data-lgs-noprim] *) {
background: rgb(11 13 16 / .94) !important;
}
:is(input%{*GamepadDialogContent>BasicTextInput}, textarea.DialogTextInputBase, %{*SliderControlPanelGroup>EditableValue}):not([data-lgs-noprim] *),
%{Group}:not(%{CenteredPill}):not([data-lgs-noprim] *) {
background: rgb(0 0 0 / .55) !important;
}
%{*SliderControlPanelGroup>SliderTrack}:not([data-lgs-noprim] *) {
background: rgb(0 0 0 / .55);
box-shadow: inset 0 0 0 2px rgb(255 255 255 / calc(.70 * var(--lgs-focus)));
}
%{*GamepadDialogContent>Toggle}:not([data-lgs-noprim] *) > %{*GamepadDialogContent>ToggleRail} {
background: rgb(255 255 255 / .30);
}
}