Signs of life
Composed.
Confident.
Alive.
Surgr
SURGR
Surgr Design System
MOTION
The choreography behind every screen.
Surgr Design System

MOTION

The choreography behind every screen — the easing, timing, and rules that keep Surgr calm, confident, and alive. Composed, never bouncy. Motion is the product's heartbeat.

ease-out-quint · 0.22, 1, 0.36, 1140ms → 2400msreduced-motion respected
01 — Philosophy

The rules of motion

Motion at Surgr is functional first. Every animation reports state; nothing moves for decoration alone. The system is composed and confident — hard, premium, tech-forward — never playful.

/01
Calm, not bouncy
Overshoot is reserved for the subtle spring on taps. Content itself always lands with a soft ease-out — no bounce, no wobble.
/02
Signal, not decoration
A pulse means live. A lift means arrival. A shimmer means loading. If an animation says nothing, cut it.
/03
Fast in, soft out
Interactions answer instantly (140ms). Content lands gently over ~520ms with ease-out-quint.
/04
Ambient is the heartbeat
Low-frequency, peripheral motion keeps the app alive. Keep lime motion under ~20% of the frame to avoid eye strain.
/05
Honor the OS
prefers-reduced-motion disables ambient and entrance motion. Function must never depend on animation.
/06
One easing family
Three curves cover everything: ease-out for enters, ease-in-out for ambient, spring for taps. Don't invent new ones.
Composed vs. bouncy
Same distance, same duration. Surgr settles; the bouncy version overshoots and wobbles — that reads as toy-like, off-brand.
Show, don't tell
This
ease-out-quint
Not this
bounce / overshoot
02 — Tokens

Easing & timing

Every motion pulls from a fixed set of CSS custom properties defined in colors_and_type.css. Reference the tokens — never hardcode a curve or a millisecond value.

Ease-out
cubic-bezier(0.22, 1, 0.36, 1)
--surgr-ease-out. Soft landings. Enters, reveals, scene transitions. The default for anything arriving on screen.
Ease-in-out
cubic-bezier(0.65, 0, 0.35, 1)
--surgr-ease-in-out. Symmetric. Breathing, pulses, glow blobs — anything that loops and returns to where it began.
Ease-spring
cubic-bezier(0.34, 1.4, 0.64, 1)
--surgr-ease-spring. Subtle overshoot. Tap responses only — the squish-back on a button. Never on incoming content.
Duration scale
Four rungs. Feel the difference — the same puck travels each duration, then holds. Fast for input, slow for arrival, ambient for life.
4 tokens
--dur-fast140msTaps
--dur-base220msHover · focus
--dur-slow520msEnters · transitions
--dur-amb2400msAmbient · breathing
--surgr-ease-out:    cubic-bezier(0.22, 1, 0.36, 1);
--surgr-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
--surgr-ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
--surgr-dur-fast:  140ms;  --surgr-dur-base: 220ms;
--surgr-dur-slow:  520ms;  --surgr-dur-amb: 2400ms;
03 — Ambient

Signs of life

Continuous, low-frequency motion that runs unattended. It tells the user the product is alive and listening — without ever demanding attention. All ambient motion loops on ease-in-out.

Pulse
Opacity-only heartbeat. Status dots, "online" pips, live indicators.
Ambient
LIVE
opacity 1 → 0.45 → 1
Keyframesurgr-pulse
Duration2400ms
Easingease-in-out
.dot { animation: surgr-pulse
  var(--surgr-dur-amb)
  var(--surgr-ease-in-out) infinite; }
@keyframes surgr-pulse {
  0%,100% { opacity: 1; }
  50%     { opacity: 0.45; } }
DoUse on a single live status point.
Don'tPulse whole cards or text blocks.
Breathe
Scale + opacity swell. Glow blobs, ambient halos behind the AI Coach.
Ambient
scale 1 → 1.08 · opacity 0.55 → 1
Keyframesurgr-breathe
Duration9000ms
Easingease-in-out
.glow { animation: surgr-breathe 9s
  var(--surgr-ease-in-out) infinite; }
@keyframes surgr-breathe {
  0%,100% { opacity:.55; transform:scale(1); }
  50%     { opacity:1;   transform:scale(1.08); } }
DoKeep it slow (8–10s) and far behind content.
Don'tSpeed it up — it becomes a distraction.
Ring
Outward-expanding halo. "Live" badges, CTA attention pulls, recording state.
Ambient
box-shadow spread 0 → 14px, fading
Keyframesurgr-ring
Duration2400ms
Easingease-out
.badge { animation: surgr-ring
  var(--surgr-dur-amb)
  var(--surgr-ease-out) infinite; }
@keyframes surgr-ring {
  0%  { box-shadow:0 0 0 0 rgba(200,255,0,.45); }
  70% { box-shadow:0 0 0 14px rgba(200,255,0,0); }
  100%{ box-shadow:0 0 0 0 rgba(200,255,0,0); } }
DoReserve for genuinely live / urgent state.
Don'tStack on every CTA — it loses meaning.
Bob
Staggered vertical dots. The AI Coach "typing" indicator.
Ambient
translateY -3px · 60ms stagger
Keyframesurgr-bob
Duration1200ms
Stagger60ms
.dot { animation: surgr-bob 1.2s
  var(--surgr-ease-in-out) infinite; }
.dot:nth-child(2){ animation-delay: 60ms; }
.dot:nth-child(3){ animation-delay: 120ms; }
@keyframes surgr-bob {
  0%,60%,100%{ transform:translateY(0); opacity:.45; }
  30%        { transform:translateY(-3px); opacity:1; } }
DoShow only while the Coach is generating.
Don'tLeave it running after the reply lands.
04 — Entrances

Arrivals

How content enters. One-shot, ease-out, ~520ms. Distance is small (6–16px) and always upward — content rises into place. Big text clears a slight blur; lists stagger.

Lift
Fade up 12px. Cards, sheets, screens.
Enter
Push Day
Força · 6 exercícios
Keyframesurgr-lift
Duration520ms
.card { animation: surgr-lift
  var(--surgr-dur-slow)
  var(--surgr-ease-out) both; }
@keyframes surgr-lift {
  from{ opacity:0; transform:translateY(12px); }
  to  { opacity:1; transform:translateY(0); } }
DoUse for cards and sheets.
Don'tSlide sideways or from far.
Reveal
Fade up + blur clears. Headlines, big numbers.
Enter
Próximo Treino
Keyframesurgr-reveal
Duration520ms
.title { animation: surgr-reveal
  var(--surgr-dur-slow)
  var(--surgr-ease-out) both; }
@keyframes surgr-reveal {
  from{ opacity:0; transform:translateY(6px);
        filter:blur(6px); }
  to  { opacity:1; transform:translateY(0);
        filter:blur(0); } }
DoReserve for hero / section heads.
Don'tBlur body copy — it hurts legibility.
Stagger
Reveal, cascaded. Lists, plan rows, stats.
Enter
Push Day · Força
Pull Day · Costas
Leg Day · Quadríceps
Rest · Recovery
Basesurgr-reveal
Stagger60ms
.list > * { animation: surgr-reveal
  var(--surgr-dur-slow)
  var(--surgr-ease-out) both; }
.list > *:nth-child(2){ animation-delay:60ms; }
.list > *:nth-child(3){ animation-delay:120ms; }
/* +60ms per item */
DoCap total cascade under ~400ms.
Don'tStagger 20 rows — it drags.
05 — Interactions

Response to touch

Direct manipulation must feel instant and physical. Taps squish, cards lean toward the cursor, surfaces warm on hover. These are the only places overshoot is allowed.

Press
Scale to 0.97 on active. Every button, globally.
Tap
Click / tap and hold
Transformscale(.97)
Duration140ms
Easingspring
button {
  transition: transform var(--surgr-dur-fast)
    var(--surgr-ease-spring); }
button:active { transform: scale(0.97); }
DoApply to all tappable controls.
Don'tAlso darken — the squish is enough.
Magnetic hover
Card leans toward the cursor. Key landing cards only.
Pointer
Surgr Coach
Sem achismo
Move your cursor across this card.
Translate~10% toward
Rotate±5–8°
Easingease-out
// on mousemove, offset from card center:
const dx = (e.clientX - cx) / rect.width;
const dy = (e.clientY - cy) / rect.height;
el.style.transform =
  `translate(${dx*20}px, ${dy*20}px)
   rotateX(${-dy*8}deg) rotateY(${dx*8}deg)`;
// transition: transform var(--surgr-dur-base)
//   var(--surgr-ease-out);
DoReserve for 1–2 hero cards.
Don'tApply to list items or grids.
Hover states
Surfaces warm; CTAs lift and glow.
Pointer
Card
Hover me — bg + accent border.
220ms · ease-out
Cardbg + border
CTA-2px + glow
Duration220ms
.card:hover {
  background: var(--surgr-bg-card-hover);
  border-color: rgba(200,255,0,.3); }
.cta:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 30px rgba(200,255,0,.25); }
DoShift color / lift only.
Don'tScale cards up on hover.
06 — Transitions

Screen to screen

Navigation crossfades with a short slide-in from the right. The outgoing screen fades; the incoming one rises into place over 520ms ease-out. No hard cuts, no full-screen wipes.

Page transition
Dashboard → Trainer → Workout. Auto-cycling every 2.6s to show the enter.
Navigation
Dashboard
Bem-vindo
Coach
Trainer
Session
Workout
Enter fromtranslateX(24px)
Duration520ms
Easingease-out
.screen {
  transition: opacity var(--surgr-dur-slow) var(--surgr-ease-out),
              transform var(--surgr-dur-slow) var(--surgr-ease-out); }
.screen.enter { opacity: 0; transform: translateX(24px); }
.screen.active { opacity: 1; transform: translateX(0); }
DoKeep the slide distance small (≤24px).
Don'tCross-slide both screens — only the new one moves.
07 — Loading

Waiting, gracefully

Never a spinner on content. Skeletons shimmer in the layout's shape so the arrival feels seamless. Numbers count up rather than snapping — progress you can feel.

Shimmer skeleton
Placeholder in the final layout's shape, with a sweeping highlight.
Loading
Keyframesurgr-shimmer
Duration1400ms
Easinglinear
.skeleton {
  background: linear-gradient(90deg,
    #13131A, #1A1A24 50%, #13131A);
  background-size: 200% 100%;
  animation: surgr-shimmer 1.4s linear infinite; }
@keyframes surgr-shimmer {
  0%  { background-position: -200% 0; }
  100%{ background-position:  200% 0; } }
DoMatch the real content's shape.
Don'tUse a spinner over the layout.
Count-up
Stats tick to their value on ease-out-quint. The dashboard's arrival moment.
Loading
{{ treinos }}
Treinos
{{ streak }}
Streak
{{ vol }}k
Volume kg
0 → target · ease-out-quint · 1.6s
ProgresseaseOutQuint
Duration~1600ms
// per frame, t = elapsed / duration (0→1)
const p = 1 - Math.pow(1 - t, 5); // ease-out-quint
el.textContent = Math.round(p * target);
// tabular-nums keeps digits from jittering
DoUse tabular-nums so width is stable.
Don'tCount up on every re-render.
08 — In context

Motion in the product

The tokens composed into real screens. Each choreography is deliberate: what enters, what pulses, what waits. Motion earns its place — or it's cut.

Bem-vindo
Pro
{{ treinos }}
Treinos
{{ streak }}
Streak
{{ vol }}k
Volume
Seu plano
Push DayForça · hoje
Pull DayCostas
Leg DayQuadríceps
Dashboard
Count-up · Stagger · Pulse
Coach online
Ombro doeu no supino hoje.
SURGR COACH
Vamos mudar a angulação e baixar a carga 10%.
Trainer
Lift · Bob typing · Ring
Push Day
01:24
Supino · 140kg × 12
Supino · 242kg × 10
Supino · 344kg × 8
Workout
Pulse timer · Press · Accent
09 — Accessibility

Respect reduced motion

When the OS requests reduced motion, ambient and entrance animations stop and elements render in their final state instantly. Nothing functional depends on movement. Toggle below to preview the difference.

prefers-reduced-motion
Every Surgr animation utility is wrapped in the media query — one rule, system-wide.
Pulse
Breathe
Card
Lift enter
Lift
Shimmer
@media (prefers-reduced-motion: reduce) {
  .surgr-anim-pulse, .surgr-anim-breathe,
  .surgr-anim-ring,  .surgr-anim-bob,
  .surgr-anim-lift,  .surgr-anim-reveal {
    animation: none !important; } }
DoRender the final state — content stays visible.
Don'tHide content that only an animation would reveal.