A CSS-like language for 3D scenes, compiled to a GPU shader.
Getting started
Why GSS
Front-end developers already describe how things look with selectors, properties, the cascade and @keyframes. To go 3D, they meet GLSL, the language of the GPU: powerful, but written in distance functions, vectors and math, far from the way they think about design.
scene.gss
one shader
the GPU draws it
GSS bridges that gap. If you can write CSS, you can already read GSS:
Selectors
sphere, .glass, #ball, :hover, :has(), the cascade and !important
Properties
color, translate, opacity, filter, in deg, s and %
Motion
@keyframes, animation, transition, cubic-bezier()
Values
calc(), var(), @property, oklch(), color-mix()
Media queries
@media, prefers-color-scheme, light-dark()
GSS does not replace GLSL. It is a way in: a familiar syntax to start creating in 3D today, and a readable path toward the shader underneath, for those who want to go further.
Your first scene
A glass ball that floats above the floor. Press Try it to edit it live.
@scene lists the shapes. A rule styles them, like CSS: #ball lifts the sphere, colors it and turns it into glass. @keyframes makes it float.
Next, put the scene on a page with Embedding a scene, or read the reference: each page describes one at-rule, selector, property or function, with examples to try.
Installation
Embedding a scene
A GSS scene runs on any web page, in three ways. All three use the package gss-lang@0.0.7.
<gss-scene src="scene.gss"></gss-scene>
GSS is open source, under the Apache-2.0 license: its code is on GitHub, where you can report a bug. Questions, ideas, a scene to show: say hi on X, @GSS_lang.
import{ mount }from"gss-lang";const scene =mount(canvas,"@scene { sphere; }");
scene.update(otherSource);
scene.destroy();
A build step
gss-lang@0.0.7 · compiled with Vite
// vite.config.tsimport gss from"gss-lang/vite";exportdefault{ plugins:[gss()]};// main.ts: the compiler stays out of the pageimport{ mount }from"gss-lang/runtime";import logo from"./logo.gss";mount(canvas, logo);
In the page
Camera
A drag turns it, the wheel zooms, :hover works. controls="none" (or controls: false) keeps only :hover, and the page scrolls over the scene. The GSS can choose too: see controls.
Off screen
The scene starts when it comes into view, and sleeps when it leaves it.
Reduced motion
The scene stands still under prefers-reduced-motion.
A still scene
It draws nothing between two changes (the mouse, a variable, an image that arrives): the GPU rests.
Images
A path starts from the .gss file, like url() in a stylesheet.
Background
The canvas is not transparent: give the scene the background of your page.
Once <gss-scene> fires load, its scene property gives the scene, to pause() it, play() it or set its variables.
Package (npm)
Install gss-lang@0.0.7 in your application. The package holds the compiler, the renderer and the TypeScript declarations.
npm install gss-lang
mount(canvas, source)
a canvas of the page
npm install gss-lang@0.0.7
import{ mount }from"gss-lang";const scene =mount(canvas,"@scene { sphere; }");
scene.update(otherSource);
scene.destroy();
mount() takes a canvas that is already in the page, and returns the scene: scene.update(source) replaces it, scene.destroy() removes it.
A variable the scene registers with @property can be set from the page without compiling again, like a CSS custom property: scene.setProperty("--lift", "2"). See Set variables from JavaScript.
Vite plugin
The Vite plugin comes with the package. It compiles the .gss imports at build time, so the browser loads only the renderer and the compiled scene.
scene.gss
vite build
the scene, compiled
npm install gss-lang@0.0.7npm install --save-dev vite
// vite.config.tsimport gss from"gss-lang/vite";exportdefault{ plugins:[gss()]};// main.ts: the compiler stays out of the pageimport{ mount }from"gss-lang/runtime";import logo from"./logo.gss";mount(canvas, logo);
With TypeScript, add "gss-lang/client" to compilerOptions.types in tsconfig.json: a .gss import then has a type. Draw the compiled scene with mount() from gss-lang/runtime.
CDN (no build step)
Load the browser module of v0.0.7 from jsDelivr: no install, no bundler. The version is pinned, so an update never changes your scene unexpectedly.
poster="cover.jpg" shows an image until the scene draws, like the poster of a <video>.
No graphics card
<gss-scene> shows its poster and a button, and draws the scene only when the reader asks. With mount(), softwareRendering(), from gss-lang or gss-lang/runtime, returns true on such a computer, to do the same.
A weak graphics card
The scene starts at a density of 0.5, then climbs to its dpr while the frames keep up.
Still too slow
The scene stops: <gss-scene> shows a button that draws it anyway, and mount() fires a gss-too-heavy event on the canvas, where scene.play() draws it anyway.
adaptDpr: false
mount(canvas, scene, { adaptDpr: false }) keeps the dpr as written and never stops, for a capture or a benchmark.
Set variables from JavaScript
A variable the scene registers with @property can be set by the page at any moment, without compiling the scene again, the way element.style.setProperty() sets a CSS custom property. Drive a scene from a slider, the scroll position or your data. Move the slider:
scene.setProperty("--lift", "1.5");
The scene registers --lift and reads it in translate and in its color; the page sets it when the slider moves:
const ball = document.querySelector("#ball");const lift = document.querySelector("#lift");// ball.scene is null until the element fires "load"const apply =()=> ball.scene?.setProperty("--lift", lift.value);
ball.addEventListener("load", apply);
lift.addEventListener("input", apply);
Three methods
Named like those of element.style:
setProperty()
Sets the variable: scene.setProperty("--lift", "1.5"). The value is a string written like CSS (below); a "<number>" also takes a number.
getPropertyValue()
Reads it back, written like a computed CSS value: "90deg", "#ff5a36". "" for a variable the scene does not register.
removeProperty()
Returns to the start value: the one scene { --lift: 2; } gives, or else initial-value.
Unlike CSS, which ignores a value it cannot read, setProperty() throws an error for a value that does not match the syntax of the variable, and for a variable the scene does not register. Only a registered variable changes while the scene runs: an unregistered one (--size: 2) is computed once, when the scene compiles.
Values
The value is written by the syntax of the variable:
"4px", in px only: the radius of blur() and bloom()
The new value is drawn at the next frame, wherever the variable goes: transforms, colors, sizes, materials, lights, fog, filters, alone or inside calc() and the color functions. A value out of its range is kept in it: a size never goes below 0. @property lists what a variable can change, and what it cannot: what the scene builds when it compiles, like the copies of * n.
From mount()
mount() returns the scene at once, mountAsync() a promise of it. mount() from gss-lang/runtime, for a scene compiled by the Vite plugin, has the same methods.
import{ mount, mountAsync }from"gss-lang";const scene =mount(canvas, source);
scene.setProperty("--lift","1.5");
scene.getPropertyValue("--lift");// "1.5"
scene.removeProperty("--lift");// back to its start value// mountAsync() gives the same scene, once its backend is readyconst other =awaitmountAsync(canvas2, source,{ backend:"webgpu"});
other.setProperty("--lift",2);
A value set this way stays when scene.update(source) gives another scene, as long as the new scene registers the variable with the same syntax.
In the playground
With @property-panel { display: open; } in the scene, the playground, and Try it under the examples of these docs, show a panel over the render with a control for each registered variable: a slider and its number, or a color picker for a "<color>". It calls setProperty() as it moves, and its reset button calls removeProperty(). A page that embeds the scene shows no panel. See @property-panel.
From <gss-scene>
The scene property of the element is the scene, with the same methods. It is null until the element fires "load": set your values in a "load" listener, as the demo does.
The element fires "load" again each time it starts a new scene: a new src, controls or backend, or the element moved in the page. A new scene starts from its start values, so the listener sets yours again.
Editor support
The GSS extension colors and formats .gss files in VS Code and in the editors built on it, like Cursor, VSCodium and Windsurf. It is not needed to run a scene.
For VS Code and CursorHighlighting, formatting and the .gss file icon.VS Code Marketplace · Open VSX for Cursor, VSCodium and Windsurf
Highlighting
Selectors, properties, values, colors, numbers and units, the at-rules, nested rules and &.
Formatting
Format Document, or saving the file, writes the code in the style of these docs: one shape per line in @scene, one declaration per line, two-space indentation, comments kept.
File icon
.gss files get the GSS icon in the explorer.
Install
In VS Code, open the Extensions view (Ctrl+Shift+X, or Cmd+Shift+X on a Mac), search for GSS and install GSS — GPU Style Sheets. Or install it from the Visual Studio Marketplace, or from the command line:
code--install-extension lukyvj.gss-language
Cursor, VSCodium and Windsurf install their extensions from Open VSX: search for GSS in their Extensions view.
Format on save
Formatting on save is on by default for .gss files. To turn it off, add this to your settings.json:
"[gss]":{"editor.formatOnSave":false}
At-rules
@keyframes
Defines the steps of an animation, played by the animation property, like CSS.
Applies rules only when the screen matches a media query, like CSS. When the screen changes, a window resized or the system switching to dark mode, the scene follows at once and the camera stays where it is.
The width of the viewport; also min-width, in px or em.
(orientation: portrait)
Taller than wide; landscape otherwise.
(prefers-color-scheme: dark)
The dark mode of the system; light for the light mode.
(prefers-reduced-motion)
The visitor asks for less motion.
and,not,,
Combine queries, like CSS.
Any media query CSS knows works: the browser reads it. @media holds rules only, so @scene and @keyframes go outside it.
More details
Inside, the rules join the cascade where the @media is written, with their usual specificity. A scene can use up to 4 different queries. On a page, the viewport is the window, like CSS. In the playground and in Try it, it is the render, like the result of CodePen: make the render narrower to see a max-width query apply.
a small screen
Under 600px wide, the render is lighter, with dpr: 1, and the sphere turns blue.
A texture drawn by a fragment shader, for texture: paint(<name>): an image that comes from code, like paint() in CSS. The block is GLSL, the shader language of WebGL, written as it is for the web. WebGL2 only, for now: with WebGPU, the object keeps its color.
The time of the scene, in seconds, a float. u_time works too. A shader that reads it is drawn again at every frame; one that does not is drawn once.
resolution
The size of the texture in pixels, vec2(512.0, 512.0). u_resolution works too.
mouse
The pointer over the scene, in the pixels of the texture, from its bottom left like gl_FragCoord. u_mouse works too.
The block is a whole fragment shader, with its own main(): write out vec4 color; and set it. It is drawn into a texture of 512 by 512 pixels, which the object shows like an image.
More details
The shader holds its uniforms, its functions and its main(). gl_FragColor works too, as in WebGL1, and GSS adds #version 300 es and a precision when the code has none. The texture goes on each face of the object, with texture-size and image-rendering. Each @paint runs on its own: its names never meet the rest of the scene, and an error in its GLSL names the @paint and its line in the file. A scene with paint() is drawn with WebGL2: <gss-scene>, mountAsync() and the playground pick it by themselves in auto; with WebGPU chosen, the object shows its color instead, and the console says why.
rings that flow
The shader reads time: it is drawn again at every frame, and the colors flow over the cube.
Registers a variable that the page changes from JavaScript while the scene runs, without compiling it again, like CSS @property. Like a variable on :root, it has one value for the whole scene.
What the variable holds: "<number>", "<angle>", "<percentage>", "<color>", or "<length>" in px, for the radius of blur() and bloom().
inherits
Required, like CSS: true or false.
initial-value
Its value until the page sets another one. scene { --lift: 2; } gives another start value; declared anywhere else, on an object, a group, a :hover rule or a frame, the variable is an error. A frame can read it.
A registered variable goes in transforms, colors, sizes, materials, lights, fog and filter. It cannot go where the scene is built when it compiles: the copies of * n, d, timings, dpr and random().
More details
The full list, wherever the shader reads a value at each frame: the transforms (translate, rotate-x, rotate-y, rotate-z, scale, transform-origin), color, opacity, background, mask-image, the sizes of the shapes (size, radius, height, thickness, corner-radius, stroke-width, depth), the numbers of a gradient, material, floor, ambient, light and the intensity of the point lights, fog, camera-target, blend, offset-distance, offset-rotate, texture-size and filter, alone or inside the math and color functions. Out of reach, built when the scene compiles: the copies of * n, d and view-box, the timing of animations and transitions, the camera the mouse moves, dpr and random(). A value set from JavaScript is never refused: it is kept in its range, so a size never goes below 0. With @property-panel, the playground and Try it show a control for each registered variable over the render.
a number
The sphere rises with --lift, which the page sets with scene.setProperty("--lift", "2").
Shows a control for each variable of @property over the render, in the playground and in Try it: a slider and its number, or a color picker for a color. Moving a control sets the variable without compiling the scene again.
open, the default: the panel shows its controls. folded: only its title, variables · 2, which opens it. none: no panel, as without the rule.
range
The ends of the slider of a variable, and its step if you like: --col 0 3 1. One per variable, separated by commas: range: --col 0 3, --row 0 3;. They are written like the values of the variable: 0deg 180deg for an angle, 0% 50% for a percentage.
The panel never writes into the code. A page that embeds the scene shows no panel: it sets the variables with setProperty().
More details
A slider goes from 0 to twice the start value of its variable, at least from 0 to 1, and around 0 for a negative value; an angle from 0 to 360deg, a percentage from 0% to 100%. A number typed past the end of a slider widens it, and the reset button gives the start value back. With a range, the slider keeps the ends of the scene: a number typed past one is held there, and the panel says what the end is. A color has a color picker and no range. When the code changes a start value, the code wins over the slider.
a slider
Drag --lift and the sphere rises, without compiling again.
Declares the objects of the scene, one per line: a shape, then an optional #id, any number of .classes, and * n for n copies. Objects are combined in the order they are declared: see operation.
Names a block of declarations and rules, written once and put into any rule with @apply, like the mixins of CSS. Its parameters are read with var(), and each can have a default. The scene compiles as if the block were written in each rule that applies it.
Starts with --, like a variable: @mixin --card { … }.
(--size, --tint: #ff5a36)
The parameters, given by @apply in order and read with var(--size) inside. After :, the default for a value left out. A type, like --size <number>, is read as in CSS; the property that gets the value checks it.
{…}
Declarations, rules nested with &, @media, and other @apply.
@contents
Where the block given to @apply goes. @contents { … } holds what goes there when @apply gives none.
A mixin goes outside the rules, before or after the rules that apply it. From Sass: @apply replaces @include, var() replaces $, and @contents replaces @content.
More details
With two mixins of the same name, the last one wins, like CSS. A parameter hides a variable of the same name, and cannot be declared inside the mixin. The block given to @apply does not see the parameters: it belongs to the rule that applies the mixin.
a mixin, and the rules it saves
Both cubes take the two declarations of --polished: the scene is the same as with them written in each rule.
Puts the declarations and rules of a @mixin in the rule where it is written, like CSS. Values in parentheses go to the parameters of the mixin, in order; a block goes where the mixin writes @contents.
No values: each parameter takes its default. @apply --card(); is the same.
@apply--card(0.7,#ff5a36);
One value per parameter, in order; those left out take their default. A value with a comma of its own goes between braces: {color 1s, scale 2s}.
@apply--card{…}
The block goes where the mixin writes @contents. Values in parentheses can come before it: @apply --card(0.7) { … }.
The mixin takes the place of @apply in the cascade: a declaration written after it in the same rule wins, one written before loses.
More details
@apply goes in any rule: an object, a group, the scene, a :hover or :active rule, a nested rule, a @media, and a frame of @keyframes when the mixin holds declarations only. An unknown mixin, more values than parameters, a parameter with no value and no default, or a mixin that applies itself is an error that points at it.
where it stands in the rule
The mixin takes the place of @apply: in #a, its color comes after the blue one and wins; in #b, the blue one comes after it and wins.
Writes its block once for each number it counts, with var(--i) replaced by that number, like the @for of Sass. It goes anywhere: in @scene to make objects, around rules, in a rule, in @keyframes. The scene compiles as if each step were written by hand.
The variable of the loop, read with var(--i) anywhere in the block: in a value, a selector, an attribute of @scene, after *.
from1through4
Counts 1, 2, 3, 4: through counts the last number.
from1to4
Counts 1, 2, 3: to stops before the last number. From the larger to the smaller, the loop counts down.
by0.5
The size of a step, more than 0. Without it, 1.
0deg,50%
The numbers can take a unit, the same for all three: from 0deg to 360deg by 45deg.
* n and sibling-index() already repeat an object; @for is for what they cannot do, like a grid that needs two indices, or rules written for each step.
More details
A loop takes 1000 steps at most. Loops can be nested, each with its own name. Inside the block, the variable of the loop hides a variable of the same name. The numbers are written in the file: a var() of a variable or of a parameter cannot set them.
objects and rules for each step
The loop in @scene makes five cubes; the loop around the rule gives each its height, and stands it on the floor.
Writes its block once for each value of a list, with var(--x) replaced by that value, like the @each of Sass. A value can be a number, a color, a word, even a shape: @each --shape in cube, sphere { var(--shape); } makes a cube, then a sphere.
The variable of the loop, read with var(--x) anywhere in the block, even in the place of a shape in @scene.
intomato,gold,teal
The values, split at the commas. A value can hold several words: in 0 1 0, 1 0 0.
{a,b}
A value with a comma of its own goes between braces, like a value of @apply.
It goes anywhere @for goes: in @scene, around rules, in a rule, in @keyframes. Each value is written in place of the var(), as if the block were written by hand for it.
More details
A list takes 1000 values at most. An empty value is an error. Inside the block, the variable of the loop hides a variable of the same name.
a different shape at each step
The variable stands in the place of the shape: four objects of four shapes, in a row.
Targets objects by an attribute, like CSS. An object gets its attributes in @scene, like HTML: cube[data-col=2], sphere[lit], or [data-name="top left"] with spaces. Its id and its classes count as the attributes id and class, so [id^="hero-"] finds every copy of torus#hero * 3.
col=1col=2col=2col=3
[data-col=2] { color: #ff5a36; }
Specificity
100, like a class, even [id=…]
Forms
[attr]
The object has the attribute, whatever its value.
[attr=value]
The value is exactly this one. Quotes are optional for a single word or a number.
[attr~=value]
One of the words of the value, split at the spaces: [class~=big].
[attr|=value]
The value, or the value followed by a dash: [lang|=en] matches en and en-US.
[attr^=value]
The value starts with this.
[attr$=value]
The value ends with this.
[attr*=value]
The value contains this.
[attr=valuei]
Ignores the case. s keeps it, which is already the default.
In @scene, an attribute takes = only, and every copy of a * n gets the same ones. id and class keep their own syntax there: cube#a.big. Like CSS, ~=, ^=, $= and *= never match an empty value.
Nesting, like CSS: a rule can hold other rules, and & stands for the selector of the rule around it. In #g { &:hover { … } }, the nested rule is #g:hover.
group#g
#g { cube { color: #ff5a36; } }
Specificity
The sum of the rule around it and of the nested selector
Forms
&:hover
Joined to the parent: #g:hover.
#g &
The parent, placed anywhere: .a { #g & { … } } is #g .a.
cube
Without &, the parent comes first, then a space: #g { cube { … } } is #g cube.
> sphere
A leading combinator: #g { > sphere { … } } is #g > sphere.
@media
A query inside a rule: its declarations apply to that rule when the query matches.
Rules nest at any depth, and & also works inside :has() and :not().
More details
The declarations written after a nested rule come after it in the cascade, like CSS. With a list as the parent, a, b { & c { … } } gives a c and b c, each with its own specificity, where CSS gives both the specificity of the most specific selector of the list.
a group and what it holds
One rule styles the group, its cubes, their :hover, and its direct sphere.
Written after a value, it makes the declaration win against every normal one, whatever their selectors. Between two !important declarations, specificity decides again.
Specificity
Beats every declaration without it
The cascade picks one value and never combines them: * { scale: 0.5 !important; } gives every object a scale of 0.5; it does not halve their own.
a default that wins
#a asks for green, but the !important red of * wins.
Targets the next sibling, with the same parent: sphere + cube is a cube declared right after a sphere, in @scene or in a group.
sphere + cube { color: #ff5a36; }
Specificity
The sum of its parts
The order is the order of declaration, copies of * n included, not the position in 3D. Groups count as siblings, empty ones too. sphere:hover + cube reacts only to the sphere right before. The spaces around + are optional.
the cube right after the sphere
#a turns red, and grows while the sphere is hovered; #b does not.
Targets every later sibling, with the same parent: sphere ~ cube is every cube after a sphere, even with other objects between them.
sphere ~ cube { color: #ff5a36; }
Specificity
The sum of its parts
It never targets an earlier sibling, nor the children of a sibling. Each copy of * n counts, and so do groups, empty ones too. Chains can mix every combinator, and the spaces around ~ are optional.
every cube after the sphere
Both cubes after the sphere turn red; the one before does not.
On an earlier sibling: the cube reacts to the sphere.
#letters:hovercube
On a group: every cube of #letters reacts as soon as the mouse is over any object of the group, like hovering a child hovers its parent in CSS.
#g:has(sphere:hover)
Inside :has(): hovering one object changes another.
A :hover rule changes animatable properties only, and it styles objects, not groups: write #g:hover cube, not #g:hover { … }.
More details
The :hover rules join the cascade like any other: #a { color: blue; } beats cube:hover { color: red; }, and a normal !important beats them all. The animatable properties are the transforms, color, opacity, mask-image, offset-distance and variables: never a face. It can also start an animation: it begins when the pointer arrives and plays to its end, pointer gone or not (see :active).
lift on hover
Each cube rises, rotates and turns red under the mouse.
A pseudo-class, like CSS: the rule applies while the object is pressed, with the mouse button or a finger. Like CSS, it stays pressed until the button goes up, even if the pointer leaves it.
cube:active { translate: 0 -0.1 0; }
Specificity
100, like a class, added to the rest
Where it goes
#button:active
On the object pressed.
sphere:active+cube
On an earlier sibling.
#g:activecube
On a group: every cube of #g is pressed when any of its objects is.
#lamp:has(#switch:active)#bulb
Inside :has(): press one object, change another.
Write :active after :hover: a pressed object is hovered too, and the later rule wins. Like :hover, it changes animatable properties only, and styles objects, not groups.
More details
cube:hover { scale: 1.1; } cube:active { scale: 0.95; } grows under the mouse and sinks when clicked. With transition, pressing takes the transition of the :active rule, and releasing the one of the hovered state. An animation in a :active rule starts at the press and plays to its end, released or not, like a button that fires: once, unless it says how many times; the pressed state stays on until the animation ends, or for good with forwards, and the next press plays it again.
a button that sinks when pressed
It rises under the mouse, and sinks quickly while pressed.
A pseudo-class, like CSS: a group matches when something inside it, at any depth, matches the selector in the parentheses. With :hover inside, hovering one object can change another.
groupgroup
group:has(sphere) cube { color: #ff5a36; }
Specificity
the most specific selector inside, added to the rest, like CSS
Forms
#g:has(sphere)
The groups that hold a sphere, read once, when the scene compiles.
#g:has(sphere:hover)
While a sphere of #g is under the mouse.
#g:has(#innersphere)
A descendant selector, read from the group down.
#g:has(sphere,cube)
A list: one match is enough.
group:has(> sphere)
A leading combinator, relative to the subject: here, direct children.
cube:has(+sphere)
The next sibling, or any later one with ~. Chains work too: cube:has(+ group > sphere).
Without a leading sibling combinator, :has() goes on a group: an object holds nothing, so cube:has(sphere) is an error.
More details
Empty groups can match, and a :has() cannot hold another one. Unlike CSS, a list that mixes a static selector with :hover matches only during a hover.
the groups that hold a sphere
Only the cube of #a, the group with a sphere, turns blue.
A pseudo-class, like CSS: the object matches when none of the selectors in the parentheses does. cube:not(.red) is every cube without the class red.
.red
cube:not(.red) { color: #4f8cff; }
Specificity
its most specific selector, added to the rest, like CSS
Forms
:not(.red,torus)
A list: leaves out both.
cube:not(#gcube)
Any selector, read from the object outwards: every cube outside #g.
cube:not(:first-child,:last-child)
The cubes in the middle.
group:not(:has(sphere))
The groups without a sphere.
:not(.a):not(.b)
Several in a row.
It weighs like the most specific selector of its list, like CSS: cube:not(#hero) beats #hero alone. It is read once, when the scene compiles: it cannot hold :hover or a face.
A pseudo-class, like CSS: the position of the object among its siblings, the objects of the same @scene block or group, counted from 1. :nth-last-child() counts from the end.
12345
:nth-child(odd) { color: #ff5a36; }
Specificity
100, like a class, plus the most specific selector after of
Arguments
3
The third sibling.
odd,even
Every other one: 1, 3, 5… or 2, 4, 6…
An+B
A formula where n runs from 0 up: 2n+1 is 1, 3, 5…, 3n every third, -n+3 the first three.
An+B of<selector>
Counts only the siblings that match the list, and the object must match it too: :nth-child(2 of .red) is the second .red.
Each copy of * n is a sibling of its own. Groups count as siblings, and the count starts again inside each group.
More details
In @scene { cube * 4; sphere; }, cube:nth-child(odd) is cubes 1 and 3, and the sphere is child 5, as sibling-index() counts. The position is read once, when the scene compiles: of cannot hold :hover.
odd, and every third
The odd cubes turn red, and every third one rises.
Like :nth-child(), counting only the siblings of the same shape: in @scene { cube * 2; sphere; cube; }, cube:nth-of-type(3) is the last cube, though it is the fourth child.
1234
cube:nth-of-type(3) { color: #ff5a36; }
Specificity
100, like a class
The type is the shape name (cube, sphere, group…), like the tag of an HTML element. It takes the same An+B as :nth-child(), without of.
even cubes, last sphere
Counted among their own shape, the even cubes turn red and the last sphere blue.
Shortcuts, like CSS: :first-child is :nth-child(1) and :last-child is :nth-last-child(1). Each has an -of-type form that counts only the siblings of the same shape. They take no argument.
:first-child, :last-child { color: #ff5a36; }
Specificity
100, like a class
Forms
:first-child
The first sibling.
:last-child
The last sibling.
:only-child
An object without siblings.
:first-of-type
The first of its shape: cube:first-of-type is the first cube of its block, even after a sphere.
:last-of-type
The last of its shape.
:only-of-type
The only one of its shape among its siblings.
They read the order of @scene, not the position in 3D.
A pseudo-element, like ::part() in CSS: the rule styles one face of the object, not the object. A face takes texture and content. It is named, or numbered like :nth-child().
cube::face(1)
pyramid::face(1)
octahedron::face(1)
cube::face(1) { content: "1"; }
Specificity
1, like a tag, added to the rest
Faces
top,bottom
Up (+y) and down, in the object's own space: the faces turn with it.
front,back
Toward +z and toward −z.
right,left
Toward +x and toward −x.
::top,::bottom
Shortcuts for ::face(top) and ::face(bottom).
<number>
A face by its number: ::face(3). A cube is numbered like a die, its opposite faces adding up to 7: front 1, top 2, right 3, left 4, bottom 5, back 6.
odd,even, An+B
Several faces, like :nth-child(): ::face(odd), ::face(2n), ::face(n + 5) (5 and the faces after it).
A face without a rule of its own shows the texture and the text of the object. Like CSS, the pseudo-element ends the selector: cube.grass::top, never #g::top cube.
More details
Every shape seen from six sides (a cube, a sphere, a cylinder, a cone, a capsule, a tube, a torus, a plane and the shapes drawn with path) has the six faces of the cube and its numbers: on a round shape, a face is the part that looks that way the most. A pyramid has five faces: its slopes from the front, turning right (front 1, right 2, back 3, left 4), then its base, 5 or bottom. An octahedron has eight triangles, with numbers only: 1 to 4 on top, from the front-right turning right (front-right, back-right, back-left, front-left), 5 to 8 below them, in the same order. A number the shape does not have, like cube::face(7), is an error; so is the content of a name the shape does not have, like pyramid::face(top).
::face()
One face gets its own texture; the other five keep the object's.
Like CSS, it is inherited: a group gives its cursor to its objects. A :hover or :active rule can change it, like cube:active { cursor: grabbing; }.
More details
A cursor changes at once: it does not glide. Over the background and the floor, the page keeps its own cursor. A hidden object has none, since the mouse goes through it.
Reads a custom property, like CSS. A property whose name starts with -- is a variable: declared on the scene (like :root), on a group or on an object, it is inherited down to the object, and the closest one wins.
The value of the variable. A missing variable is an error, so a typo is never ignored.
var(--name,fallback)
The fallback, when the variable is not defined.
A variable can hold several values (translate: var(--pos)), use another one (--big: calc(var(--size) * 2)), and go inside calc().
More details
A frame of @keyframes can set a variable: every animatable property that uses it moves with it. The compiler replaces the variables with their values, unless @property registers them: the shader receives only numbers.
Computes a value, like CSS calc(): + - * / and parentheses, with numbers, angles (deg, rad, turn) and durations (s, ms). Like CSS, + and - need a space on each side.
rotate-y: calc(360deg / 5 * sibling-index());
Syntax
calc(<expression>)
Computed
at compile time, once per object
The compiler computes it once per object: the shader receives only the result, so the math costs nothing on the GPU. calc() and the other math functions work in any value, even inside another function: metal(#fff, calc(0.1 * 2)).
a staircase
Each copy climbs a step higher, with sibling-index().
The inverse trigonometric functions of CSS: they take a number and return an angle, in deg. atan2(y, x) gives the angle of the point (x, y), whatever its quarter: the way to turn an object toward a point. Its two values must share a unit.
The smallest or the largest of their values, or a value kept between two bounds, like CSS. The values must share a unit: max(10deg, 20deg), not max(10deg, 2).
Where a value sits between a start and an end, from 0 to 1, like the CSS function: progress(sibling-index(), 1, sibling-count()) goes from 0 for the first copy to 1 for the last. The result is kept between 0 and 1, and the three values must share a unit.
A random value between a minimum and a maximum, like CSS: random(0.2, 1.4), random(0deg, 360deg). The value is chosen once, when the scene compiles, and stays the same at every reload.
How many siblings the object has, itself included, like the CSS function. Divided into a full turn, it spreads objects evenly, whatever their number: change * 8 into * 20, and the ring follows.
The position of the object among its siblings, from 1, like the CSS function: cube * 12 makes 12 siblings numbered 1 to 12. With calc(), one rule gives each copy its own place, angle or size: no loop needed.
Siblings are the objects of the same @scene block or group. A group is a sibling too, and the count starts again inside each group. It means nothing in scene { } or in @keyframes, shared by every object.
a ring of petals
Twelve copies, each placed and turned 30° further than the one before.
The trigonometric functions of CSS: they take an angle (deg, rad, turn) or a number of radians, and return a number. cos() and sin() of the same angle give a point on a circle: the way to place objects in a ring, a spiral or a wave. pi is known too: cos(pi) is -1.
Not a shape: it holds objects and other groups, like <g> in SVG, and draws nothing itself. Its translate, rotations and scale apply to everything inside it, and the positions of its children become relative to it.
A contour, filled, then turned around the vertical axis, like clay on a potter's wheel: a vase, a bowl, a bottle, a chess piece. A prism pushes its contour straight back into a flat plate; a lathe turns it into an object that is round from every side. The contour is a polygon() or a path() (see d), drawn right of the axis x = 0.
Draw half the outline: x is the distance from the axis, and y goes down, like SVG. A contour that does not touch the axis turns into a ring.
More details
A contour inside another one is a hole. The height is centered on the contours (or on the view-box), and y goes up in the scene, like a path. Seen from the front, a prism and a lathe of the same contour can look alike; turned, the prism is a cut-out as thick as its depth, and the lathe stays round.
a lathe and a prism of the same contour
Both turned by 60deg: the lathe, on the left, is a vase from every side; the prism is a flat plate.
A thin, flat rectangle lying in the xz plane, centered on its origin: 1 × 1 by default. size sets its width and its depth, and rotate-x: 90deg stands it up, like a wall.
A contour, filled, then given a depth: a flat object, like a star, a letter, an arrow, a logo. The contour is a polygon() or a path() (see d), and a contour inside another one is a hole.
It stands in the xy plane, facing the camera, centered on its contours (or on its view-box), like a path. To make a round object of a contour, like a vase or a bowl, turn it with a lathe instead.
A pyramid on a square base, pointing up, centered on its origin: a base of 1 by 1 and a height of 1 by default. size sets the width and the depth of its base, and its height, like the box of a cube.
A hollow cylinder, a pipe, standing on the y axis and centered on its origin: an outer radius of 0.5, a height of 1 and a wall 0.1 thickness thick by default.
The line a path follows, written like the d of an SVG path or CSS path(). On a prism, the contour to fill; on a lathe, the contour to turn around its axis: a polygon() or a path().
An arc of ellipse: rx ry rotation large-arc sweep x y, like SVG.
Z
Closes the path.
polygon()
On a prism or a lathe: one point per comma, x and y separated by a space, y going down, like CSS clip-path. It closes itself.
A path copied from an SVG keeps its way up. One path unit is one scene unit, so an icon drawn in a box of 24 usually needs a scale.
More details
Capitals are absolute, lowercase letters relative. y goes down in SVG and up in the scene: GSS flips a path. On a prism or a lathe, each subpath of a path() is a closed contour, filled with the even-odd rule: a contour inside another one is a hole, like the inside of an o. A lathe turns its contour around x = 0, so its x are never negative.
Sets the full height of a cylinder, a cone or a capsule, along the y axis. The shape is centered on its origin: to stand it on the floor, set its y to half its height.
cylinder
cone
capsule
tube
height: …;
Syntax
<number>
Initial value
1
Applies to
cylinder, cone, capsule, tube
Animatable
no
The height of a capsule counts its round ends, so it is at least twice its radius.
Sets the radius of a sphere, a cylinder or a capsule, or that of a torus ring, measured to the center of its tube. On an octahedron, from its center to each tip; on a tube, its outer radius.
A cone takes a bottom and a top radius, like border-radius takes several values: the top one is 0 by default, a point, and a positive one cuts the top.
Sets the size of a cube along the x, y and z axes: one value makes a cube, three make a box. On a pyramid, the width and the depth of its base, and its height in the middle. On a plane, it sets the width and the depth: one value makes a square.
Smooths the junction between the object and the objects declared before it, over the given distance. 0 keeps a sharp junction; it works with every operation.
Sets the base color of the surface of the object: a hex color (#ff5a36), rgb(), hsl() or one of the 148 named colors of CSS (tomato). It can also be a gradient, a noise(), or one of them moved by displace().
Any CSS color: the compiler turns it into a hex color.
<gradient>
linear-gradient(), radial-gradient() or conic-gradient(), painted on the object as seen from the front, and taken by every material.
noise()
A noise cut in the object's own space, like a block of stone.
transparent
A transparent color (#ff000080, rgb(255 0 0 / 50%)), or the transparent stops of a gradient, make the object transparent, like opacity.
A gradient can be animated and changed by :hover, into another gradient of the same kind with as many colors. A color cannot change into a gradient.
More details
Each number of an animated gradient moves on its own. Through a variable, one number is enough: linear-gradient(var(--angle), …) turns when @keyframes changes --angle. On a point light, color is the color of its light: a plain color only.
Sets the background of the scene, seen wherever there is no object and no floor: a color, a gradient drawn over the canvas like a CSS background, a noise() or a displace().
Like CSS, a background can have several layers, separated by commas, the first on top. Only the last layer can be a plain color.
More details
Two layers: noise(3, #ffffff00 40%, #ffffff), linear-gradient(#2f6bd8, #9fc4ff). Transparent colors let the layers below show through, and background-blend-mode blends them. With animation on the scene, a color changes into a color and a gradient into a gradient of the same kind, also through variables: without objects and floor, the scene is a moving image.
How each layer of background blends with what is below it, like CSS: one mode per layer, the first for the top layer. A shorter list repeats over the layers.
background-blend-mode: multiply;
Syntax
<blend-mode>#
Initial value
normal
Applies to
the scene
Animatable
no
Modes
normal
Covers what is below: the default.
multiply,screen
Darkens, where white changes nothing; lightens, where black changes nothing.
overlay,soft-light,hard-light
More contrast: multiplies the darks, screens the lights.
darken,lighten
The darker or the lighter of the two colors.
color-dodge,color-burn
Brightens or darkens what is below, by the color of the layer.
difference,exclusion
The difference of the two colors; softer with exclusion.
hue,saturation,color,luminosity
One part of the color of the layer, over the rest of the color below.
The transparency of a layer still applies: a transparent part blends nothing.
a grain over a gradient
A gray noise multiplied over a gradient, like a grain.
Sets the color of the floor, an infinite plane at y = 0, lit like the objects. It can also be a gradient, a noise(), or one of them moved by displace(). none removes it: the objects float over the background, like the GSS logo.
floor: #e8e3db;floor: none;
Syntax
<color>|<gradient>|none
Initial value
#e8e3db
Applies to
the scene
Animatable
no
Values
<color>
Any opaque CSS color.
<gradient>
linear-gradient(), radial-gradient() or conic-gradient(), spread over a square of 40 units centered under the scene: everything the scene draws.
noise()
A noise read at each point of the floor, in the units of the scene: its scale is how many patterns fit in one unit.
none
No floor.
The floor takes one image, not layers, and opaque colors only. It is not animated, but the numbers of its image can be set from JavaScript.
More details
A gradient is seen from above, like on a plane: its top is away from the camera at rest, so linear-gradient(#0b1020, #273d62) goes from the horizon to the camera, and past the square it goes on with its first and last colors. A displace() moves the image by a share of that square: 0.02 moves it by up to 0.4 units. Reflections see it, and fog covers it like the rest of the scene.
How the color of the object blends with what is behind it, like CSS: the objects behind it, the floor and the background. It takes the modes of background-blend-mode, and plus-lighter, which adds the colors. normal, the default, covers what is behind.
mix-blend-mode: screen;
Syntax
<blend-mode>|plus-lighter
Initial value
normal
Applies to
objects
Animatable
yes
Modes
normal
Covers what is behind: the default.
multiply,screen
Darkens, where white changes nothing; lightens, where black changes nothing.
overlay,soft-light,hard-light
More contrast: multiplies the darks, screens the lights.
darken,lighten
The darker or the lighter of the two colors.
color-dodge,color-burn
Brightens or darkens what is behind, by the color of the object.
difference,exclusion
The difference of the two colors; softer with exclusion.
hue,saturation,color,luminosity
One part of the color of the object, over the rest of the color behind it.
plus-lighter
Adds the two colors, up to white.
On a group, the mode goes into each object, so they blend with each other too: CSS blends the group as one picture. On :hover and in @keyframes, the mode switches halfway through.
More details
The eye sees through a blended object to what is behind it, like through a transparent one, and the color of the object, lit, blends with the color behind it. An object never blends with itself: it shows its nearest surface, over what is behind the whole object. Its opacity still applies: at 50%, half of the blend shows. On a group, an object with a mode of its own keeps it, and there is no isolation. The switch halfway through is the one of a discrete property of CSS. The reflections show a blended object with its own color, and the eye sees six surfaces behind one another at most.
three lights, screened
A red, a green and a blue sphere over black: where they meet, screen adds them up to white.
A color from its red, green and blue channels, like CSS: numbers from 0 to 255, or percentages (100% is 255). Spaces or commas both work, and rgba() is the same function.
rgb(255 90 54)rgb(79 140 255)
Syntax
rgb(<red><green><blue>)|rgb(<red>,<green>,<blue>)
Computed
at compile time, once per object
An alpha (rgb(255 0 0 / 50%)) makes the color transparent, which only color, background and mask-image take: anywhere else, it is an error.
More details
Values outside the range are clamped. The math works inside, so one rule can give each copy its own color.
red, green and blue
@scene{sphere;}sphere{color:rgb(2559054);}
more red for each copy
calc() and sibling-index() raise the red of each cube.
A color from its hue, saturation and lightness, like CSS. The hue is an angle on the color wheel (0 red, 120 green, 240 blue), in degrees or in deg, rad or turn, and it goes round: -120 is 240.
hsl(<hue> 100% 60%)
Syntax
hsl(<hue><saturation><lightness>)
Computed
at compile time, once per object
Saturation and lightness are percentages, or numbers where 100 is 100%; a lightness of 50% gives the pure color. Commas, hsla() and the alpha work as in rgb().
More details
With sibling-index() on the hue, the copies of an object spread a rainbow.
A color from a hue, and how much white and black are mixed into it, like CSS: hwb(0 0% 0%) is pure red; more whiteness makes it paler, more blackness darker. When whiteness and blackness add up to 100% or more, the color is a gray. The hue works as in hsl().
The CIE Lab color space of CSS, built on how the eye sees: the lightness goes from 0 (black) to 100 (white), a from green to red, b from blue to yellow. lch() is the same space, written with a chroma (how colorful) and a hue.
The OKLab color space of CSS: its lightness matches the eye much better than hsl(), so colors of the same lightness look equally light, whatever their hue. With sibling-index() on its hue, oklch() gives a rainbow whose colors all look as light.
The lightness goes from 0 to 1, or 0% to 100%. oklch() adds a chroma, from about 0 to 0.4 (100% is 0.4), and a hue. A color the screen cannot show is clipped to it.
Mixes two colors in a color space, like CSS. Without percentages, half of each; with one, the other color takes the rest; with both, they are scaled to add up to 100%.
A mix of the light itself: brighter middle colors.
lab
Like oklab, in CIE Lab.
oklch,lch,hsl,hwb
Spaces with a hue: it goes the shorter way round the wheel, or the longer one with longer hue. A gray takes the hue of the other color.
Like CSS, when the percentages add up to less than 100%, the color becomes transparent, which only color, background and mask-image take: anywhere else, it is an error.
More details
A percentage set from JavaScript is kept between 0% and 100%, and percentages that add up to less are scaled up to 100%.
The first color in light mode, the second in dark mode, like CSS. The scene follows the system as it changes: it works like @media (prefers-color-scheme: dark), and counts as one of the queries of the scene.
A keyword, like CSS: the object's own color, wherever a color is expected. color-mix(in oklab, currentColor 60%, white) is a lighter version of whatever color the object has, so one rule can tint many objects.
outline-color: currentColor;
Syntax
currentColor
Computed
at compile time, once per object
It works in a material, in the stops of a gradient, in light-dark() and in a variable. color: currentColor is an error: a group passes no color down.
More details
As the color of a material, metal(currentColor, 0.2) is metal(0.2): the material follows the color, animated, on :hover, or a gradient. In a variable, it is read with the color of the object that uses the variable. An object without color uses the initial #e6e6e6. It can be written currentcolor too.
a lighter shell for every color
Three spheres, each in a jelly lighter than its own color.
The gradients of CSS, for the background of the scene, the color of an object, the color of a material and the floor. Each color can have one or two positions; the missing ones are spread like CSS, and two colors at the same place make a hard edge.
Along a line, to bottom by default. It takes an angle (0deg up, 90deg right), or to a side or a corner.
radial-gradient()
An ellipse that reaches the farthest corner by default. It takes circle or ellipse, a size keyword and a position: at 30% 40%, at top.
conic-gradient()
Around a center, clockwise from the top: from 90deg starts a quarter turn later, and at 30% 40% moves the center. Its colors take angles or percentages: a color wheel, a pie chart, the sweep of a hand.
repeating-linear-gradient()
Repeats the stops; also repeating-radial-gradient() and repeating-conic-gradient().
On an object, the gradient covers it as seen from the front, whatever its size, and moves and turns with it. In the background, it covers the canvas.
More details
In the background, the gradient follows the size of the canvas; reflections and glass see it in the direction they look. On an object, it goes from left to right and from bottom to top (a plane is seen from above): to top goes from its bottom to its top. On the floor, it covers a square of 40 units centered under the scene, seen from above. Colors are mixed in sRGB, like CSS with hex colors.
A checkerboard, wherever a gradient goes: the color of an object, the color of a material, the background, the floor. Like noise(), it is cut in the object's own space: a grid of cubes, one color and then the other.
On an object, the cells keep their size whatever the shape, and move and turn with it. The edges of the cells are sharp: far away, a fine checkerboard shimmers.
More details
On an object, the grid is cut in its own space, like a block of stone carved out of a checkered material. On the floor, it is read in the units of the scene; in the background, it follows the direction of the view. Like noise(), it can be animated into another checker(), its numbers can be set from JavaScript, and it can be the map of displace().
Moves an image by another one, like SVG feDisplacementMap, wherever a gradient goes: color, a material, background and its layers, mask-image, floor. Stripes moved by a turbulence make marble, rings make wood, a mask gets ragged edges.
Usually a noise(): its colors move each point where the image is read. On a gradient, red moves it right and green down, like SVG; on a noise(), red, green and blue move it in 3D. A channel at 50% moves nothing, 0% and 100% the most.
<amount>
A share of the size of the image: 0.3 or 30% moves it by up to 15% of its size.
It can be animated, and changed by :hover, into another displace() whose image and map are of the same kinds. Animating the at of a noise map makes the image flow.
More details
A noise() map gives each channel a noise of its own, like feTurbulence, so even a gray noise moves the image in every direction; a gradient map is read once, by its colors, which are opaque. Its numbers can be set from JavaScript.
A noise image, wherever a gradient goes: the background, the color of an object, the color of a material, the floor. Its colors are placed like the stops of a gradient, along the value of a smooth 3D noise, like SVG feTurbulence.
The octaves, from 1 to 8, if any: each one adds detail twice as fine, like numOctaves.
turbulence
Sharp creases instead of soft clouds: marble, fire, lightning.
seed<integer>
Another pattern.
at<x><y><z>
Moves the pattern.
On an object, the noise has no seam, and moves and turns with it. Animating at makes it drift, like clouds or smoke.
More details
On an object, the noise is cut in the object's own space, like a block of stone. On the floor, it is read at each point of the floor, in the units of the scene. In the background, it follows the direction of the view. Like a gradient, it can be animated and changed by :hover, into another noise() of the same kind with as many colors. Its numbers can be set from JavaScript.
Bands of two colors across an axis, wherever a gradient goes. Like checker(), they are cut in the object's own space: across y by default, like the rings of a column.
The scale: how many pairs of bands fit in one unit.
x,y,z
The axis the bands follow one another along: y by default, horizontal bands.
at<x><y><z>
Moves the bands: animated, they scroll.
<color>,<color>
The two colors, each half of a pair.
stripes() goes all around the object, in its own space. For bands seen from the front only, at any angle, use repeating-linear-gradient().
More details
Like noise(), it can be animated into another stripes() of the same axis, its numbers can be set from JavaScript, and it can be the map of displace(). The edges are sharp.
Sets how the surface of the object reacts to light. Without a color, a material uses the color property, like currentColor in CSS, so the color stays animatable; with its own, like brass or metal(#3a7bff), it keeps it, even when color is a gradient. The color can also be a gradient: metal(linear-gradient(#ffd27a, #ff5a36), 0.2).
Reflects the scene. Its roughness goes from 0, a mirror, to 1, brushed metal (0.2 by default).
jelly()
Lets the light through its thin parts, like a gummy candy. Its density goes from 0, clear, to 1, deep (0.5 by default).
glass()
See-through, bent by its refraction index, from 1 to 3 (1.5 by default: water is 1.33, diamond 2.4), then a frost from 0 to 1.
emissive()
Gives its own light: a lamp, a screen, a neon. It is never darker than its color times its strength, from 0, none, to 4 (1 by default: its full color, even in the shade). It does not light the objects around it; with bloom() in the filter of the scene, it glows.
iridescent()
A thin film, like a soap bubble or the back of a CD: its colors turn with the angle you see it at, most at the edges. Its strength goes from 0, none, to 1 (0.7 by default).
frosted,wavy,hammered,blurred
The style of the frost: white patches (the default), big waves, small bumps, a soft blur.
gold,chrome
metal(#d4af37, 0.2) and metal(#ffffff, 0.05).
copper,silver,brass,aluminum
metal(#c8784a, 0.25), metal(#e3e4e6, 0.1), metal(#c9a24d, 0.2) and metal(#c4c8cc, 0.35): the metals of everyday objects.
jelly,glass,ice
jelly(), glass(), and glass(#cfeaff, 1.31, frosted 0.25).
emissive,iridescent
emissive() and iridescent(), with the color of color.
A live image of an HTML element of the page, for texture, like CSS element(): a card, a form, a chart, any interface on a 3D surface. The browser draws the element with the CSS and the fonts of the page, and the object shows it again each time it changes.
<div id="card">
element(#card)
texture: element(#card);
Syntax
element(<id>)
Computed
by the browser, each time the element changes
Put the element inside <gss-scene>, next to its script, or inside the canvas given to mount(). Screen readers still read it, but it is seen only on the object.
More details
One image covers each face, like any texture: give the element the proportions of the face, and use ::face(front) for one face only. A <canvas> inside the element is captured with it, frame after frame while the scene draws: a chart, a game, another shader. A scene with an element is drawn with WebGL2.
an HTML card
A card of HTML that sways on the front of a thin cube.
<articleid="card"style="
width: 320px; height: 200px; padding: 28px;
box-sizing: border-box; border-radius: 18px;
background: #f4f1ea; color: #1a1d2b;
font: 600 30px/1.2 system-ui, sans-serif;">
Hello from <emstyle="color: #ff5a36;">HTML</em>, on a 3D card
</article>
a canvas, live
A <canvas> and its one-line WebGL shader inside the element: the face follows it, frame by frame. The script rides an onerror attribute, so it runs even where a page inserts the HTML with innerHTML, as the playground does; in a page of yours, a plain <script> works too.
The texture a @paint draws, for texture, like CSS paint(), whose image comes from code: texture: paint(rings);. WebGL2 only, for now.
@paint rings { … }
512 × 512 px
texture: paint(rings);
Syntax
paint(<name>)
Computed
on the GPU: once, or at every frame when its shader reads time
The name is that of a @paint of the scene. Several objects can show the same one: it is drawn once for all of them.
More details
A scene with paint() is drawn with WebGL2, which <gss-scene>, mountAsync() and the playground pick by themselves; with WebGPU chosen, the object shows its color instead.
two objects, one shader
The cube and the sphere show the same stripes, drawn once per frame.
@paintstripes{uniformfloat time;
outvec4 color;
voidmain() {
float band = step(0.5, fract(gl_FragCoord.y / 64.0 - time * 0.5));
color = vec4(mix(vec3(1.0, 0.35, 0.21), vec3(0.98, 0.95, 0.9), band), 1.0);
}
}@scene{cube;sphere;}cube{translate:-0.90.60;size:1.1;texture:paint(stripes);}sphere{translate:0.90.70;radius:0.7;texture:paint(stripes);}
texture
Projects an image onto the surface of the object: one image per face, the top, the bottom and the sides, whatever the size of the object. The image replaces the base color, and moves, turns and scales with the object.
url("photo.jpg")
texture
texture: url("photo.jpg");
Syntax
url("<file>")|element(<id>)|paint(<name>)
Initial value
none
Applies to
objects
Animatable
no
Values
url("<file>")
An image file, read next to the .gss file, like url() in a stylesheet.
element(<id>)
A live image of an HTML element of the page: see element().
<articleid="card"style="
width: 320px; height: 200px; padding: 28px;
box-sizing: border-box; border-radius: 18px;
background: #f4f1ea; color: #1a1d2b;
font: 600 30px/1.2 system-ui, sans-serif;">
Hello from <emstyle="color: #ff5a36;">HTML</em>, on a 3D card
</article>
texture-mapping
How the image of texture is laid on the object. box, the default, projects one image on each face of the box around it; sphere wraps one image around the whole object, like a map around a globe, for the Earth, the Moon or a planet.
url("earth.jpg")
texture-mapping: box;
texture-mapping: sphere;
Syntax
box|sphere
Initial value
box
Applies to
objects
Animatable
no
Values
box
One image on each face of the box around the object: the default.
sphere
One image around the whole object: the longitude across, the latitude up. The middle of the image faces the front, its top is the top of the object, and its left and right edges meet at the back.
sphere expects a map twice as wide as it is tall (an equirectangular map, like most maps of the Earth and the Moon). It covers the object once: texture-size has no effect.
More details
The image is seen from the center of the object: a sphere shows it in one piece, without the stretched corners of box, and only the poles squeeze its top and bottom rows, like on any globe. A map whose left and right edges match shows no seam at the back. Another shape shows the image too, projected from its center. The images of ::face() are wrapped the same way, each one on its own face, and image-rendering applies as with box. A large map can shimmer on a small sphere: 1024 pixels across is enough.
the Earth
NASA's Blue Marble around a sphere tilted like the Earth, turning once in 12 seconds.
The size of one image on the surface, in the units of the object, like background-size: the image repeats to cover each face. auto, the default, fits one image to each face.
Writes a text on the surface of the object, the way content gives a text to a ::before in CSS. The text is painted over the color, the gradient or the texture of the object: it moves, turns and takes the light with it, and goes behind what stands in front of it.
GSS"GSS"
GSSGSSGSScontent
content: "GSS";
Syntax
<string>+|none
Initial value
none
Applies to
objects
Animatable
yes
Values
<string>
The text, between quotes. Several strings side by side are joined: "3D" " text". \A starts a new line, like CSS: "one\A two".
var(--name)
A variable that holds a string.
none
No text: the default. normal is the same.
The text is as large as each face lets it be, or as font-size says. Unlike CSS, content goes on the object itself, not on a ::before.
More details
The text sits in the middle of each face, and keeps its proportions. Without ::face(), every face of the object shows it, read from left to right on each; ::face() gives a face a text of its own, or none, by its name or its number (::face(3), ::face(odd)). A cube and a plane write on their flat faces. A round shape (a sphere, a cylinder, a tube, a cone, a capsule, a torus) wraps the text around itself like a label on a can, on four sides around its axis, and lays it flat on its top and its bottom. A pyramid writes on its four slopes and its base, an octahedron on its eight triangles, upright. Any other shape takes the text of each side like a sticker, fading where its surface turns away. The font comes from font-family, font-weight and font-style, the size from font-size, the color from text-color, the side its lines lean to from text-align. An emoji keeps its own colors. On :hover and in @keyframes the text changes halfway, like a value of CSS that does not interpolate.
a sign
The word fits the front of the board, and each of its other faces.
The font of the text of content, like CSS: a list of fonts, and the first one the browser has draws the text.
Aafont-family: sans-serif;
Aafont-family: serif;
Aafont-family: monospace;
Syntax
[<family-name>|<generic-family>]#
Initial value
sans-serif
Applies to
objects
Animatable
yes
A font the page loads, with @font-face or a stylesheet of fonts, can be named: the text is drawn again when it arrives. Unlike CSS, the font is not inherited: set it on the object that has the text.
a serif and a monospace
@scene{cube#a;cube#b;}scene{camera-distance:5;}cube{size:1.80.90.2;color:#f4f1ea;content:"Hello";}#a{translate:-10.80;font-family:Georgia,"Times New Roman",serif;}#b{translate:10.80;font-family:monospace;}
font-size
The height of the letters of content, in the units of the scene, like the other sizes of GSS. A line is as wide as the face: a longer text goes on to the next line, between its words.
Aafont-size: auto;
Aafont-size: 0.2;
Syntax
auto|<number>
Initial value
auto
Applies to
objects
Animatable
yes
Values
auto
The text is as large as each face lets it be, its lines broken only where it says \A: the default.
<number>
The height of the letters: 0.3 on a cube of 1 gives about three lines.
Unlike CSS, the size is in the units of the scene, and auto fits the text to the face. A taller text is cut at the edges of the face; its lines are centered on its height.
a paragraph on a board
@scene{cube;}scene{camera-angle:-15deg10deg;}cube{translate:00.90;size:2.41.40.1;color:#f4f1ea;content:"Selectors, the cascade and keyframes, compiled into one shader.";font-size:0.2;}
The color of the text of content. Without one, the text is black on a light object and white on a dark one, so it stays readable when the color of the object changes.
GSSGSSGSSblack or white
GSSGSSGSStext-color: #ff5a36;
Syntax
<color>
Initial value
black or white
Applies to
objects
Animatable
yes
In CSS, color colors the text. In GSS, color is the color of the object, so its text has a property of its own.
Post-processing, like CSS filter: a list of functions applied in order, on the whole image (scene { filter }), on an object, or on a group and everything in it.
filter: blur(4px);
filter: bloom();
filter: hue-rotate(160deg);
filter: grain();
Syntax
none|<filter-function>+
Initial value
none
Applies to
the scene, objects and groups
Animatable
no
Functions
brightness(),contrast(),saturate()
A number or a percentage: 1 or 100% changes nothing.
grayscale(),sepia(),invert()
From 0 to 1: how far it goes.
hue-rotate()
An angle around the color wheel.
grain()
A film grain that moves at every frame (0.1 by default).
vignette()
Darker towards the corners, like an old lens: from 0 to 1 (0.5 by default). On the scene only.
chromatic-aberration()
Red and blue pulled apart towards the corners, like a cheap lens: a length in px at the corners (2px by default). On the scene only.
blur()
A blur, by a length in px, like CSS.
bloom()
Makes the bright parts glow: an amount (0.6 by default) and a radius in px (16px by default).
opacity()
Makes an object or a group transparent, multiplied with opacity. On the scene, it is an error: the scene stays opaque.
On an object or a group, the filters change only its own pixels. blur() and bloom() cost more as their radius grows; the other filters cost almost nothing.
More details
The reflections see the filters of an object too, and a blur() spreads the object over what is around it. Like CSS, an object's filter comes before its group's, and the scene's comes last; on an object, the filters that read one pixel come before blur() and bloom(), and an object and its group cannot both have one of them. blur() and bloom() cost more because they read the pixels around each pixel.
none leaves the object out of the scene, like CSS: it is not drawn, casts no shadow and cannot be pointed at. On a group, every object inside it is left out; on a light, the light is off.
none
sphere { display: none; }
Syntax
none|block
Initial value
block
Applies to
objects
Animatable
no
It changes with @media, not on :hover or in @keyframes: to hide an object for a moment, use visibility.
More details
The object still counts among its siblings, like an element of the page with display: none: :nth-child() and sibling-index() see it. It is set when the scene compiles. Like CSS, @media (max-width: …) reads the width of the window on a page; in the playground and in Try it, the width of the render.
a whole group left out
The group of small spheres says display: none: only the torus is drawn. Remove the rule to see them.
The small spheres show only when the scene is wider than 400px: Try it is narrower, so they are left out; open it in the playground and drag the divider to see them come and go.
Cuts holes in the object, like a CSS mask: where the image covers the surface, the object is there; where it is transparent, the surface is not drawn, and the eye sees inside the object, then what is behind it. The image is a gradient, a noise(), or one of them moved by displace().
Unlike CSS, a mask does not fade the object: a part covered less than half is a hole, with a sharp edge. The mouse goes through the holes.
More details
It is read like a gradient in color: seen from the front in the object's own space, and in 3D for a noise(), so the holes move and turn with the object. mask-mode says what counts, the alpha by default. The holes show in the reflections too, and :hover reaches the object behind a hole.
holes from a noise
A noise() whose black covers and whose transparent cuts.
How much the object covers what is behind it, like CSS: from 0, invisible, to 1, opaque (the default), as a number or a percentage; a value outside is kept between them. The alpha of its color and opacity() in filter multiply with it.
opacity: 1;opacity: 0.4;
Syntax
<number>|<percentage>
Initial value
1
Applies to
objects
Animatable
yes
Unlike CSS, a group does not fade as one picture: each of its objects fades, so they show through each other.
More details
Through a transparent object, the eye sees its back face from the inside, then what is behind it: a sphere at 50% looks like a bubble. With shadows, the light goes through a transparent object like stained glass: a red glass at 50% casts a pink light. The mouse still points at a transparent object, like CSS, and the reflections show it opaque.
A line around the silhouette of the object, like CSS outline: a width, a style and a color, in any order. Like CSS, it needs a style: outline: 0.03 solid #111;, or dashed, dotted, double…
outline: 0.03 solid #ff5a36;
Syntax
<outline-width>||<outline-style>||<outline-color>
Initial value
medium none currentColor
Applies to
objects
Animatable
yes
Unlike CSS, its width is in the units of the scene, not in pixels: it gets thinner far from the camera, like the object. It changes on :hover and in @keyframes.
More details
The line is drawn outside the object, where the eye passes close to its edge, and over what is behind it: another object, the floor or the background. It takes no room, is never lit and cannot be pointed at, like CSS, but the fog covers it. outline-width, outline-style, outline-color and outline-offset set one part each, and win over outline wherever they are written, like the other longhands of GSS.
In CSS, the browser draws its own line, the one of a focused field. GSS has no browser look of its own: auto draws solid.
dashed
Dashes three widths long, with gaps as long, all around the object.
dotted
Round dots, as wide as the line, one width apart.
double
Two thin lines, each a third of the width, with the third between them empty.
groove
Carved into the scene: the outer half darker at the top left, the inner half at the bottom right.
ridge
Raised out of the scene: groove the other way.
inset
The line darker at the top left, as if the object sat in a hollow.
outset
The line darker at the bottom right, as if the object stood out.
A :hover or :active rule can change the style; @keyframes cannot.
More details
Dashes and dots are placed around the object, by the angle seen from the camera, and as many as fit around its size: they close without a broken one. The shaded styles take the light from the top left of the screen, like the borders of CSS, at half the color on their dark side. When the object has no style at rest, the one of :hover, then of :active, is drawn.
every style
Eight spheres, one style each, from left to right: solid, dashed, dotted, double, groove, ridge, inset and outset.
Like CSS, it is inherited: a hidden group hides its objects, and an object inside it can be visible again. A hidden object cannot be hovered: hide another object, like #button:hover #label.
More details
It changes on :hover and in @keyframes, at once, like CSS: between visible and hidden, the object is there for the whole way, and gone only at hidden. A group's own visibility is read when the scene compiles.
a light that blinks
The sphere is gone for the second half of each second.
How far along its offset-path the object is, like CSS: a number, in the units of the path, or a percentage of its length. Animate it with @keyframes, a transition on :hover, or the scroll.
0%50%100%
offset-distance: 50%;
Syntax
<number>|<percentage>
Initial value
0
Applies to
objects
Animatable
yes
On an open path, the object stops at its ends; on a closed one (with Z), it goes round, so 150% is halfway through the second lap. A ray() has no length: give it a number.
A motion path, like CSS: the line the object travels along, placed by offset-distance and turned by offset-rotate. It works on groups too.
0%100%
offset-distance: 0%;offset-distance: 100%;
Syntax
none|path(<string>)|ray(<angle>)
Initial value
none
Applies to
objects
Animatable
no
Values
path(<string>)
The d of an SVG path, in the object's own xy plane, facing the camera like the path shape: centered on itself, y up, one path unit per scene unit. A path shape with the same d draws the track; a path that ends with Z is closed, and the object goes round it.
ray(<angle>)
A straight line from the place of the object, at an angle: 0deg up, 90deg right, like CSS.
none
No motion path: the default.
Like CSS, the motion path comes after translate, the rotations and scale: translate moves the whole path, scale scales it. For a path on the floor, turn a group with rotate-x: 90deg.
along a track
A ball that follows the curve of a path drawn under it.
Rotates the object around the x axis. Like CSS rotateX(), a positive angle turns its top away from the viewer. On a group, it turns everything inside it around the origin of the group.
x
rotate-x: 0deg → 360deg;
Syntax
<angle>
Initial value
0deg
Applies to
objects
Animatable
yes
45 degrees
@scene{cube;}cube{rotate-x:45deg;}
rotate-y
Rotates the object around the y axis. Like CSS rotateY(), a positive angle turns its right side away from the viewer. On a group, it turns everything inside it around the origin of the group.
y
rotate-y: 0deg → 360deg;
Syntax
<angle>
Initial value
0deg
Applies to
objects
Animatable
yes
-45 degrees
@scene{cube;}cube{rotate-y:-45deg;}
rotate-z
Rotates the object around the z axis. Like CSS rotate(), a positive angle turns it clockwise. On a group, it turns everything inside it around the origin of the group.
z
rotate-z: 0deg → 360deg;
Syntax
<angle>
Initial value
0deg
Applies to
objects
Animatable
yes
-45 degrees
@scene{cube;}cube{rotate-z:-45deg;}
scale
Scales the object by one number, on its three axes. On a group, it scales everything inside it, the positions of its children included.
scale: 0.55;scale: 1.35;
Syntax
<number>
Initial value
1.0
Applies to
objects
Animatable
yes
twice as big
@scene{cube;}cube{scale:2.0;}
transform-origin
The point the object turns and scales around, like CSS: rotate-x, rotate-y, rotate-z and scale keep it in place, and a motion path carries it along the path.
The sides of the box of the object, like CSS. Two keywords go in any order: top left.
<percentage>
Also on the box: 0% is the left or the top side, 100% the right or the bottom one.
<number>
A point of the object's own space, from its center, y up, like translate: 0 0.5 0 is 0.5 above the center. The third value, z, is always a number.
Unlike CSS, where a length is measured from the top left corner, a number is measured from the center, like everything in GSS. A group has no box: it takes numbers and center.
Moves the object along the x, y and z axes. Like CSS, x points right and z toward the viewer; y points up, and the floor is at y = 0. On a group, it moves everything inside it, and the positions of its children become relative to the group.
A cube of 1, half a unit up: it sits on the floor.
@scene{cube;}cube{translate:00.50;}
Animation and transitions
animation
Plays a @keyframes animation on the object, like CSS. After its name come the duration, then if needed an easing, a delay, a number of iterations, a direction and a fill mode, in any order.
The first time is the duration of one iteration, in s or ms; the second one, the delay: the animation starts after it, or partway with a negative delay.
<easing>
linear (the default), ease, ease-in, ease-out, ease-in-out, cubic-bezier(), linear() or steps().
<number>|infinite
How many times it plays: 1.5 stops halfway through the second time. infinite is the default.
normal,reverse,alternate,alternate-reverse
The direction: forward, backward, forward then backward, or backward first.
none,forwards,backwards,both
The fill mode: what the object shows outside the animation.
Unlike CSS, an animation loops forever unless it says how many times it plays.
More details
Each part also has a property of its own, like animation-duration, which wins over what animation says. On an object, it animates translate, the rotations, scale, color (a gradient too), opacity, mask-image and offset-distance; on a group, its transforms. On the scene, it animates the properties of the scene, like background, light or fog, and the variables they use: the objects do not follow the variables a scene animates. In a :hover or :active rule, the animation starts with the state and plays to its end, once unless it says how many times, like a button that fires: see :active.
up and down
alternate plays the frames forward, then backward.
How long the animation waits before it starts, in s or ms; a negative delay starts it partway, as if it had begun earlier. It wins over the delay written in animation.
How many times the animation plays: a number, where 1.5 stops halfway through the second time, or infinite. Unlike CSS, where an animation plays once, a GSS animation loops forever by default. It wins over the count written in animation.
The part of a scroll timeline the animation plays on, like CSS: entry while the scene comes into view, exit while it leaves, or percentages of the whole scroll. It goes with animation-timeline.
From the moment the scene starts to enter its scroll container to the moment it has left: the whole of view().
contain
While the scene is all inside its scroll container, or, when it is larger, while it fills it.
entry
While the scene comes in, from its first pixel to all of it. entry-crossing: while its start edge crosses its whole height.
exit
While the scene goes out, from its first pixel out to all of it. exit-crossing: from its end edge leaving to its start edge leaving.
<percentage>
A share of the named range before it, or of the whole timeline alone: entry 50%, 20% 80%.
<length>
Pixels from the start of the named range before it, or of the whole timeline: 100px.
It takes a start, then an end: entry 10% exit 90%. Before the range the animation shows its first frame, past it its last.
More details
A name alone is the whole of that range, animation-range: entry; a name alone at the end is its end, contain exit. animation-range-start and animation-range-end set one side each, and win over animation-range wherever they are written, like the other longhands of animation. The named ranges belong to view(): scroll() takes percentages and pixels. Unlike CSS, which ignores it there, a range on an animation that plays in time is an error. In the playground, which does not scroll, the slider over the render stands for one screen of scroll: 100px is a hundred pixels of the height of the window.
rise while it comes in
The spheres rise while the scene enters the page, then stay up.
Drives the animation with the scroll of the page instead of time, like CSS scroll-driven animations. The whole timeline is the whole animation, so the duration and the delay do not count.
A scroll container, from its start (0%) to its end (100%): nearest (the default) is the closest one around the scene, root the page. The axis is block (the default), inline, y or x.
view()
The scene crossing its scroll container: 0% when it enters at the bottom, 100% when it leaves at the top.
Write it after animation, which still needs a name and a duration: animation: spin 1s linear; animation-timeline: scroll();. Scrolling back plays the animation backward.
More details
The number of iterations and the direction still count, an animation without a count plays once along the scroll. A scene takes up to 4 different timelines. In the playground, which does not scroll, a slider stands in for the scroll.
Glides the object to its :hover state and back, instead of jumping, like CSS. One transition covers every property that :hover changes on the object.
no transitiontransition: 0.4s
cube { transition: 0.4s ease-out; }
Syntax
none|[all]<time>[<easing>][<time>]
Initial value
none
Applies to
objects
Animatable
no
Values
<time>
The first time is the duration, the second one the delay.
<easing>
ease by default, or any easing: a keyword, cubic-bezier(), linear() or steps().
Like CSS, the object enters :hover with the transition of its :hover rule, if it has one, and leaves it with the transition written at rest. Leaving halfway goes back from where it is, in the time already spent.
An easing curve, like CSS, for animation and transition. The curve goes from (0, 0) to (1, 1), pulled by two handles: x is the time, from 0 to 1, and y the progress, which can go below 0 or above 1 to overshoot.
An easing drawn as straight segments, like CSS: each number is the progress at one moment, a percentage of the duration. With enough points, it draws bounces and springs.
A missing moment is spread evenly between its neighbours; the first point is at 0% and the last one at 100%. The keyword linear is linear(0, 1): a constant speed.
More details
Two percentages on one number hold it still between them, and two points at the same moment jump.
a bounce
The sphere falls and bounces twice before it rests.
An easing that moves by equal jumps instead of gliding, like CSS: steps(4) holds still, then jumps, four times. Good for ticking hands, sprites and anything mechanical.
Sets where the camera starts around its target: first the angle around the vertical axis (0deg in front, on +z; 90deg on the right, on +x), then the height above the horizon, from 0deg to 80deg. Dragging with the mouse changes it.
The gestures that move the camera: orbit, a drag turns it around its target, and zoom, the wheel brings it closer. none keeps the camera where the GSS puts it, and the page scrolls over the scene. On an object, the gestures that cannot start over it.
dragcontrols: orbit;
wheelcontrols: zoom;
Syntax
auto|none|[orbit||zoom]
Initial value
auto
Applies to
the scene, objects and groups
Animatable
no
Values
auto
Both gestures, the default: a drag turns the camera, the wheel zooms.
none
Neither: the camera stays where the GSS puts it, and the wheel scrolls the page.
orbit
A drag turns the camera around its target.
zoom
The wheel brings the camera closer or takes it farther, from 3 to 15.
On scene, it says which gestures move the camera. On an object or a group, it says which gestures can start over it: a button with controls: none is pressed without turning the camera.
More details
CSS has no camera, so GSS has a property of its own for it. Around a button with controls: none, a drag from the floor still turns the camera. A gesture moves the camera when the scene and the object under the pointer both take it. Like cursor, it is inherited from the groups; it follows @media, not :hover. On a page, <gss-scene controls="none"> and mount(canvas, scene, { controls: false }) turn every gesture off, whatever the GSS says. camera-spin still turns the camera.
a still camera
The camera stays where the GSS puts it: a drag does nothing, and the wheel scrolls the page.
Sets the sun of the scene: its direction, then if needed its color and its intensity, in any order. The default lights the scene from the upper left, in front.
light: -45deg 54.7deg;
Syntax
none|<angle><angle>[<color>||<number>]?
Initial value
-45deg 54.7deg
Applies to
the scene
Animatable
yes
Values
<angle><angle>
The direction: the azimuth around the vertical axis (0deg points to +z, 90deg to +x), then the elevation above the horizon (90deg is straight overhead).
<color>
The color of the sun, white by default.
<number>
Its intensity, 1 by default.
none
No sun: the scene is lit by its point lights and its ambient light.
With animation on the scene, the sun can turn, change color, or rise from none.
a low sun from behind
-120deg 30deg: from behind, on the left, low over the horizon.
Not a shape: a point of light, which draws nothing. Its color is the color of its light (white by default), and intensity how much light it gives, on top of the sun and the ambient light of the scene.
A light moves like an object, and is never drawn: to see the bulb, put a shape at its place. A scene takes 8 lights at most.
More details
What moves a light: translate, the groups it is in, animations, :hover through its group, a motion path, and rotations around a transform-origin, in numbers, since a light has no size. Lights add up. A light cannot be hovered or pressed: hover the shape at its place, like #lamp:hover light. Without shadows, the light goes through the objects.
a lamp
A warm bulb, with a faint ambient light and no sun.
How much light a point light gives: what a surface facing it receives at 1 unit, then less with the square of the distance, a quarter at 2 units and a ninth at 3. 0 turns it off.
Sets the light received by the surfaces the sun does not reach, from 0 (black shadows) to 1 (no shadows). A color can follow, for the light of the sky in the shadows: ambient: 0.2 #9db4ff gives bluish shadows under a warm sun.
Lets the objects cast shadows, on the floor and on each other: a point in the shadow of an object keeps only the ambient light.
shadows: none;
shadows: hard;
shadows: soft;
Syntax
none|hard|soft
Initial value
none
Applies to
the scene
Animatable
no
Values
none
No shadow: the default.
soft
A penumbra that grows with the distance to the object that casts it, like the shadow of the sun.
hard
A sharp edge.
The sun and every point light cast shadows, and the holes of mask-image let the light through. Each light costs one more ray per pixel. The reflections and the refractions show the objects without shadows.
soft shadows
The shadows of the sun blur as they get farther from the objects.
Fills the scene with fog, measured from the camera: none before the first distance, only fog after the second one, and more and more of it between. Without a color, each object fades into the background behind it.
fog: none;fog: 6 14;
Syntax
none|[<color>]?<number><number>[<color>]?
Initial value
none
Applies to
the scene
Animatable
yes
Values
<number><number>
Where the fog starts, and where it hides everything.
<color>
Its color, before or after the distances. The background takes it too: past the fog, only the fog is seen.
none
No fog: the default.
The scene draws nothing farther than 20 units from the camera: a fog that ends before that hides the far edge of the floor. The scene can animate its fog, from none too.
More details
The reflections and the refractions show the objects without fog.
into the background
The cubes melt into the background as they go away.
Smooths the silhouettes of the objects, without raising the pixel density of the render.
shape-rendering: auto;
shape-rendering: geometricPrecision;
Syntax
auto|geometricPrecision
Initial value
auto
Applies to
the scene
Animatable
no
Values
auto
The default: each pixel shows one surface, and silhouettes keep their steps.
geometricPrecision
A pixel that the edge of an object only partly covers blends the object with what is behind it: the silhouettes come out smooth.
Like shape-rendering in SVG, but on the scene only, with two values. Only silhouettes are smoothed: the edges of a texture, of a shadow or of the floor keep their steps.
More details
auto keeps the sharp edges GSS has always drawn. The smoothing comes from the ray each pixel already casts, not from more rays or a bigger render.
smooth silhouettes
The sphere keeps one ray per pixel; only its grazing edge gets partial coverage.
Shows the scene as it is lit, or with the isolines of its distance field over it: the distance to the nearest object, which the shader measures at every step of every ray.
view: shaded;
view: distance;
Syntax
shaded|distance
Initial value
shaded
Applies to
the scene
Animatable
no
Values
shaded
The scene, lit: the default.
distance
The same scene, with a line every 0.25 units from the objects, fainter as it goes away. The lines lie on the plane that faces the camera through camera-target, and show where that plane is in front of the objects.
The floor is left out of the distance: the lines go around the objects only.
More details
Far from a path, a prism or a lathe, the distance is the one to its box, which the shader uses to skip it quickly, and the lines show that box. The lines are light on a dark scene and dark on a light one.