Skip to content

COMPONENT

Lamp

Text that strikes like a fluorescent tube the first time it comes into view. One class, one attribute, and nothing whatsoever without a script — with none, the element is text this stylesheet has never touched.

Scroll to it, or press replay

SELECTED WORK

HTML
<h2 class="lamp" x-ds-reveal>SELECTED WORK</h2>

IT HIDES NOTHING UNTIL A SCRIPT SAYS SO

Every rule in this component is keyed on data-ds-reveal, and only x-ds-reveal ever writes it. With no JavaScript the attribute never appears, not one declaration matches, and the heading is text this stylesheet has never touched — not text that happens to be visible. A reveal that hides its host in CSS and waits for a script to undo it is one broken bundle away from a blank page, and search engines are the last to be told.

The glow is currentColor

A lamp glows in its own light, so there is nothing to configure and nothing to re-theme: cream text blooms cream, accent text blooms accent, and a theme nobody has written yet is already correct. Same reasoning as ripple, and the alternative here is worse — a colour role would have to answer which of the eleven inks, and every answer is wrong on some surface.

The travelling strike

.lamp-spread adds a mask, so the light catches at one point and runs outward through the letters instead of arriving everywhere at once. It catches at a fraction of the element, because CSS cannot mask relative to a run of text — on a heading that fills its box that is exactly right, and on a short word in a very wide box the light catches past the end of it and the effect degrades to the plain lamp. That is why the plain lamp is the base case.

Scroll to it, or press replay

SELECTED WORK

HTML
<h2 class="lamp lamp-spread" x-ds-reveal>SELECTED WORK</h2>

A row of them

Four tubes striking in unison read as one event rather than four objects, so a row wants staggering. --lamp-delay is zero by default and set per instance.

Scroll to it, or press replay

ENGINE
FRONT
RESCUE
CREATION
HTML
<div class="hairline-grid" style="--cell: 150px; width: 100%">
  <div class="hairline-cell">
    <span class="fact-n lamp" x-ds-reveal>ENGINE</span>
  </div>
  <div class="hairline-cell">
    <span class="fact-n lamp" x-ds-reveal style="--lamp-delay: 140ms">FRONT</span>
  </div>
  <div class="hairline-cell">
    <span class="fact-n lamp" x-ds-reveal style="--lamp-delay: 280ms">RESCUE</span>
  </div>
  <div class="hairline-cell">
    <span class="fact-n lamp" x-ds-reveal style="--lamp-delay: 420ms">CREATION</span>
  </div>
</div>

Reference

Read from components/lamp.css when this page was built, so it cannot fall behind the stylesheet.

CLASSES.lamp.lamp-spread
SETTINGSSet the first on one element; a theme sets the second for all of them.--lamp-duration — falls back to --ds-lamp-duration--lamp-strike — falls back to --ds-lamp-strike--lamp-bloom — falls back to --ds-lamp-bloom
READSNothing from the theme — it is arrangement only.

Notes

Where it must not go

Not on the largest thing above the fold. Chrome refuses to count an opacity: 0 element as painted, so hiding whatever a page's Largest Contentful Paint element happens to be defers LCP by the whole reveal. Measured on a real page: hiding four sub-headings moved LCP by nothing at all, because they were never the largest; hiding the hero title moved it from 268ms to 1112ms.

Layout is untouched either way. Opacity and masks are composited, so Cumulative Layout Shift measured identical to four decimal places with and without. The cost is never reflow; it is only ever LCP, and only on one element per page.

Tuning it

--lamp-durationHow long the tube takes to settle.
--lamp-bloomThe glow's radius at the brightest moment. In em, so one value serves a chip and a headline.
--lamp-strikeWhere .lamp-spread catches, as a fraction across the element.
--lamp-delayZero by default. Set it to stagger a row.