Back to the library

Colonnade deploy, overshoot

Variant: Overshoot

The same colonnade build with a spring-like curve on every entrance. Each column rises a few units past the stylobate before it drops back, and the beam and pediment dip slightly as they land. Use it where the product voice is warmer and a deploy should feel like something to celebrate. The overshoot is kept to about 45% curve tension, so the lift reads as a settle rather than a bounce, and the columns rise inside a clip region with headroom so the springing never gets cut off.

deployconfirmationstaggerspringarchitecture

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" />
<meta name="animation-knobs" content="overshoot: ok" />
<title>Colonnade deploy, overshoot</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; /* include ONLY if at least one border-radius is wrapped below */
  --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: 3.6s; /* the authored master loop */
  --t: calc(var(--cycle) / var(--speed));

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1); /* include only if used */
  --ease-accent: cubic-bezier(0.34, 1.45, 0.64, 1); /* the snippet's ONE characterful curve, if it has one */
}

* { box-sizing: border-box; }

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

.card {
  display: grid;
  justify-items: center;
  gap: clamp(8px, 2.5vw, 14px);
  padding: clamp(16px, 4vw, 24px) clamp(20px, 6vw, 36px);
  background: var(--paper);
  border: 1px solid var(--oat);
  border-radius: calc(16px * var(--round));
}

.temple {
  display: block;
  width: min(220px, 60vw, 72vh);
  height: auto;
  overflow: visible;
}

.marble { fill: var(--ivory); stroke: var(--oat); stroke-width: 1.5; }
.flute { fill: none; stroke: var(--oat); stroke-width: 1; }
.stone { fill: var(--oat); }
.triglyph { fill: var(--olive); }
.pediment-face { fill: var(--clay); }
.tympanum { fill: none; stroke: var(--clay-d); stroke-width: 1.5; stroke-linejoin: round; }

.col, .beam, .pediment { transform-box: fill-box; }

/* 60ms per column: five columns finish inside ~750ms, before the beam lands. */
.col {
  animation: rise var(--t) infinite both;
  animation-delay: calc(var(--_i) * 0.06s / var(--speed));
}
.beam { --_i: 6; animation: cap-beam var(--t) infinite both; }
.pediment { --_i: 8; animation: cap-pediment var(--t) infinite both; }

.status {
  display: grid;
  font-size: clamp(11px, 3.2vw, 13px);
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  line-height: 1.3;
}
.status > span {
  grid-area: 1 / 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
}
.building { color: var(--clay-d); animation: say-building var(--t) infinite both; }
.deployed { color: var(--slate); animation: say-deployed var(--t) infinite both; }

.seal { width: 1.3em; height: 1.3em; flex: none; }
.seal circle { fill: var(--olive); }
.seal path { fill: none; stroke: var(--paper); stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }

@keyframes rise {
  0%, 6% { opacity: 0; transform: translateY(104%); animation-timing-function: var(--ease-accent); }
  14% { opacity: 1; transform: translateY(0); }
  86% { opacity: 1; transform: translateY(0); animation-timing-function: var(--ease-out); }
  91%, 100% { opacity: 0; transform: translateY(0); }
}

@keyframes cap-beam {
  0%, 22% { opacity: 0; transform: translateY(-8px); animation-timing-function: var(--ease-accent); }
  29% { opacity: 1; transform: translateY(0); }
  85% { opacity: 1; transform: translateY(0); animation-timing-function: var(--ease-out); }
  89%, 100% { opacity: 0; transform: translateY(0); }
}

@keyframes cap-pediment {
  0%, 27% { opacity: 0; transform: translateY(-12px); animation-timing-function: var(--ease-accent); }
  35% { opacity: 1; transform: translateY(0); }
  84% { opacity: 1; transform: translateY(0); animation-timing-function: var(--ease-out); }
  88%, 100% { opacity: 0; transform: translateY(0); }
}

@keyframes say-building {
  0%, 36% { opacity: 1; animation-timing-function: var(--ease-out); }
  40% { opacity: 0; }
  88% { opacity: 0; animation-timing-function: var(--ease-out); }
  93%, 100% { opacity: 1; }
}

@keyframes say-deployed {
  0%, 39% { opacity: 0; transform: translateY(4px); animation-timing-function: var(--ease-accent); }
  46% { opacity: 1; transform: translateY(0); }
  84% { opacity: 1; transform: translateY(0); animation-timing-function: var(--ease-out); }
  88%, 100% { opacity: 0; transform: translateY(0); }
}

@keyframes rest-rm {
  0%, 6% { opacity: 0.3; animation-timing-function: var(--ease-out); }
  16% { opacity: 1; }
  86% { opacity: 1; animation-timing-function: var(--ease-out); }
  92%, 100% { opacity: 0.3; }
}

@keyframes say-deployed-rm {
  0%, 39% { opacity: 0; animation-timing-function: var(--ease-out); }
  46% { opacity: 1; }
  84% { opacity: 1; animation-timing-function: var(--ease-out); }
  88%, 100% { opacity: 0; }
}

@media (max-width: 380px) {
  .card { padding: 14px 18px; gap: 8px; }
  .status { letter-spacing: 0.16em; }
}

/* Stone stays put; the build reads as stones brightening in sequence at half pace. */
@media (prefers-reduced-motion: reduce) {
  .col, .beam, .pediment {
    animation: rest-rm calc(var(--t) * 2) infinite both;
    animation-delay: calc(var(--_i) * 0.08s / var(--speed));
  }
  .building { animation-duration: calc(var(--t) * 2); }
  .deployed { animation: say-deployed-rm calc(var(--t) * 2) infinite both; }
}
</style>
</head>
<body>
  <div class="card">
    <svg class="temple" viewBox="0 0 200 120" aria-hidden="true">
      <defs>
        <!-- Clip ends at the stylobate but extends 20 units above the frame so the overshoot has headroom. -->
        <clipPath id="ground"><rect x="0" y="-20" width="200" height="124" /></clipPath>
      </defs>

      <g class="pediment">
        <polygon class="pediment-face" points="16,40 100,10 184,40" />
        <polygon class="tympanum" points="42.6,36.5 100,16 157.4,36.5" />
      </g>

      <g class="beam">
        <rect class="stone" x="20" y="40" width="160" height="10" />
        <rect class="triglyph" x="38" y="42" width="4" height="6" />
        <rect class="triglyph" x="68" y="42" width="4" height="6" />
        <rect class="triglyph" x="98" y="42" width="4" height="6" />
        <rect class="triglyph" x="128" y="42" width="4" height="6" />
        <rect class="triglyph" x="158" y="42" width="4" height="6" />
      </g>

      <g clip-path="url(#ground)">
        <g class="col" style="--_i: 0">
          <rect class="marble" x="31" y="50" width="18" height="4" />
          <rect class="marble" x="34" y="54" width="12" height="46" />
          <rect class="marble" x="32" y="100" width="16" height="4" />
          <path class="flute" d="M38 57V97M42 57V97" />
        </g>
        <g class="col" style="--_i: 1">
          <rect class="marble" x="61" y="50" width="18" height="4" />
          <rect class="marble" x="64" y="54" width="12" height="46" />
          <rect class="marble" x="62" y="100" width="16" height="4" />
          <path class="flute" d="M68 57V97M72 57V97" />
        </g>
        <g class="col" style="--_i: 2">
          <rect class="marble" x="91" y="50" width="18" height="4" />
          <rect class="marble" x="94" y="54" width="12" height="46" />
          <rect class="marble" x="92" y="100" width="16" height="4" />
          <path class="flute" d="M98 57V97M102 57V97" />
        </g>
        <g class="col" style="--_i: 3">
          <rect class="marble" x="121" y="50" width="18" height="4" />
          <rect class="marble" x="124" y="54" width="12" height="46" />
          <rect class="marble" x="122" y="100" width="16" height="4" />
          <path class="flute" d="M128 57V97M132 57V97" />
        </g>
        <g class="col" style="--_i: 4">
          <rect class="marble" x="151" y="50" width="18" height="4" />
          <rect class="marble" x="154" y="54" width="12" height="46" />
          <rect class="marble" x="152" y="100" width="16" height="4" />
          <path class="flute" d="M158 57V97M162 57V97" />
        </g>
      </g>

      <rect class="stone" x="14" y="104" width="172" height="6" />
      <rect class="stone" x="8" y="111" width="184" height="7" opacity="0.7" />
    </svg>

    <div class="status" role="status">
      <span class="building">Building</span>
      <span class="deployed">
        <svg class="seal" viewBox="0 0 20 20" aria-hidden="true">
          <circle cx="10" cy="10" r="10" />
          <path d="M6 10.4l2.7 2.7L14.2 7.4" />
        </svg>
        Deployed
      </span>
    </div>
  </div>
</body>
</html>

The variant family

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