Back to the library

Signature edge save

Variant: Standard

The same fore-edge save indicator at a normal UI weight: thicker page rules, a fuller edge stain, and a seal mark you can read at a glance across a panel. Use it as the default when the badge sits alone in a sidebar footer or toolbar rather than inline with type. Only stroke, travel and opacity change from the hairline sibling — the settle stagger stays at 46ms and the sweep stays linear so the rhythm is identical.

savestatusambienteditorialloop

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>Signature edge save — standard</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);
    --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);

    /* Weight tokens — the one axis this family varies. */
    --_rule: 2px;
    --_travel: 9%;
    --_ink: 0.78;
    --_seal: 3px;
    --_stain: 0.72;
  }

  * { box-sizing: border-box; }

  body {
    margin: 0;
    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 {
    width: min(420px, 92vw);
    padding: clamp(16px, 5vw, 26px);
    background: var(--paper);
    border: 1px solid var(--oat);
    border-radius: calc(10px * var(--round));
    display: grid;
    gap: clamp(12px, 3.4vw, 18px);
  }

  .head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
  }

  .title {
    font-size: clamp(12px, 3.3vw, 13.5px);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    font-weight: 600;
  }

  .state {
    font-size: clamp(11px, 3vw, 12px);
    color: var(--clay-d);
    letter-spacing: 0.04em;
    opacity: 0.35;
    animation: state-ink var(--t) var(--ease-out) infinite;
  }

  .block {
    position: relative;
    height: clamp(86px, 26vw, 112px);
    overflow: hidden;
    border-radius: calc(4px * var(--round));
  }

  .rules {
    position: absolute;
    inset: 0;
    display: grid;
    grid-template-rows: repeat(7, 1fr);
    align-content: stretch;
  }

  .rule {
    position: relative;
    align-self: center;
    height: var(--_rule);
    background: var(--slate);
    opacity: 0;
    transform-origin: left center;
    /* Pages settle in from the spine side; ease-out so the stack lands. */
    animation: page-settle var(--t) var(--ease-out) infinite;
    animation-delay: calc(var(--_i) * 46ms / var(--speed));
  }

  .rule:nth-child(1) { --_i: 0; width: 88%; }
  .rule:nth-child(2) { --_i: 1; width: 94%; }
  .rule:nth-child(3) { --_i: 2; width: 79%; }
  .rule:nth-child(4) { --_i: 3; width: 96%; }
  .rule:nth-child(5) { --_i: 4; width: 84%; }
  .rule:nth-child(6) { --_i: 5; width: 91%; }
  .rule:nth-child(7) { --_i: 6; width: 72%; }

  .stain {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 100%;
    background: linear-gradient(90deg,
      color-mix(in oklab, var(--clay) 0%, transparent) 0%,
      var(--clay) 42%,
      var(--clay) 58%,
      color-mix(in oklab, var(--clay) 0%, transparent) 100%);
    opacity: var(--_stain);
    transform: translateX(-100%);
    mix-blend-mode: multiply;
    animation: edge-sweep var(--t) linear infinite;
  }

  .seal {
    position: absolute;
    top: 12%;
    bottom: 12%;
    right: 2%;
    width: var(--_seal);
    background: var(--rust);
    transform-origin: center bottom;
    transform: scaleY(0.04);
    opacity: 0;
    animation: seal-close var(--t) var(--ease-out) infinite;
  }

  .foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: clamp(10.5px, 2.9vw, 11.5px);
    letter-spacing: 0.06em;
    color: color-mix(in oklab, var(--slate) 52%, var(--paper));
  }

  .tick {
    width: clamp(46px, 14vw, 62px);
    height: 3px;
    background: var(--oat);
    border-radius: 999px;
    overflow: hidden;
  }

  .tick i {
    display: block;
    height: 100%;
    width: 100%;
    background: var(--olive);
    transform-origin: left center;
    transform: scaleX(0);
    animation: tick-fill var(--t) linear infinite;
  }

  @keyframes page-settle {
    0%       { opacity: 0; transform: translateX(calc(var(--_travel) * -1)) scaleX(0.985); }
    16%, 74% { opacity: var(--_ink); transform: translateX(0) scaleX(1); }
    90%, 100%{ opacity: 0; transform: translateX(0) scaleX(1); }
  }

  @keyframes edge-sweep {
    0%, 34%   { transform: translateX(-100%); }
    70%, 100% { transform: translateX(100%); }
  }

  @keyframes seal-close {
    0%, 62%   { opacity: 0; transform: scaleY(0.04); }
    76%, 88%  { opacity: 1; transform: scaleY(1); }
    100%      { opacity: 0; transform: scaleY(1); }
  }

  @keyframes tick-fill {
    0%        { transform: scaleX(0); }
    72%, 100% { transform: scaleX(1); }
  }

  @keyframes state-ink {
    0%, 58%   { opacity: 0.35; }
    76%, 92%  { opacity: 1; }
    100%      { opacity: 0.35; }
  }

  @media (max-width: 380px) {
    .card { padding: 14px; gap: 11px; }
    .block { height: 82px; }
  }

  @media (prefers-reduced-motion: reduce) {
    .rule {
      opacity: var(--_ink);
      transform: none;
      animation: page-hold calc(var(--t) * 2) var(--ease-in-out) infinite;
      animation-delay: calc(var(--_i) * 46ms / var(--speed));
    }
    .stain {
      transform: none;
      animation: stain-breathe calc(var(--t) * 2) var(--ease-in-out) infinite;
    }
    .seal {
      transform: scaleY(1);
      animation: seal-hold calc(var(--t) * 2) var(--ease-in-out) infinite;
    }
    .tick i {
      transform: scaleX(1);
      animation: state-ink calc(var(--t) * 2) var(--ease-in-out) infinite;
    }
    .state { animation: state-ink calc(var(--t) * 2) var(--ease-in-out) infinite; }
  }

  @keyframes page-hold {
    0%, 100% { opacity: calc(var(--_ink) * 0.55); }
    50%      { opacity: var(--_ink); }
  }
  @keyframes stain-breathe {
    0%, 100% { opacity: calc(var(--_stain) * 0.4); }
    50%      { opacity: var(--_stain); }
  }
  @keyframes seal-hold {
    0%, 100% { opacity: 0.4; }
    50%      { opacity: 1; }
  }
</style>
</head>
<body>
  <div class="card">
    <div class="head">
      <span class="title">Draft block</span>
      <span class="state">committing</span>
    </div>

    <div class="block">
      <div class="rules">
        <span class="rule"></span>
        <span class="rule"></span>
        <span class="rule"></span>
        <span class="rule"></span>
        <span class="rule"></span>
        <span class="rule"></span>
        <span class="rule"></span>
      </div>
      <div class="stain"></div>
      <div class="seal"></div>
    </div>

    <div class="foot">
      <span>edge sealed</span>
      <span class="tick"><i></i></span>
    </div>
  </div>
</body>
</html>

The variant family

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