Back to the library

Lattice screen reveal

Variant: Ease-out

A panel of thin vertical slats parts to disclose the content behind it — a staggered, screen-like reveal for a card or drawer that has just loaded its data. The slats rotate around their own left edge so the panel thins out instead of sliding away, which keeps the disclosure anchored to where the content already sits. This primary uses a strong ease-out: the slats commit immediately on the first frame and settle without fuss.

revealslatsstaggerdisclosureloader

The source

A complete HTML document. Paste it into an empty .html file and it plays — no stylesheet, script, font, or image to fetch.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Lattice screen reveal — ease-out</title>
<style>
:root {
  /* Neutrals — fixed, never remixed. */
  --ivory: #faf9f5;
  --paper: #ffffff;
  --oat: #e3dacc;
  --slate: #141413;

  /* Knobs — the remix API. A knob exists iff declared here and used below. */
  --hue: 38.8;
  --lift: 0;
  --chroma: 1;
  --round: 1;
  --speed: 1;

  /* Accents — derived from the knobs with the family's offsets baked in,
     so one hue roll moves them together. Defaults reproduce the original hex
     (#d97757 / #b85c3e / #788c5d / #b04a3f). */
  --clay: oklch(calc(0.6724 + var(--lift)) calc(0.1308 * var(--chroma)) var(--hue));
  --clay-d: oklch(calc(0.5797 + var(--lift)) calc(0.127 * var(--chroma)) var(--hue));
  --olive: oklch(calc(0.6118 + var(--lift)) calc(0.0713 * var(--chroma)) calc(var(--hue) + 88.3));
  --rust: oklch(calc(0.5408 + var(--lift)) calc(0.1357 * var(--chroma)) calc(var(--hue) - 10.3));

  /* Clock — every duration in the document runs off --t. */
  --cycle: 4.4s; /* the authored master loop */
  --t: calc(var(--cycle) / var(--speed));

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-accent: cubic-bezier(0.23, 1, 0.32, 1); /* the snippet's ONE characterful curve */

  /* File-specific: per-slat stagger step, kept on the master clock. */
  --_step: calc(0.055s / var(--speed));
}

* { box-sizing: border-box; }

html, body { margin: 0; }

body {
  min-height: 100vh;
  display: grid;
  place-items: center;
  background: var(--ivory);
  font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  color: var(--slate);
}

.card {
  position: relative;
  width: min(360px, 88vw);
  padding: clamp(16px, 4.5vw, 22px);
  background: var(--paper);
  border: 1px solid var(--oat);
  border-radius: calc(14px * var(--round));
  overflow: hidden;
}

.rows {
  display: grid;
  gap: clamp(9px, 2.6vw, 12px);
}

.row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: clamp(9px, 2.6vw, 12px);
  opacity: 0;
  animation: row-in var(--t) var(--ease-out) infinite;
}

.row:nth-child(1) { animation-delay: calc(0.30s / var(--speed)); }
.row:nth-child(2) { animation-delay: calc(0.38s / var(--speed)); }
.row:nth-child(3) { animation-delay: calc(0.46s / var(--speed)); }

.dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--olive);
}
.row:nth-child(2) .dot { background: var(--clay); }
.row:nth-child(3) .dot { background: var(--oat); }

.bar {
  height: 8px;
  border-radius: 999px;
  background: var(--oat);
}
.row:nth-child(1) .bar { width: 82%; }
.row:nth-child(2) .bar { width: 64%; }
.row:nth-child(3) .bar { width: 73%; }

.tag {
  font-size: clamp(11px, 2.9vw, 12px);
  letter-spacing: 0.04em;
  color: var(--clay-d);
}

.caption {
  margin-top: clamp(14px, 3.6vw, 18px);
  font-size: clamp(11px, 2.9vw, 12px);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--slate);
  opacity: 0.42;
}

/* The screen: thin slats that swing open on their own left edge. */
.screen {
  position: absolute;
  inset: 0;
  display: flex;
  pointer-events: none;
}

.slat {
  flex: 1 1 0;
  background: var(--ivory);
  border-right: 1px solid var(--oat);
  transform-origin: left center;
  animation: slat-part var(--t) var(--ease-accent) infinite;
}

.slat:nth-child(1) { animation-delay: calc(var(--_step) * 0); }
.slat:nth-child(2) { animation-delay: calc(var(--_step) * 1); }
.slat:nth-child(3) { animation-delay: calc(var(--_step) * 2); }
.slat:nth-child(4) { animation-delay: calc(var(--_step) * 3); }
.slat:nth-child(5) { animation-delay: calc(var(--_step) * 4); }
.slat:nth-child(6) { animation-delay: calc(var(--_step) * 5); }
.slat:nth-child(7) { animation-delay: calc(var(--_step) * 6); }
.slat:nth-child(8) { animation-delay: calc(var(--_step) * 7); }

/* 0–6% holds the closed screen, 6–17% is the ~500ms opening, the tail re-closes. */
@keyframes slat-part {
  0%, 6%   { transform: scaleX(1); opacity: 1; }
  17%, 84% { transform: scaleX(0.04); opacity: 0; }
  95%, 100% { transform: scaleX(1); opacity: 1; }
}

@keyframes row-in {
  0%, 5%   { opacity: 0; transform: translateY(6px); }
  18%, 82% { opacity: 1; transform: translateY(0); }
  92%, 100% { opacity: 0; transform: translateY(0); }
}

@media (max-width: 380px) {
  .card { padding: 14px; }
}

@media (prefers-reduced-motion: reduce) {
  /* Screen stays parted; only a slow luminance breath survives. */
  .slat {
    animation: slat-fade calc(var(--t) * 2) var(--ease-out) infinite;
    animation-delay: 0s;
    transform: scaleX(0.04);
    opacity: 0;
  }
  .row {
    animation: row-breathe calc(var(--t) * 2) var(--ease-out) infinite;
    transform: none;
  }
  .row:nth-child(1) { animation-delay: 0s; }
  .row:nth-child(2) { animation-delay: calc(0.12s / var(--speed)); }
  .row:nth-child(3) { animation-delay: calc(0.24s / var(--speed)); }

  @keyframes slat-fade {
    0%, 100% { opacity: 0; }
  }
  @keyframes row-breathe {
    0%, 100% { opacity: 0.55; }
    50%      { opacity: 1; }
  }
}
</style>
</head>
<body>
  <div class="card">
    <div class="rows">
      <div class="row"><span class="dot"></span><span class="bar"></span><span class="tag">cedar</span></div>
      <div class="row"><span class="dot"></span><span class="bar"></span><span class="tag">stone</span></div>
      <div class="row"><span class="dot"></span><span class="bar"></span><span class="tag">paper</span></div>
    </div>
    <p class="caption">Contents loaded</p>

    <div class="screen" aria-hidden="true">
      <span class="slat"></span><span class="slat"></span><span class="slat"></span><span class="slat"></span>
      <span class="slat"></span><span class="slat"></span><span class="slat"></span><span class="slat"></span>
    </div>
  </div>
</body>
</html>

The variant family

Same idea, one axis moved. Compare them side by side before you commit.