Back to the library

Cover lens scan

Variant: Eased

A framed lens moves back and forth across a masked key. Wherever it passes, the real characters show through, so the user can see what the scanner is reading while the rest of the value stays hidden. Use it as the in-progress state for secret scanning, credential checks or redaction review. The lens moves on screen, so it uses a strong ease-in-out: it leaves each group slowly, crosses the middle quickly and settles squarely on the far group before the hold.

scanrevealloadersecurity

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>Cover lens scan — Eased</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.2s; /* the authored master loop */
  --t: calc(var(--cycle) / var(--speed));

  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); /* include only if used */
}

* { 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 {
  width: min(420px, 92vw);
  padding: clamp(16px, 5vw, 24px);
  background: var(--paper);
  border-radius: calc(14px * var(--round));
  box-shadow: 0 1px 2px color-mix(in oklch, var(--slate) 6%, transparent),
              0 10px 28px -14px color-mix(in oklch, var(--slate) 22%, transparent);
}

.head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: clamp(12px, 3.5vw, 16px);
}

.label { font-size: clamp(13px, 3.6vw, 14px); font-weight: 600; }

.tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--clay-d);
}

.tag::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--clay);
}

.track {
  position: relative;
  height: clamp(44px, 12vw, 52px);
  border-radius: calc(8px * var(--round));
  background: color-mix(in oklch, var(--oat) 40%, var(--paper));
}

.groups {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  align-items: center;
  justify-items: center;
  height: 100%;
  font-size: clamp(14px, 4.2vw, 17px);
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
}

.mask {
  color: color-mix(in oklch, var(--slate) 38%, transparent);
  letter-spacing: 0.14em;
}

/* Lens is 25% of the track, so 300% of its own width lands it on the last group.
   The inner copy is 400% of the lens (= track width) and counter-moves by 75%
   of itself — the same keyframes and curve make the two cancel on every frame,
   so the real characters stay pinned while the window slides over them. */
.lens {
  position: absolute;
  top: 0;
  left: 0;
  width: 25%;
  height: 100%;
  overflow: hidden;
  border-radius: calc(8px * var(--round));
  background: var(--paper);
  box-shadow: inset 0 0 0 1.5px var(--clay),
              0 6px 14px -6px color-mix(in oklch, var(--clay-d) 45%, transparent);
  animation: lens var(--t) var(--ease-in-out) infinite;
  will-change: transform;
}

.lens-inner {
  position: absolute;
  top: 0;
  left: 0;
  width: 400%;
  color: var(--clay-d);
  font-weight: 600;
  animation: lens-counter var(--t) var(--ease-in-out) infinite;
  will-change: transform;
}

.reveal-full {
  display: none;
  position: absolute;
  inset: 0;
  border-radius: calc(8px * var(--round));
  background: color-mix(in oklch, var(--oat) 40%, var(--paper));
  color: var(--clay-d);
  font-weight: 600;
  opacity: 0;
}

.foot {
  margin: clamp(10px, 3vw, 14px) 0 0;
  font-size: clamp(12px, 3.4vw, 13px);
  color: color-mix(in oklch, var(--slate) 60%, transparent);
}

@keyframes lens {
  0%, 8% { transform: translateX(0); }
  46%, 58% { transform: translateX(300%); }
  96%, 100% { transform: translateX(0); }
}

@keyframes lens-counter {
  0%, 8% { transform: translateX(0); }
  46%, 58% { transform: translateX(-75%); }
  96%, 100% { transform: translateX(0); }
}

@keyframes peek {
  0%, 20% { opacity: 0; }
  45%, 60% { opacity: 1; }
  85%, 100% { opacity: 0; }
}

@media (max-width: 380px) {
  .card { padding: 16px; }
  .groups { font-size: 14px; letter-spacing: 0.04em; }
}

@media (prefers-reduced-motion: reduce) {
  .lens { display: none; }
  .reveal-full {
    display: grid;
    animation: peek calc(var(--t) * 2) ease infinite;
  }
}
</style>
</head>
<body>
  <div class="card">
    <div class="head">
      <span class="label">Production key</span>
      <span class="tag">Scanning</span>
    </div>
    <div class="track">
      <div class="groups mask" aria-hidden="true"><span>••••</span><span>••••</span><span>••••</span><span>••••</span></div>
      <div class="lens" aria-hidden="true">
        <div class="groups lens-inner"><span>9f2c</span><span>a71e</span><span>0b4d</span><span>e8c3</span></div>
      </div>
      <div class="groups reveal-full" aria-hidden="true"><span>9f2c</span><span>a71e</span><span>0b4d</span><span>e8c3</span></div>
    </div>
    <p class="foot">Checking for exposed credentials</p>
  </div>
</body>
</html>

The variant family

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