@reference "../../../app.css";
/*
* The material: the tokens behind every visual (iso planes, hairline plates, rims, ink, curves, avatars), dark first, then light re-pointing the same tokens —
* and the shared classes built on them: .glass, .composite, .rim, .glint, .rim-copy, .at,
* .avatar, plus the shared keyframes. A visual written against these gets both
* themes; its own CSS sits beside its component.
* The treatments are :where()-weighted, so a component's own rules always win over them.
*/
.landing {
/* Iso planes: rotate · squash · skew, in that order. Use as the element's one
`transform` — Lightning CSS drops `translate`/`rotate`/`scale` set in the same rule. */
--iso-plane: rotate(19deg) scale(1, 0.98) skewX(-12deg);
/* --iso-plane mirrored: rising to the right. */
--iso-hero: rotate(-19deg) scale(1, 0.98) skewX(12deg);
--iso-picker: rotate(19deg) scale(1, 0.95) skewX(19deg);
--iso-device: rotate(19deg) skewX(-43deg) scale(1, 0.73);
--iso-slot: rotate(-28deg) skewX(-27deg) scale(1, 0.89);
/* Ink: the foreground, for every tint, hairline and hover fill —
color-mix(in srgb, var(--ink) 5%, transparent). */
--ink: var(--color-foreground-default);
/* Hairline: every visual is a plate in --hl-plate, drawn by its strokes —
the silhouette in --hl-edge, dividers and creases in --hl-mid,
and one highlight in --hl-hi. A plate's thickness is a second silhouette --hl-depth below
it, never a shadow. */
--hl-plate: var(--color-background-default);
--hl-edge: var(--color-border-strong);
--hl-mid: var(--color-border-default);
--hl-hi: var(--color-foreground-default);
/* The walls between a plate and its thickness line, so the piece reads solid. */
--hl-side: var(--color-background-inset);
--hl-depth: 4px;
/* Mild glass: a sheen from the top-left over the plate. Floating plates (--hl-glass) let a
little of the page through, blurred by --hl-blur; solids keep the plate opaque under the
sheen (--hl-solid). */
--hl-sheen: linear-gradient(150deg, rgb(255 255 255 / 2.1%), rgb(255 255 255 / 0%) 45%);
--hl-glass: var(--hl-sheen), color-mix(in srgb, var(--hl-plate) 94%, transparent);
--hl-solid: var(--hl-sheen), var(--hl-plate);
--hl-blur: blur(6px) saturate(121%);
/* The glint (.glint): the light from the top-left catching a plate's rim, brightest at the
top-left and bottom-right corners and tapering along the edges from each. */
--hl-glint: rgb(255 255 255 / 28%);
--hl-glow: rgb(255 255 255 / 7%);
--glint-inset: -1px;
--glass-fill: var(--hl-glass);
--glass-blur: var(--hl-blur);
--glass-rim: var(--hl-edge);
/* Curves, beside --ease-out (enter/leave) and --ease-in-out in landing.css. */
/* A pop with overshoot: ticks, badges, a shield landing. */
--ease-pop: cubic-bezier(0.3, 1.6, 0.5, 1);
/* A harder overshoot, for a knot pulled tight. */
--ease-pop-strong: cubic-bezier(0.3, 1.8, 0.5, 1);
/* A long, soft settle for things travelling a distance: the stack stepping down, a stage
rising into place, a panel lifting on hover. */
--ease-glide: cubic-bezier(0.2, 0.8, 0.2, 1);
/* Leaves fast, settles slowly: a slot sliding in or out of its groove. */
--ease-settle: cubic-bezier(0.16, 1, 0.3, 1);
/* Hover on a plate. */
--glass-solid-hover: var(--color-background-subtle);
/* Avatars: the photo in a gap, inside a hairline ring. */
--av-gap: var(--color-background-default);
/* The small chips (the self-host step numbers, the copy-link tip): a 2px black ring and a
soft drop on dark; light goes without. */
--pill-shadow: 0 0 0 2px rgb(0 0 0 / 40%), 0 1px 2px rgb(0 0 0 / 25%);
}
.landing {
@variant light {
--av-gap: var(--color-background-inset);
--pill-shadow: none;
--hl-sheen: linear-gradient(150deg, rgb(255 255 255 / 36%), rgb(255 255 255 / 0%) 55%);
--hl-glass:
var(--hl-sheen), color-mix(in srgb, var(--color-background-default) 87.6%, transparent);
--hl-solid: var(--hl-sheen), var(--color-background-default);
--hl-glint: rgb(255 255 255 / 42%);
--hl-glow: rgb(255 255 255 / 34%);
--glint-inset: 0;
}
}
.landing {
/* Glass: the faint fill, the page blurred behind it and the piece's own shadow, from
`--glass-bg`, `--glass-backdrop` and `--glass-shadow` (all optional). On 1× screens the
blur goes and the fill turns solid (see .composite). :where() keeps it at no weight:
components set the variables, never `background` / `backdrop-filter` themselves. */
:where(.glass) {
background: var(--glass-bg, var(--glass-fill));
background-clip: padding-box;
box-shadow: var(--glass-shadow, none);
-webkit-backdrop-filter: var(--glass-backdrop, var(--glass-blur));
backdrop-filter: var(--glass-backdrop, var(--glass-blur));
}
/* An iso piece on its own layer, so its skewed edges are filtered, not stair-stepped. */
:where(.composite) {
will-change: transform;
backface-visibility: hidden;
}
/* 1× screens: a composited piece has its text rasterised flat at 1× and then resampled
through the skew — soft. Off its own layer the text is drawn in the skewed space and
stays sharp. */
@media (max-resolution: 1.5dppx) {
:where(.composite) {
will-change: auto;
backface-visibility: visible;
}
}
/* The rim: a gradient ring cut out with a mask — a border can't take a gradient. On the
element's ::before; `--rim` swaps the gradient. It lies just inside
the element's edge; on an element with a border, `--rim-inset: -1px` (minus the border)
lays it over the border instead. The element needs `position: relative` or absolute.
:where() keeps it at one class, so a component's own ::before rules win over it. */
:where(.rim)::before {
content: "";
position: absolute;
inset: var(--rim-inset, 0);
padding: 1px;
border-radius: inherit;
background: var(--rim, var(--glass-rim));
-webkit-mask:
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask:
linear-gradient(#000 0 0) content-box exclude,
linear-gradient(#000 0 0);
pointer-events: none;
}
/* The glint, on the element's ::after: its rim lit, then masked to the two lit corners, each
fading out before the edge reaches the next corner.
It lies over a 1px border (--glint-inset: -1px); light lays it just inside. The element needs `position: relative` or absolute. */
:where(.glint)::after {
content: "";
position: absolute;
inset: var(--glint-inset);
border-radius: inherit;
box-shadow:
inset 0 0 0 1px var(--hl-glint),
inset 0 0 6px var(--hl-glow);
--glint-mask:
radial-gradient(45% 90% at 0 0, #000, rgb(0 0 0 / 35%) 40%, transparent),
radial-gradient(45% 90% at 100% 100%, rgb(0 0 0 / 80%), rgb(0 0 0 / 25%) 40%, transparent);
-webkit-mask: var(--glint-mask);
mask: var(--glint-mask);
pointer-events: none;
}
/* The slab's lower edge as a copy of the rim: an empty sibling under the piece — same
classes but no .glass, so no fill — set --hl-depth down by the component (`translate`). */
:where(.rim-copy) {
pointer-events: none;
}
/* A piece placed in its frame: absolutely, by `translate` from the frame's top-left
to (--x, --y). `--ax` / `--ay` set the anchor on the piece (0% top-left, 50% centre).
Placed by `translate`, so a move between slots is a transform, not layout. */
:where(.at) {
position: absolute;
left: 0;
top: 0;
translate: calc(var(--x, 0px) - var(--ax, 0%)) calc(var(--y, 0px) - var(--ay, 0%));
}
/* An avatar (.avatar) at --av (default 36px): the photo in an --av-gap ring, inside a
hairline circle — all scaled to the size. Logos (.is-logo) fill more of the disc, no gap. */
:where(.avatar) {
--av: 36px;
display: grid;
place-items: center;
flex: none;
width: var(--av);
height: var(--av);
border-radius: 50%;
background: var(--av-gap);
box-shadow: inset 0 0 0 1px var(--hl-edge);
}
:where(.avatar) img {
display: block;
width: calc(var(--av) * 0.855);
height: calc(var(--av) * 0.855);
border-radius: 50%;
object-fit: cover;
box-shadow: 0 0 0 calc(var(--av) * 0.052) var(--av-gap);
}
/* Someone without a face: their initial in the accent, on an accent-tinted disc the photo's
size, in the same --av-gap ring. */
:where(.avatar-initial) {
display: grid;
place-items: center;
width: calc(var(--av) * 0.855);
height: calc(var(--av) * 0.855);
border-radius: 50%;
background: color-mix(in srgb, var(--ind-grid) 16%, var(--av-gap));
box-shadow: 0 0 0 calc(var(--av) * 0.052) var(--av-gap);
font-family: var(--font-display);
font-size: calc(var(--av) * 0.42);
font-weight: 500;
line-height: 1;
color: var(--accent-text);
user-select: none;
}
:where(.avatar.is-logo) img {
width: calc(var(--av) * 0.96);
height: calc(var(--av) * 0.96);
box-shadow: none;
}
}
/* A visual whose Live isn't running (data-held, set from `live.running`) holds its CSS loops
with its timers. !important because each loop's `animation` shorthand resets the play state. */
.landing [data-held] *,
.landing [data-held] *::before,
.landing [data-held] *::after {
animation-play-state: paused !important;
}
/* Shared entrances. On `scale` / `translate`, never `transform` — that holds the iso plane. */
/* A pop with overshoot (use with --ease-pop): a tick, a badge. */
@keyframes d-pop {
from {
opacity: 0;
scale: 0.6;
}
}
/* Rises in 10px. */
@keyframes d-in {
from {
opacity: 0;
translate: 0 10px;
}
}
@keyframes d-fade {
from {
opacity: 0;
}
}
@keyframes d-fade-out {
to {
opacity: 0;
}
}
@keyframes d-spin {
to {
rotate: 360deg;
}
}