ExklusivBeta

Still Time <mv-still-time>

Regler für Umgebungsbewegung: Umschließe einen beliebigen Bereich, und jede Animation darin (CSS-Keyframes, CSS-Transitions, Web Animations, stummgeschaltete Videos) läuft nur so schnell, wie der Nutzer gerade aktiv ist. Zeigergeschwindigkeit, Mausrad- und Scrollgeschwindigkeit, Tastendrücke und Touch-Bewegungen speisen ein Aktivitätsniveau, das eine kritisch gedämpfte Feder in eine gleichmäßige Zeitrate umwandelt, von nahezu eingefroren, wenn du stillhältst (standardmäßig ×0,03, oder ein exakter Stopp mit idle-freeze), bis zu voller Geschwindigkeit unter der Hand (×1). Die Rate wird über playbackRate angewendet, ohne etwas neu zu starten, und steht als --mv-time-rate, als Eigenschaft rate, als skalierte Zeituhr und als gedrosseltes Event mv-timerate bereit, sodass Canvas- und Shader-Schleifen folgen können; in Ruhe schläft der Regler vollständig. Standardmäßig ruhig, lebendig unter der Hand.

KategorieHintergründe
TypWeb Component (<mv-still-time>)
StatusBeta
Keywordsexclusive, culture, ambient, motion, playback-rate, time-scale, slow-motion, activity, idle, calm, governor, web-animations, css-animations, background, hero, attention

When to use

  • A landing hero or dashboard has decorative motion that competes with reading and should calm down while visitors read
  • Ambient loops (marquees, orbits, floating cards, shimmer) should feel responsive without distracting idle users
  • A kiosk or wall display should stay nearly still until someone interacts with it
  • Canvas or shader backgrounds need a shared speed signal tied to user activity

Avoid when

  • The animation conveys real progress or status (a loading spinner, a countdown) that must never slow down: mark it data-still-time-ignore or keep it outside
  • The goal is to cap the total amount of motion on a page for performance or accessibility → use Motion Budget instead
  • Motion should simply follow scroll position rather than activity → use Scroll Velocity instead

Installation

node scripts/add.mjs still-time --out ./src/marvelous

KI-Agent mit dem MCP-Server von Marvelous UI: install_components({ slugs: ["still-time"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Kopierte Dateien (inklusive Abhängigkeiten): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, components/still-time/still-time.js, components/still-time/still-time.css.

Verwendung

Referenz-Markup zum Starten und Anpassen mit Attributen, data-* und CSS-Variablen:

<div id="st-demo">
  <style>
    #st-demo { display:grid; gap:1rem; width:100%; max-width:68rem; margin-inline:auto; font-size:.875rem }
    #st-demo .st-hero { position:relative; overflow:hidden; display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); align-items:center; gap:2rem; min-height:27rem; padding:2.5rem 2.5rem 5.5rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-2xl); background:var(--mv-surface); box-shadow:var(--mv-shadow-sm); isolation:isolate }
    #st-demo .st-sky { position:absolute; inset:0; z-index:-1; pointer-events:none; overflow:hidden }
    #st-demo .st-blob { position:absolute; width:26rem; height:26rem; border-radius:50%; opacity:.55; background:radial-gradient(closest-side, color-mix(in oklch, var(--_c) 55%, transparent), transparent 70%); animation:st-drift 18s ease-in-out infinite alternate }
    #st-demo .st-b1 { --_c:var(--mv-accent); left:-6rem; top:-8rem }
    #st-demo .st-b2 { --_c:var(--mv-info); right:-4rem; top:-6rem; animation-duration:22s; animation-delay:-7s }
    #st-demo .st-b3 { --_c:var(--mv-success); right:18%; bottom:-14rem; animation-duration:26s; animation-delay:-13s; opacity:.35 }
    @keyframes st-drift { to { transform:translate(4rem, 3rem) scale(1.15) } }

    #st-demo .st-copy { display:grid; gap:1rem; justify-items:start; max-width:28rem }
    #st-demo .st-copy h2 { margin:0; font-size:clamp(1.75rem, 1.2rem + 1.8vw, 2.5rem); line-height:1.08; letter-spacing:-.03em; font-weight:650 }
    #st-demo .st-copy p { margin:0; color:var(--mv-fg-muted); font-size:.9375rem; line-height:1.55 }
    #st-demo .st-cta { display:flex; flex-wrap:wrap; gap:.5rem; margin-top:.25rem }

    #st-demo .st-visual { position:relative; height:21rem }
    #st-demo .st-orbit { position:absolute; left:50%; top:50%; width:15rem; height:15rem; margin:-7.5rem 0 0 -7.5rem }
    #st-demo .st-ring { position:absolute; inset:0; border-radius:50%; border:1px dashed color-mix(in oklch, var(--mv-fg) 16%, transparent) }
    #st-demo .st-ring-2 { inset:2.5rem; border-style:solid; border-color:color-mix(in oklch, var(--mv-accent) 22%, transparent) }
    #st-demo .st-spin { position:absolute; inset:0; animation:st-rotate 16s linear infinite }
    #st-demo .st-spin-2 { inset:2.5rem; animation-duration:9s; animation-direction:reverse }
    #st-demo .st-dot { position:absolute; left:50%; top:0; width:.75rem; height:.75rem; margin:-.375rem 0 0 -.375rem; border-radius:50%; background:var(--mv-accent); box-shadow:0 0 0 4px color-mix(in oklch, var(--mv-accent) 18%, transparent); transform-origin:50% 7.875rem }
    #st-demo .st-spin-2 .st-dot { width:.5rem; height:.5rem; margin:-.25rem 0 0 -.25rem; transform-origin:50% 5.25rem; background:var(--mv-fg-muted); box-shadow:none }
    #st-demo .st-mark { position:absolute; left:50%; top:50%; display:grid; place-items:center; width:4rem; height:4rem; margin:-2rem 0 0 -2rem; border-radius:1.25rem; color:var(--mv-fg-on-accent); background:linear-gradient(145deg, color-mix(in oklch, var(--mv-accent), white 18%), color-mix(in oklch, var(--mv-accent), black 18%)); box-shadow:0 12px 30px -10px color-mix(in oklch, var(--mv-accent) 70%, transparent); animation:st-breathe 4s ease-in-out infinite }
    #st-demo .st-mark svg { width:1.75rem; height:1.75rem }
    @keyframes st-rotate { to { rotate:360deg } }
    @keyframes st-breathe { 50% { scale:1.06 } }

    #st-demo .st-card { position:absolute; display:grid; gap:.375rem; margin:0; padding:.75rem .875rem; min-width:11.5rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:color-mix(in oklch, var(--mv-surface-raised) 88%, transparent); -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px); box-shadow:var(--mv-shadow-lg); animation:st-float 6s ease-in-out infinite alternate }
    #st-demo .st-card small { color:var(--mv-fg-muted); font-size:.6875rem; font-weight:500; text-transform:uppercase; letter-spacing:.06em }
    #st-demo .st-card b { font-size:1.125rem; letter-spacing:-.02em; font-variant-numeric:tabular-nums }
    #st-demo .st-card .st-up { color:var(--mv-success); font-size:.75rem; font-weight:600 }
    #st-demo .st-row { display:flex; align-items:center; gap:.5rem; font-size:.8125rem; font-weight:500 }
    #st-demo .st-row span:last-child { color:var(--mv-fg-muted); font-weight:400; margin-inline-start:auto; font-size:.75rem }
    #st-demo .st-c1 { left:0; top:1rem; animation-delay:-2s }
    #st-demo .st-c2 { right:0; bottom:4rem; animation-duration:7.5s; animation-delay:-4s }
    #st-demo .st-c3 { left:4%; bottom:.25rem; animation-duration:8.5s; animation-delay:-1s; min-width:13rem }
    @keyframes st-float { from { transform:translateY(-7px) rotate(-.6deg) } to { transform:translateY(7px) rotate(.6deg) } }
    #st-demo .st-spark { width:100%; height:1.75rem; overflow:visible }
    #st-demo .st-spark path { fill:none; stroke:var(--mv-accent); stroke-width:2; stroke-linecap:round; stroke-linejoin:round; stroke-dasharray:120; animation:st-draw 5s var(--mv-ease-in-out) -3.4s infinite }
    @keyframes st-draw { 0% { stroke-dashoffset:120 } 55%, 100% { stroke-dashoffset:0 } }
    #st-demo .st-check { display:grid; place-items:center; width:1.25rem; height:1.25rem; border-radius:50%; background:color-mix(in oklch, var(--mv-success) 18%, transparent); color:var(--mv-success) }
    #st-demo .st-check svg { width:.75rem; height:.75rem }
    #st-demo .st-bar { position:relative; height:.375rem; border-radius:var(--mv-radius-full); background:var(--mv-bg-muted); overflow:hidden }
    #st-demo .st-bar i { position:absolute; inset:0 38% 0 0; border-radius:inherit; background:linear-gradient(90deg, var(--mv-accent), color-mix(in oklch, var(--mv-accent), white 30%) 50%, var(--mv-accent)) 0 0 / 200% 100%; animation:st-shimmer 1.8s linear infinite }
    @keyframes st-shimmer { to { background-position:-200% 0 } }

    #st-demo .st-marquee { position:absolute; left:0; right:0; bottom:0; height:3.5rem; display:flex; align-items:center; overflow:hidden; border-top:1px solid var(--mv-border); background:color-mix(in oklch, var(--mv-bg-subtle) 70%, transparent); -webkit-mask:linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent); mask:linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent) }
    #st-demo .st-track { display:flex; flex:none; gap:3rem; padding-inline-end:3rem; animation:st-scroll 28s linear infinite; color:var(--mv-fg-subtle); font-weight:600; letter-spacing:-.01em; white-space:nowrap }
    #st-demo .st-track span { display:inline-flex; align-items:center; gap:.5rem }
    #st-demo .st-track span::before { content:""; width:.625rem; height:.625rem; border-radius:.2rem; background:currentColor; opacity:.5 }
    @keyframes st-scroll { to { transform:translateX(-100%) } }

    #st-demo .st-lower { display:grid; grid-template-columns:minmax(0,1.1fr) minmax(0,1fr); gap:1rem }
    #st-demo .st-panel { position:relative; display:grid; gap:1rem; padding:1.25rem 1.25rem 3.25rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); box-shadow:var(--mv-shadow-sm) }
    #st-demo .st-panel header { display:flex; align-items:center; gap:.5rem }
    #st-demo .st-panel header strong { font-size:.9375rem }
    #st-demo .st-panel header span { margin-inline-start:auto; color:var(--mv-fg-muted); font-size:.8125rem; font-variant-numeric:tabular-nums }
    #st-demo .st-live { position:relative; width:.5rem; height:.5rem; border-radius:50%; background:var(--mv-success) }
    #st-demo .st-live::after { content:""; position:absolute; inset:0; border-radius:50%; background:var(--mv-success); animation:st-ping 1.6s var(--mv-ease-out) infinite }
    @keyframes st-ping { to { transform:scale(3); opacity:0 } }
    #st-demo .st-eq { display:flex; align-items:flex-end; gap:.375rem; height:6.5rem; padding-bottom:.25rem; border-bottom:1px solid var(--mv-border) }
    #st-demo .st-eq i { flex:1; height:100%; border-radius:.3rem .3rem .1rem .1rem; background:linear-gradient(to top, color-mix(in oklch, var(--mv-accent) 70%, transparent), var(--mv-accent)); transform-origin:bottom; animation:st-eq 1.6s ease-in-out infinite alternate }
    @keyframes st-eq { from { transform:scaleY(.18) } to { transform:scaleY(1) } }
    #st-demo .st-legend { display:flex; justify-content:space-between; color:var(--mv-fg-subtle); font-size:.6875rem; font-variant-numeric:tabular-nums }

    #st-demo .st-side { display:grid; gap:1rem; align-content:start; padding:1.25rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-bg-subtle) }
    #st-demo .st-side h3 { margin:0; font-size:.9375rem }
    #st-demo .st-side p { margin:0; color:var(--mv-fg-muted); line-height:1.5 }
    #st-demo .st-readouts { display:grid; gap:.5rem }
    #st-demo .st-readout { display:flex; align-items:center; gap:.75rem; padding:.625rem .75rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-lg); background:var(--mv-surface) }
    #st-demo .st-readout span { color:var(--mv-fg-muted) }
    #st-demo .st-readout output { margin-inline-start:auto; font:600 .8125rem/1 var(--mv-font-mono); font-variant-numeric:tabular-nums }
    #st-demo .st-readout output small { color:var(--mv-fg-subtle); font-weight:500 }
    #st-demo .st-toggle { display:flex; align-items:center; gap:.625rem; cursor:pointer; font-weight:500 }

    @media (prefers-reduced-motion: reduce) {
      #st-demo .st-blob, #st-demo .st-spin, #st-demo .st-mark, #st-demo .st-card, #st-demo .st-spark path, #st-demo .st-bar i, #st-demo .st-track, #st-demo .st-live::after, #st-demo .st-eq i { animation:none }
    }
    :where([data-motion="reduce"]) #st-demo :is(.st-blob, .st-spin, .st-mark, .st-card, .st-spark path, .st-bar i, .st-track, .st-eq i), :where([data-motion="reduce"]) #st-demo .st-live::after { animation:none }
    @media (max-width: 760px) {
      #st-demo .st-hero { grid-template-columns:1fr; padding:1.5rem 1.25rem 5rem }
      #st-demo .st-lower { grid-template-columns:1fr }
    }
  </style>

  <mv-still-time class="st-hero" id="st-hero" indicator>
    <div class="st-sky" aria-hidden="true"><span class="st-blob st-b1"></span><span class="st-blob st-b2"></span><span class="st-blob st-b3"></span></div>

    <div class="st-copy">
      <span class="mv-badge" data-variant="secondary">New · Focus mode</span>
      <h2>Analytics that move when you do</h2>
      <p>Your dashboard stays calm while you read and comes alive the moment you reach for it. Move the pointer over this panel.</p>
      <div class="st-cta">
        <button class="mv-button">Start free trial</button>
        <button class="mv-button" data-variant="outline">Book a demo</button>
      </div>
    </div>

    <div class="st-visual" aria-hidden="true">
      <div class="st-orbit">
        <span class="st-ring"></span>
        <span class="st-ring st-ring-2"></span>
        <span class="st-spin"><i class="st-dot" style="rotate:20deg"></i><i class="st-dot" style="rotate:140deg"></i><i class="st-dot" style="rotate:260deg"></i></span>
        <span class="st-spin st-spin-2"><i class="st-dot" style="rotate:80deg"></i><i class="st-dot" style="rotate:260deg"></i></span>
        <span class="st-mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19V9M10 19V5M16 19v-7M22 19H2"/></svg></span>
      </div>
      <figure class="st-card st-c1">
        <small>Revenue · this week</small>
        <div class="st-row"><b>$48,210</b><span class="st-up">↑ 12.4%</span></div>
        <svg class="st-spark" viewBox="0 0 120 28" preserveAspectRatio="none"><path d="M2 22 18 18 30 20 44 12 58 15 72 8 86 11 100 5 118 3"/></svg>
      </figure>
      <figure class="st-card st-c2">
        <div class="st-row"><span class="st-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>Deploy succeeded<span>42s</span></div>
        <div class="st-row"><span class="mv-spinner" style="--mv-spinner-size:1rem;color:var(--mv-accent)"></span>Warming edge cache<span>3 of 5</span></div>
      </figure>
      <figure class="st-card st-c3">
        <div class="st-row">Syncing 1,284 events<span>62%</span></div>
        <div class="st-bar"><i></i></div>
      </figure>
    </div>

    <div class="st-marquee" aria-hidden="true">
      <div class="st-track"><span>Northwind</span><span>Acme Logistics</span><span>Halden Studio</span><span>Blue Harbor</span><span>Lumen Health</span><span>Vertex Labs</span><span>Orbital Freight</span></div>
      <div class="st-track"><span>Northwind</span><span>Acme Logistics</span><span>Halden Studio</span><span>Blue Harbor</span><span>Lumen Health</span><span>Vertex Labs</span><span>Orbital Freight</span></div>
    </div>
  </mv-still-time>

  <div class="st-lower">
    <mv-still-time class="st-panel" id="st-panel" idle-freeze indicator>
      <header><i class="st-live" aria-hidden="true"></i><strong>Live traffic</strong><span>1,284 visitors now</span></header>
      <div class="st-eq" aria-hidden="true">
        <i style="animation-delay:-.2s"></i><i style="animation-delay:-1.1s"></i><i style="animation-delay:-.6s"></i><i style="animation-delay:-1.4s"></i><i style="animation-delay:-.3s"></i><i style="animation-delay:-.9s"></i><i style="animation-delay:-1.5s"></i><i style="animation-delay:-.5s"></i><i style="animation-delay:-1.2s"></i><i style="animation-delay:-.1s"></i><i style="animation-delay:-.8s"></i><i style="animation-delay:-1.3s"></i><i style="animation-delay:-.4s"></i><i style="animation-delay:-1s"></i>
      </div>
      <div class="st-legend" aria-hidden="true"><span>2:00 PM</span><span>2:30 PM</span><span>3:00 PM</span></div>
    </mv-still-time>

    <div class="st-side">
      <h3>Time follows your hand</h3>
      <p>Keep still: the hero crawls at ×0.03 and the traffic panel freezes completely. Move, scroll or type and both speed up to ×1.00.</p>
      <div class="st-readouts">
        <div class="st-readout"><span>Hero</span><output id="st-hero-rate">×0.03 <small>→ ×1.00</small></output></div>
        <div class="st-readout"><span>Traffic panel</span><output id="st-panel-rate">×0.00 <small>→ ×1.00</small></output></div>
      </div>
      <label class="st-toggle"><input type="checkbox" role="switch" class="mv-switch" id="st-freeze" checked> Freeze the panel completely when idle</label>
    </div>
  </div>
</div>

<script type="module">
  const root = document.getElementById("st-demo");
  if (root) {
    const show = (el, out) => {
      const paint = (rate) => {
        out.textContent = `×${rate.toFixed(2)} `;
        const max = document.createElement("small");
        max.textContent = "→ ×1.00";
        out.append(max);
      };
      el.addEventListener("mv-timerate", (e) => paint(e.detail.rate));
      customElements.whenDefined("mv-still-time").then(() => paint(el.rate ?? 0));
    };
    const hero = root.querySelector("#st-hero");
    const panel = root.querySelector("#st-panel");
    show(hero, root.querySelector("#st-hero-rate"));
    show(panel, root.querySelector("#st-panel-rate"));
    root.querySelector("#st-freeze").addEventListener("change", (e) => {
      panel.idleFreeze = e.target.checked;
    });
  }
</script>

Kulturelle Referenz

SUPERHOT, SUPERHOT Team (2016, Videospiel). Die Zeit bewegt sich nur, wenn du dich bewegst: Solange du stillstehst, ist die Welt eingefroren. In der UI laufen Umgebungsanimationen nur so schnell, wie der Nutzer aktiv ist, und kriechen fast bis zum Stillstand, wenn er stillhält.

API

Attributes

NameTypDefaultDescription
min-ratenumber0.03Time rate when the user is still (0 … max-rate). Animations crawl at this speed instead of stopping, so the scene never looks broken.
max-ratenumber1Time rate under full activity. Values above 1 make motion faster than authored.
idle-freezebooleanfalseExact 0 when idle: animations hold their current frame (playbackRate 0) and governed videos are paused, then resume where they were.
scoperegion | pageregionWhere activity is measured: only over the element (pointer, touch, wheel, keys and focus inside it, scrolling of its descendants), or over the whole page (including window scroll speed).
sensitivitynumber1Multiplies pointer and scroll speeds before they are mapped to activity (2 = half the speed is needed to reach full time).
videomuted | all | nonemutedWhich <video> descendants follow the rate (clamped to the 0.0625-16 range browsers support). Muted only by default, so audio is never slowed down.
indicatorbooleanfalseShows a small decorative time gauge in the bottom corner (ring on a log scale + “×0.03”), aria-hidden, plus a visually hidden sentence explaining the behavior.
data-still-time-ignoreboolean (on a descendant)Excludes this subtree: its animations and videos keep their own speed (use it for spinners, countdowns and other honest status).

Properties

NameTypDescription
ratenumberCurrent time rate (read-only).
timenumber (s)Scaled clock: seconds that advance at the current rate (read-only). Drive a canvas or shader loop with it to follow the governor exactly.
minRate, maxRate, idleFreeze, scope, sensitivity, video, indicatorreflectedMirror the attributes (scope is read when the element connects).

Methods

NameDescription
nudge(amount = 0.6)Feeds activity manually (0 … 1), e.g. from a gamepad, a voice command or a custom gesture.

Events

NameDescription
mv-timerateThrottled to ~10 per second while the rate changes, plus one when it comes to rest. detail: { rate, activity, resting }.

Content structure

NameDescription
(content)Any content with ambient motion: CSS keyframes, transitions, Web Animations (other components included), muted <video>. Script-created animations are picked up within ~1.5 s, CSS ones as soon as they start.

CSS classes

NameDescription
mv-still-time-gaugeIndicator pill (aria-hidden); data-level = frozen | still | moving | live.
mv-still-time-arc / -track / -hand / -valueParts of the gauge: filled ring, its track, the clock hand and the “×0.03” text.

CSS variables

NameDefaultDescription
--mv-time-rate1Set on the element to the current rate (3 decimals) while it changes: read it in calc() for effects that are not animations (blur, glow, transition-duration of your own).
--mv-still-time-colorvar(--mv-accent)Color of the gauge arc.
--mv-still-time-insetvar(--mv-space-3)Distance of the gauge from the bottom and inline-end edges.

Accessibility

Purely presentational: it adds no roles, no focus stops and never moves focus. It only slows motion down, so it reduces distraction for people who read slowly or are sensitive to motion, while keyboard users are treated like pointer users (key presses and focus changes count as activity, so nobody gets a frozen interface because they do not use a mouse). The indicator gauge is aria-hidden; when indicator is set, a visually hidden sentence explains that ambient animations follow your activity (no live announcements, which would be noise). Honest status animations (spinners, countdowns, progress) can be excluded with data-still-time-ignore. Reduced motion (system or data-motion="reduce"): the governor does not react at all and keeps the rate at min-rate (0 with idle-freeze); well-behaved content already stops its own continuous animations in that mode. Performance: the animation set is refreshed on animationstart / transitionrun events plus a 1.5 s sweep (getAnimations({ subtree: true })), never per frame; the frameLoop runs only while the rate is changing and sleeps at rest (no work at all while nobody moves), pauses offscreen and in hidden tabs; playbackRate is updated with updatePlaybackRate (no restart, no jump) only when it actually changed; video rates are updated at ~10 Hz. On disconnect every governed animation and video is restored to its natural rate and resumed if it was paused.

Diese Seite wurde mit KI übersetzt. Übersetzungsfehler melden