Skeleton

Level 1. A shape that stands where a thing will be. 482 marks in 90 hosts on 19 painted screens, three kinds, four widths, and no states at all. Measured 2026-08-08 in a browser, at 390 and 1280.

482 marks 3 kinds 90 hosts 0 states 0 drawn at zero

What this page is for

The level and the reason for it stay on the atoms page. What this page holds is the one thing this component has taught the repository twice: a mark that draws at 0x0 looks exactly like a mark that is not there, and the second lesson is that the reverse is also true.

A mark is a SHAPE, so it needs a box before it needs a host

Every rule in this file used to be scoped under .card.skeleton or .pos.skeleton. The consequence was read across all the painted screens on 2026-08-06: 52 of 482 marks drew at zero size, carrying the fill colour they had been given and occupying nothing.

causewhat it measured
Eight bars inside section.ed-section and div.bp-inner had no height rule at all, because neither host matched them601x0 and 288x0
Twelve bars are <span class="sk-line"> inside <span class="sk-head">, and an inline box ignores height and width0x0

One line fixed both, and display comes first in it on purpose: it is the property that decides whether the other two mean anything. .sk-line,.sk-thumb,.sk-btn{display:block;background:var(--bg-control)} plus a height on each kind. The markup could not be the fix, because a painted screen has a grey twin frozen since stage 04 and the two are compared by hand.

The base rules win nothing. Every scoped rule below them reaches the same element with more classes, so a card mark and a position mark keep exactly the treatment they had. What changed is only where nothing had applied.

Read again today, and the instrument needed the correction this time

readingmarks at 0x0
at 12800 of 482
at 3906 of 482, all six sk-line inside div.bp-inner

The six are not a defect, and a sweep that only counts zeros cannot tell. They stand inside .bet-panel, whose computed display is none below 760px, because the panel is the desktop face of the bet control and the sheet is the phone one. Measured the same way in the opposite direction: 16 of the trader's dock buttons read 0x0 at 1280, for the mirror reason and with nothing wrong with them either.

So the criterion is not "is the box zero", it is "is the box zero while its host is on the screen". That is the same shape as the touch-floor reading of 2026-08-08, where a criterion the system answers CONDITIONALLY was measured without asserting its condition. A zero measured inside a hidden ancestor is the measurement's own condition going unasserted.

The three kinds, and the two hosts

the card host, 68 of the 90

Vault, darkDaylight, light

A thumbnail, a head of two lines, a line, a row of two buttons and a line. It is the card's own layout with every piece of content replaced by its box, which is the only way a loading state can promise what is coming without knowing it.

the position host, 22 of the 90

Vault, darkDaylight, light

The position mark is the only one that is not flat: it takes a three-stop gradient across its width and a 6px corner, where the card mark is one colour and a square end. Two hosts, two treatments, one atom, and the treatments are the hosts' because a row in a list and a card are different materials.

kindmarksat 390at 1280
.sk-line2828 tall, 104 to 248 wide8 tall, 122 to 614 wide
.sk-btn132130x32148x32 and 207x32
.sk-thumb6856x56, and 72x72 on the detail head56x56, and 72x72

The four widths are a vocabulary, not a scale: .w40 100 marks, .w60 90, .w80 74, .w70 18. They are percentages of the row, so the same four classes work in a 267px card and a 614px one, and nothing in this file carries a pixel width except the thumbnail, which is a square.

No states, one movement, and it was argued on 2026-08-15

No hover, no press, no focus, no disabled, and this is the only atom in the system of which all four are true. Nothing can point at a mark, so there is nothing for it to answer.

This section said "nothing shimmers" and said it was an absence rather than a decision, because nobody had argued it either way. The Animation stage argued it. The inventory of moments names three jobs a movement can do, and one of them is to say a process is still running: the product had 482 marks on 19 loading screens and performed that job zero times, so nineteen screens about waiting were drawing a still box. A plate that does not move is not distinguishable from an empty block, which is the reading a person takes from a screen that has stalled.

What it did NOT become is a sweep. A shimmer travelling across the mark is a gradient moving under a mask, and it draws attention to itself rather than to the wait. This is opacity, one of the two properties a browser animates without laying the page out again, which matters here more than anywhere: a feed skeleton is forty-two marks pulsing at once. The amplitude is .55 of full and shallow on purpose, because a plate that goes nearly invisible reads as content flashing in and out rather than as a wait.

MOTION. Job: status. sk-pulse, an opacity cycle at --pulse-period, 1.4s, --ease-standard, infinite, on .sk-line, .sk-thumb and .sk-btn. 482 marks on 19 screens. The period is deliberately NOT one of the two duration tokens: those answer how long a change takes and this answers how often it comes round, so it carries its own name and cannot be mistaken for a third rung.

And under prefers-reduced-motion the cycle is REPLACED, never shortened. The system's mechanism is to redeclare the durations at 1ms, and 1ms per period at infinite repeats is a flicker, which is worse than the still box this component shipped before. So skeleton.css carries its own block: the animation stops and the mark keeps a flat, fully opaque tone. Measured on three loading screens in both engines: 60 marks running in the normal pass, 0 running and every mark back at full opacity under the setting. The state stays and only the movement goes.

What DOES say "loading", to somebody who cannot see a grey box: a sentence, since 2026-08-16, and until then nothing at all. The region is marked busy and the shapes declare themselves decoration - measured, 19 of 19 screens that carry a skeleton carry aria-busy, the two lists are the same 19 files with no odd one in either direction, hosts 88 of 90 - and none of that is a message. aria-busy is a property of a region rather than something announced, so 42 pulsing rectangles were the whole signal.

.sk-status is one sentence, visually hidden, first child of the busy region, and it names what is loading: Loading events, Loading your open bets, Loading the bet panel, one per region, 48 across the two screen trees. It is markup rather than role="status" on the busy container, and that is the whole decision: a live region on .grid would make twelve event cards the thing announced the moment the skeletons are replaced, which is the feed read aloud to somebody who asked whether it had finished. Hidden with clip-path:inset(50%), the idiom base.css already uses, so it is out of the eye and IN the tree: measured, StaticText "Loading events" not ignored, painting 1x1. The grey tree shows the same line as visible text, which is correct rather than an oversight - a wireframe whose region is loading should say so in words, and a grey file links no stylesheet to hide it with.

The two without aria-hidden are one each on event-detail-loading and event-detail-logged-out-loading, which are also the two screens in the section above. A host that does not declare itself decoration reaches the accessibility tree as an empty article. It is a two-attribute fix in both trees, grey first, and it is a row rather than an edit made from this page. docs/backlog.md 56.

The rule

A shape gets its box from itself. A host may say what a mark looks like; it may not be the reason a mark has a size, because a mark with no size still carries a colour and still looks like nothing at all.

A skeleton is the layout with the content taken out. Not a spinner, not a box the size of the region: the same rows, the same rhythm, so nothing jumps when the content lands.

It is decoration and it declares that. The shapes are hidden from assistive technology and the region is marked busy.

The anti-rule

Never scope a shape to a host. Fifty-two marks drew at zero because every rule they needed was written under a class two of their screens did not have.

Never put a height on an inline box. Twelve of the fifty-two were spans inside spans, carrying a colour into a box that ignores both of the properties that would have given it one.

Never report a zero without asking whether its host is on the screen. Six marks read 0x0 at 390 today and all six are correct: they stand in the desktop bet panel, which is display:none on a phone.

Where the rest of it is

The level and its reason: the atoms page. The stylesheet and the argument for the one line that fixed both causes: components/skeleton.css. The hosts: components/card.css and components/position.css. The loading screens themselves: nineteen of the 106 in ui-visual/, and their grey twins in wireframes/.