@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; } }