Back to the library

Pilotis panel lift, slow beat

Variant: Slow beat

The same panel entrance stretched to a heavier single beat — the slab takes roughly twice as long to settle and the dwell before the loop repeats is longer. Use it on large surfaces such as a full-width section or a drawer, where a fast arrival reads as a flicker. Geometry, travel and the ease-out curve are identical to the single-beat sibling; only the clock changed.

entrancecardrevealarchitecturalloop

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>Pilotis panel lift — slow beat</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;
  --t: calc(var(--cycle) / var(--speed));

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);

  --_enter: calc(0.46s / 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);
  padding: clamp(10px, 4vw, 24px);
}

.stage {
  width: min(400px, 90vw);
  display: grid;
  gap: clamp(8px, 2.4vw, 12px);
}

.panel {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--oat);
  border-radius: calc(4px * var(--round));
  padding: clamp(12px, 3.6vw, 18px);
  display: grid;
  gap: clamp(9px, 2.6vw, 13px);
}

.roof {
  height: clamp(8px, 2.4vw, 10px);
  border-radius: calc(2px * var(--round));
  background: var(--olive);
  transform-origin: 0% 100%;
  animation: roof-set var(--t) var(--ease-out) infinite;
}

.slab {
  height: clamp(46px, 15vw, 62px);
  border-radius: calc(3px * var(--round));
  background:
    linear-gradient(90deg, var(--clay) 0 38%, var(--clay-d) 38% 100%);
  animation: slab-lift var(--t) var(--ease-out) infinite;
}

.ribbon {
  position: absolute;
  left: clamp(12px, 3.6vw, 18px);
  right: clamp(12px, 3.6vw, 18px);
  top: calc(clamp(12px, 3.6vw, 18px) + clamp(8px, 2.4vw, 10px) + clamp(9px, 2.6vw, 13px) + clamp(14px, 4.4vw, 19px));
  height: clamp(14px, 4vw, 18px);
  background: var(--ivory);
  border-radius: calc(2px * var(--round));
  transform-origin: 0% 50%;
  animation: ribbon-open var(--t) var(--ease-out) infinite;
}

.piers {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: clamp(10px, 3.2vw, 16px);
  height: clamp(20px, 6vw, 26px);
}

.pier {
  background: var(--slate);
  border-radius: calc(2px * var(--round));
  transform-origin: 50% 100%;
  animation: pier-raise var(--t) var(--ease-out) infinite;
}

.caption {
  font-size: clamp(11px, 3vw, 12px);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--slate);
  opacity: 0.5;
  text-align: center;
}

/* 10.5% of a 4.4s loop ≈ 460ms — twice the single-beat entrance,
   with the extra loop length spent dwelling rather than travelling. */
@keyframes slab-lift {
  0%     { opacity: 0; transform: translate3d(0, 5%, 0) scale(0.97); }
  10.5%  { opacity: 1; transform: none; }
  100%   { opacity: 1; transform: none; }
}

@keyframes roof-set {
  0%     { opacity: 0; transform: translate3d(0, 6px, 0) scaleX(0.94); }
  10.5%  { opacity: 1; transform: none; }
  100%   { opacity: 1; transform: none; }
}

@keyframes ribbon-open {
  0%     { opacity: 0; transform: scaleX(0.04); }
  10.5%  { opacity: 1; transform: scaleX(1); }
  100%   { opacity: 1; transform: scaleX(1); }
}

@keyframes pier-raise {
  0%     { opacity: 0; transform: scaleY(0.35); }
  10.5%  { opacity: 1; transform: scaleY(1); }
  100%   { opacity: 1; transform: scaleY(1); }
}

@media (max-width: 380px) {
  .panel { padding: 12px; }
  .caption { letter-spacing: 0.1em; }
}

@media (prefers-reduced-motion: reduce) {
  .slab, .roof, .ribbon, .pier {
    animation: soft-breathe calc(var(--cycle) * 2.4 / var(--speed)) ease-in-out infinite;
    transform: none;
  }
  @keyframes soft-breathe {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.62; }
  }
}
</style>
</head>
<body>
  <div class="stage">
    <div class="panel">
      <div class="roof"></div>
      <div class="slab"></div>
      <div class="ribbon"></div>
      <div class="piers">
        <span class="pier"></span>
        <span class="pier"></span>
        <span class="pier"></span>
        <span class="pier"></span>
        <span class="pier"></span>
      </div>
    </div>
    <p class="caption">Section ready</p>
  </div>
</body>
</html>

The variant family

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