Titandocs

Motion

Four curves, a press affordance on everything, and the anti-blank-screen rule.

Short transitions (120–350 ms). Never gratuitous. Always under prefers-reduced-motion: no-preference.

The curve vocabulary

Click Replay — the difference between "gentle" and "spring" is the whole point.

--ease-gentlecubic-bezier(.2,.7,.3,1)

Gentle deceleration — page/popover entrances, general transitions.

--ease-springcubic-bezier(.34,1.56,.64,1)

Overshoot spring — menu/modal pops, gauges, slider thumbs. The Titan signature.

--ease-shakecubic-bezier(.36,.07,.19,.97)

Shake — invalid input.

CurveRole
cubic-bezier(.2,.7,.3,1)Gentle deceleration — page/popover entrances, general transitions
cubic-bezier(.34,1.56,.64,1)Overshoot spring — menu/modal pops, gauges, slider thumbs. The Titan signature
cubic-bezier(.36,.07,.19,.97)Shake — invalid input
ease / ease-outShort colour/opacity transitions

Universal interactions

Every interactive element gets the same transition set and the same press:

button, a, [role="button"], input {
  transition: transform .14s cubic-bezier(.2,.7,.3,1),
    background-color .15s ease, color .15s ease, border-color .15s ease,
    box-shadow .2s ease, filter .2s ease, opacity .2s ease;
}
button:active, a:active, [role="button"]:active {
  transform: scale(.96);   /* the Apple press */
}
PatternEffect
Pressscale(.96) on every interactive element
Material icon on hoverscale(1.08–1.12), .28s spring. Settings gear → rotate(70deg)
Primary CTA (.titan-cta)hover brightness(1.12), active scale(.98)
Card (.card-lift)hover translateY(-2px) + border-color: var(--border-strong)
Table row (.row-hover)hover background: var(--hover)
Connect button (.btn-chrome)animated 4s shimmer

The segmented control's thumb uses the same vocabulary — the active item drops back to --surface on a --surface-3 track:

Named keyframes

AnimationDurationUsage
titan-page-in.35sRoute entrance (translateY 8px → 0)
wp-in.15–.22sPopover / menu entrance (translateY -6px + scale .98)
kbd-modal-in / scaleIn.26–.28sModal entrance (scale .96 → 1)
titan-flash-up/down.55sPrice glow on tick
dt-drawer-shake.36sInvalid amount
dt-drawer-hint2.6sSlide-to-confirm handle pulse
diamond/obsidianShimmer4sConnect button
tlpLivePulse / wp-pulse"Live" halo

The anti-blank-screen rule

Page entrances never touch opacity. titan-page-in translates only — translateY(8px) → 0, opacity pinned at 1.

A frozen-animation screenshotter (visual regression, an OG image renderer, a crawler) captures frame zero. If frame zero is opacity: 0, every screenshot of the app is a blank page. Translate costs nothing perceptually and makes that failure impossible.

Reduced motion

Everything lives under @media (prefers-reduced-motion: no-preference). Under reduce, durations drop to ~0.01ms.

This is not optional politeness — a trading terminal with constantly-updating rows is exactly the kind of interface that triggers vestibular symptoms.

@media (prefers-reduced-motion: no-preference) {
  .titan-page-in { animation: titan-page-in .35s var(--ease-gentle); }
}

The demo above respects it: with reduce active, the dot renders at its end position and the page says so.

On this page