Samedi 4 octobre · Ksar Ghilane · Tozeur
Origins
Le désert, un line-up international et rien d'autre à des kilomètres.
Every primitive, in every variant. Nothing ships to a product page until it renders correctly here in both themes.
theme.css · @layer components
Every surface is exactly one of these. A resting card casts e1 and lifts to e2 under the pointer — the third edition of the elevation doctrine, after «cast nothing» made the product read flat. Shadows are wide, deep and low-alpha ink; a tight contact line is what the second edition got wrong.
shadow-e0 … shadow-e5
Soft, wide and deep at low alpha — a bloom, not a contact line. e1 is what every resting card gets, e2 is its hover, e3 a dropdown, e5 the modal. Untinted near-black ink: a brand-tinted shadow at this spread reads as a colour cast rather than as light, which is what «looks like PowerPoint» meant. The one exception is `featured`, whose shadow is brand-tinted because the object casting it is.
.gradient-brand · .gradient-signature · .gradient-sheen
Derived entirely from the brand ramp and the supplied aurora accents — no new hues. Reserved for brand moments: the hero, the logo mark, a ticket. They are no longer on cards or buttons, which is where they read as gloss.
duration-(--duration-*) · ease-*
Five durations, five easings. Hover each tile. Everything here is CSS — no animation library, and the global prefers-reduced-motion rule in theme.css switches all of it off in one place rather than per component.
<x-ui.event-spotlight />
Une diapositive du carrousel d'accueil. Trois couches : un fond pleine largeur qui est une copie floutée de l'affiche de l'événement lui-même, une carte arrondie qui porte la bannière nette, et un panneau givré en bas. ⚠ Les deux flous ne sont pas la même technique : le fond utilise filter: blur() sur une image statique — nous contrôlons ce qu'il y a derrière, donc flouter l'image directement est identique à l'œil et ne coûte aucune couche de composition. Le panneau utilise le vrai .glass, parce qu'il doit flouter du contenu vivant, la photo nette en dessous. C'est la deuxième des trois surfaces autorisées.
Samedi 4 octobre · Ksar Ghilane · Tozeur
Le désert, un line-up international et rien d'autre à des kilomètres.
<x-ui.confetti />
La rafale d'arrivée : une seule fois dans tout le produit, quand l'acheteur atterrit sur ses billets après avoir payé. Zéro octet de JavaScript — trente pièces, une keyframe partagée, transform et opacity uniquement, donc le compositeur s'en occupe sur son propre thread. Sous prefers-reduced-motion elle disparaît entièrement plutôt que de clignoter : la règle globale de theme.css réduit la durée à 0,01 ms, ce qui pour ces pièces serait un flash plein écran — pire que l'animation, et précisément pour la personne qui a demandé moins de mouvement. Rechargez la page pour la rejouer.
Paiement confirmé
<x-ui.label>
<x-ui.text-field>
The notched floating label, Google's outlined field reproduced with a real <fieldset>/<legend> so the browser cuts the gap natively. No JavaScript: the float is :focus or :not(:placeholder-shown). Click into one, then type.
Le billet est envoyé à cette adresse.
Ce numéro ne semble pas valide.
Cliquez pour ouvrir le calendrier.
<x-ui.checkbox>
A real <input type=checkbox> with the OS paint removed by appearance:none — not a hidden input behind a fake box. :checked and :indeterminate do the styling, so it is correct before JavaScript loads.
<x-ui.radio>
Same construction. A radio group also needs a <fieldset>/<legend> around it — the caller's job, since only the caller knows what the group is called.
<x-ui.switch>
Also a real checkbox, with role=switch. The usual div-plus-Alpine switch submits nothing with the form and stops working if the bundle fails — for a control that decides whether an event is on sale, that is not cosmetic.
<x-ui.money>
Every dinar in the product renders through this. It takes a Money value object or raw millimes and never does arithmetic on either — the columns are cast to Money precisely so that dividing them is impossible, and the checkout's first draft fatally errored trying. Tabular figures come from Onest's own tnum, so a column lines up without changing typeface.
| Catégorie | Vendus | Total |
|---|---|---|
| Général | 214 | 9 630,000 TND |
| Pelouse VIP | 147 | 13 230,000 TND |
| Carré Or | 50 | 9 000,000 TND |
<x-ui.avatar>
Initials rather than a broken-image icon, and the full name in an sr-only span so a screen reader hears the person, not two letters.
<x-ui.badge>
Solid and saturated — the opposite of the alert. A 20px chip has too little area for a pastel to read as a colour at all, which is why the old /15 alpha tints looked like grey pills. Scarcity is a sales signal, not an error: `inventory` must never render in the same red as a failure.
<x-ui.alert>
Your hold expires in 12 minutes
Payment received
Only 3 left
Payment declined
Payment in flight
Order abandoned
<x-ui.scan-result>
Exactly the five states the gate can produce — three from `ScanOutcome::signal()`, two from the device. A duplicate is amber, never red: REQ-ACC-03 makes it a staff judgement call, and red decides it before the volunteer has read the line. The two neutral states are not decisions and must never look like one.
ADMIS
Accès général · Porte Nord
ETK-4H7K-2P9R
DÉJÀ SCANNÉ
21h34 par Ahmed · Porte Nord
ETK-4H7K-2P9R
Billet remboursé
Annulé le 14 septembre
ETK-9QW2-XR41
EN ATTENTE
Hors ligne — sera vérifié au retour du réseau
ETK-1TT8-BB23
ERREUR
Le serveur n'a pas répondu
ÉTAT INCONNU
Ce que produit un état inconnu
<x-ui.card>
Every card responds on hover — surface, stroke and shadow move together, no prop required. `interactive` adds the 1px lift and the focus ring, and means «clickable», not «alive»: a card that moves when it cannot be clicked is a lie about affordance. `glass` stays rationed to three surfaces platform-wide.
The one filled card per view. Two on a screen is none — the scarcity is the signal. Brand-tinted shadow, because an ink shadow under a blue panel reads as dirt.
| Porte | Scans |
|---|---|
| Nord | 412 |
| Sud | 288 |
<x-ui.table>
Alignment is text-start, never text-left, so RTL is a token flip. Hover a row: a neutral tint rather than the brand accent, because a blue wash reads as selected and the row is only under the pointer.
| Référence | Acheteur | Billets | Total |
|---|---|---|---|
| ETK-4H7K-2P9R | Amine B. | 2 | 84.000 |
| ETK-9QW2-XR41 | Sarra M. | 1 | 42.000 |
| Heure | Porte | Billet | Résultat |
|---|---|---|---|
| 21:34:02 | Nord | ETK-4H7K-2P9R | Admis |
| 21:34:11 | Nord | ETK-9QW2-XR41 | Déjà scanné |
| 21:34:19 | Sud | ETK-1TT8-BB23 | Admis |
<x-ui.modal-shell>
A native <dialog>: focus trapping, Escape and the top layer come free. `tone` bands the HEADER only — «Rembourser 340.000 TND ?» and «Enregistrer ?» must not open the same-looking box, but a dialog whose whole surface is red turns its buttons into shapes on a coloured field, and the button is what the person has to aim at.
<x-ui.spinner>
Inline SVG: the one icon shown while the network is what we are waiting on.
<x-ui.skeleton>
Fixed heights: a loading state that resizes on arrival blows the CLS budget.
<x-ui.empty-state>
Distinct from an error on purpose: 'nothing yet' and 'it broke' look identical unless we say which.
No orders yet
Sales appear here the moment your first ticket is bought.
No scans recorded
<x-ui.event-card>
Image-led listing card. The scrim is a contrast guarantee, not decoration: covers are organizer-supplied and can be anything, so the text's ground is ours regardless of what was uploaded. Hover lifts the card and zooms the image — never the card, which would scale its own type.
<x-ui.event-hero>
The cover floats over a blurred, scaled copy of itself. This is filter: blur() on a static image — a one-time raster — NOT backdrop-filter, which re-blurs a live layer every scroll frame and is the documented lag source the glass budget caps. Same look, no per-frame cost.
Zerda Events
Samedi 8 août · portes 20h00
La Marsa, Tunis
<x-ui.genre-icon>
One outlined mark per event genre, for the browsing strip and the header menu. Inline SVG rather than an icon package: the storefront's whole script budget is ~2 KB gzipped, and pulling a set to draw seven glyphs would cost hundreds of kilobytes of source for eight paths. Stroke and currentColor only, so it inherits the chip's text colour in both themes and owns no token of its own. ⚠ `genres.icon` has existed since the genre work and was read by nothing — GenreSeeder writes it and no view had ever rendered it, the same shape as venues.lat/lng before the venue picker. ⚠ An unknown name draws the CALENDAR rather than nothing: a genre is a row somebody adds without a deploy, so the eighth genre arrives before its glyph, and an empty box beside a label reads as a broken image where a calendar reads as « an event ».
<x-ui.carousel>
A scroll-snap track, not a stack of toggled slides — so swipe, momentum and keyboard scrolling are the browser's, and with JavaScript off it degrades to a swipeable row instead of one slide under two dead buttons. The bullets are built from the track's real children and double as a progress indicator: the active one widens and fills across the dwell time. Under prefers-reduced-motion the timer never starts and the pause control is hidden, because a pause button over something that is not moving is a lie about the interface. Controls sit BELOW the slides, on the canvas, so they use semantic tokens rather than gambling white-on-artwork.
<x-ui.ticket>
The art is above the perforation; the QR sits below it on a flat white panel with a real quiet zone, in BOTH themes. A scanner reads reflectance, not our dark-mode preference, and nothing may ever be placed behind that panel. The notches are PAINTED, not cut — they take their colour from `--perf-ground`, overridden here because these tickets sit on a white panel rather than on the canvas.
Samedi 8 août · 20h00
La Marsa, Tunis
22 septembre · 21h00
Tozeur
Ce billet a été remboursé le 2 août. Il ne donne plus accès à l'événement.
<x-ui.line-icon>
THE ONE DOOR TO THE ICON PACK (§19): a semantic name in, a **Heroicons outline** glyph out, normalised to one size and `currentColor` so it takes the colour of whatever it sits inside. ⚠ This note used to say *"there is no icon library in this project and there is not going to be one… a package is tens of kilobytes on a page whose whole script budget is ~2 KB gzipped"* — **wrong twice**: `blade-heroicons` has been installed all along as a Filament dependency, and `blade-icons` is a SERVER-SIDE PHP package that ships nothing to the browser but the one `<svg>` it prints. The reasoning confused a composer package with a bundled JS icon library, and the result was two people's idea of a chevron shipping side by side. An UNKNOWN name renders NOTHING — deliberately the opposite of `genre-icon`, whose fallback is a neutral calendar. A genre is a row somebody adds in the back office, so the eighth genre arrives before its glyph and needs something to draw; these names are written by us in a view, so a typo is a bug and a placeholder would hide it.
<x-ui.participant-card>
One collapsible per admission on a nominative order (`REQ-CAT-11`). It is a NATIVE `details` element with a shared `name`, so open, close, keyboard focus, Escape and the exclusive behaviour are the platform's and cost no JavaScript. THREE STATES, NOT TWO: « À renseigner » is work not begun and « À compléter » is work somebody thinks they finished — the half-typed card is the one that gets missed, and one word for both hides it. The collapsed row carries the name and the ticket type and NOT the e-mail: a checkout page is screenshotted and shown to whoever is paying. The save button below the fields is REMOVED by `app.js` once autosave is live; it ships so the page still works with the bundle blocked, which matters most for a buyer with money in flight.