Device
@sushindustries/uiA phone, a tablet or a laptop in CSS 3D, chosen by the stylesheet rather than by JavaScript, with a real screen in it.
Choosing one by hand
<Device kind="tablet">{...}</Device>Every generated rule is written twice for this: once under its media query for
.device:not([data-device]), once as .device[data-device="tablet"] with no
query at all. The two selectors tie on specificity, the attribute forms come
last, and so a choice beats a width.
That is what a settings panel writes, and it is how a showcase can put all three on one page.
The 3D, and which element carries what
Three flat elements laid out in space. Two properties do the work and putting either on the wrong element breaks the illusion in a way that is hard to name and easy to see.
perspective goes on the outer stage. One vanishing point for the whole
machine. On the body instead, the body gets its own and the deck stops agreeing
with it about where the viewer is standing.
transform-style: preserve-3d goes on the body, so the screen inside it
stays in the same space rather than being flattened into a picture of itself.
rotateX is the tilt, and it is the only reason the perspective has
anything to do. At zero this is an ordinary rectangle and the entire 3D stage
is dead weight.
translateZ orders the screen and the back panel. Inside preserve-3d,
paint order comes from 3D position rather than from the DOM.
The lens is longer on a laptop (1400px) than on a phone (1100px), which is the same instinct as a photographer's: the machine should look like it is being looked at, not like it is being photographed from six inches away.
The back panel must never be clickable
.device-back is inset: 0 over the whole body and comes after the screen
in the DOM. The only thing keeping it behind is preserve-3d honouring its
translateZ(-2px), and any ancestor that flattens the 3D context - a filter,
an overflow, a browser quirk - puts it on top, where it silently swallows
every click on the screen and looks completely fine doing it.
That happened. It is why the panel carries pointer-events: none as well.
The screen is a container
.device-screen declares container-type: inline-size, so everything inside
sizes against the screen rather than against the window.
That is the difference between the machine being a frame and being a costume. A
tablet chosen on a wide monitor is 40rem of screen inside a 1600px window; a
@media query would give its contents a laptop's spacing, which is the exact
thing choosing a tablet was meant to avoid.
.device-desktop {
padding: clamp(var(--s-3), 3cqi, var(--s-4));
}It does not animate open
It used to, on scroll. The lid rose as you reached it.
A frame around real controls should not make you wait to reach them, and this one is full of real controls - folders that open, windows that drag, a dock. The animation was a thing you sat through before the screen became readable, and it ran again every time somebody scrolled back up.
The dock belongs to the screen, not the desktop
The dock is a child of the screen, not of the desktop, so anything it opens is measured against the screen and clipped by the screen. Inside the desktop it would be measured against a scrolled box and cropped by it, which is a bug that looks like a rendering glitch and is really a containing block.
No window controls
Deliberately not three coloured dots. Those are one vendor's furniture, they mean close, minimise and zoom, and none of those three things can happen to this. Drawing controls that do nothing is worse than drawing none.
The windows inside the screen do have those actions, and they have real
buttons for them. See DeskWindow.
Where this is used
| Where | What |
|---|---|
| The home page | with FolderShelf on the screen and a DockDockA launcher, what is open, and a corner. Search opens upward and results are the consumer's to compute.@sushindustries/ui · layout along the bottom |
packages/atoms/devices.md | the three machines, as a table |
packages/atoms/src/devices.css | generated. The widths, as media queries |
packages/ui/src/device-kinds.ts | generated. The same widths, as values |
| useDeviceKinduseDeviceKindWhich machine the stylesheet is currently drawing, as a value. Null until mounted, on purpose - a default would be a claim the server cannot support.@sushindustries/ui · layout | for code that has to name the machine rather than draw it |