css-motion-library 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +121 -2
- package/catalog.json +77 -0
- package/examples/001-commerce-storyboard/README.md +19 -0
- package/examples/001-commerce-storyboard/animation.css +167 -0
- package/examples/001-commerce-storyboard/index.html +146 -0
- package/examples/002-commerce-film/README.md +38 -0
- package/examples/002-commerce-film/animation.css +197 -0
- package/examples/002-commerce-film/index.html +73 -0
- package/examples/003-kinetic-headline/README.md +29 -0
- package/examples/003-kinetic-headline/animation.css +276 -0
- package/examples/003-kinetic-headline/index.html +82 -0
- package/examples/004-loading-states/README.md +28 -0
- package/examples/004-loading-states/animation.css +87 -0
- package/examples/004-loading-states/index.html +119 -0
- package/examples/005-orbit-diagram/README.md +30 -0
- package/examples/005-orbit-diagram/animation.css +123 -0
- package/examples/005-orbit-diagram/index.html +66 -0
- package/package.json +31 -4
- package/src/auto.js +2 -0
- package/src/demo.css +12 -0
- package/src/player.js +140 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<meta name="color-scheme" content="dark">
|
|
7
|
+
<title>003 — Kinetic Headline | CSS Motion Library</title>
|
|
8
|
+
<script>if (new URLSearchParams(location.search).has('embed')) document.documentElement.classList.add('is-embedded');</script>
|
|
9
|
+
<link rel="stylesheet" href="../../src/demo.css">
|
|
10
|
+
<link rel="stylesheet" href="animation.css">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<nav class="demo-nav">
|
|
14
|
+
<a href="../../">← Library</a>
|
|
15
|
+
<span>003 / Kinetic Headline</span>
|
|
16
|
+
<a href="../004-loading-states/">Next: Loading States ↗</a>
|
|
17
|
+
</nav>
|
|
18
|
+
|
|
19
|
+
<main>
|
|
20
|
+
<section class="kh" data-motion-player data-duration="8" aria-labelledby="kh-title">
|
|
21
|
+
<div class="kh__stage" data-motion-stage>
|
|
22
|
+
<div class="kh__overview">
|
|
23
|
+
<div class="kh__content">
|
|
24
|
+
<div class="kh__eyebrow">INTRODUCING</div>
|
|
25
|
+
<h1 class="kh__headline" id="kh-title">
|
|
26
|
+
<span class="kh__line">
|
|
27
|
+
<span class="kh__word">Make</span>
|
|
28
|
+
<span class="kh__word">the</span>
|
|
29
|
+
<span class="kh__word">next</span>
|
|
30
|
+
<span class="kh__word">thing</span>
|
|
31
|
+
</span>
|
|
32
|
+
<span class="kh__line kh__line--second">
|
|
33
|
+
<span class="kh__word">feel</span>
|
|
34
|
+
<span class="kh__last">
|
|
35
|
+
<span class="kh__word">inevitable.</span>
|
|
36
|
+
<svg class="kh__underline" viewBox="0 0 280 18" preserveAspectRatio="none" aria-hidden="true" focusable="false">
|
|
37
|
+
<path class="kh__underline-path" pathLength="1" d="M4 11 C42 4 78 15 118 8 S188 3 222 9 C242 12 258 10 276 7"></path>
|
|
38
|
+
</svg>
|
|
39
|
+
</span>
|
|
40
|
+
</span>
|
|
41
|
+
</h1>
|
|
42
|
+
<p class="kh__subline">A reusable headline reveal in pure CSS.</p>
|
|
43
|
+
<span class="kh__pill">Get started →</span>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="kh__scene" aria-hidden="true">
|
|
48
|
+
<div class="kh__content kh__content--scene">
|
|
49
|
+
<div class="kh__eyebrow">INTRODUCING</div>
|
|
50
|
+
<div class="kh__headline">
|
|
51
|
+
<span class="kh__line">
|
|
52
|
+
<span class="kh__mask"><span class="kh__word kh__word--1">Make</span></span>
|
|
53
|
+
<span class="kh__mask"><span class="kh__word kh__word--2">the</span></span>
|
|
54
|
+
<span class="kh__mask"><span class="kh__word kh__word--3">next</span></span>
|
|
55
|
+
<span class="kh__mask"><span class="kh__word kh__word--4">thing</span></span>
|
|
56
|
+
</span>
|
|
57
|
+
<span class="kh__line kh__line--second">
|
|
58
|
+
<span class="kh__mask"><span class="kh__word kh__word--5">feel</span></span>
|
|
59
|
+
<span class="kh__last">
|
|
60
|
+
<span class="kh__mask"><span class="kh__word kh__word--6">inevitable.</span></span>
|
|
61
|
+
<svg class="kh__underline" viewBox="0 0 280 18" preserveAspectRatio="none" aria-hidden="true" focusable="false">
|
|
62
|
+
<path class="kh__underline-path" pathLength="1" d="M4 11 C42 4 78 15 118 8 S188 3 222 9 C242 12 258 10 276 7"></path>
|
|
63
|
+
</svg>
|
|
64
|
+
</span>
|
|
65
|
+
</span>
|
|
66
|
+
</div>
|
|
67
|
+
<p class="kh__subline">A reusable headline reveal in pure CSS.</p>
|
|
68
|
+
<span class="kh__pill">Get started →</span>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<span class="kh__clock" aria-hidden="true"></span>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div class="kh__controls" data-motion-controls hidden><button type="button" data-motion-toggle aria-label="Pause animation">Ⅱ Pause</button><button type="button" data-motion-restart aria-label="Replay animation from the beginning">↺ Replay</button><label><span class="kh__sr-only">Animation position in seconds</span><input data-motion-seek type="range" min="0" max="8" step="0.1" value="0" aria-label="Animation position in seconds"></label><output data-motion-time>0:00 / 0:08</output><label><span class="kh__sr-only">Playback speed</span><select data-motion-speed aria-label="Playback speed"><option value="0.5">0.5×</option><option value="1" selected>1×</option><option value="1.5">1.5×</option></select></label></div>
|
|
76
|
+
</section>
|
|
77
|
+
</main>
|
|
78
|
+
|
|
79
|
+
<footer class="demo-footer">A staged headline reveal with a drawn underline · <a href="animation.css">View CSS</a></footer>
|
|
80
|
+
<script type="module" src="../../src/auto.js"></script>
|
|
81
|
+
</body>
|
|
82
|
+
</html>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# 004 — Loading States
|
|
2
|
+
|
|
3
|
+
Four reusable loading micro-animations on one six-second loop: a skeleton shimmer, a
|
|
4
|
+
determinate progress bar, bouncing dots, and a spinner that resolves into a check mark.
|
|
5
|
+
|
|
6
|
+
| Card | Motion | Cycle |
|
|
7
|
+
|---|---|---|
|
|
8
|
+
| Skeleton | Gradient shimmer sweeps across placeholder lines | 2s |
|
|
9
|
+
| Progress | Bar and tick row fill 0→100%, hold, reset | 6s |
|
|
10
|
+
| Dots | Three staggered bounces | 1.5s |
|
|
11
|
+
| Success | Spinner rotates, then a check mark draws in at ~3s | 6s |
|
|
12
|
+
|
|
13
|
+
All cycles divide six seconds evenly, so seeking keeps every card in sync.
|
|
14
|
+
|
|
15
|
+
## Customize
|
|
16
|
+
|
|
17
|
+
- Theme: `--bg`, `--card`, `--text`, `--accent` on `.ls`.
|
|
18
|
+
- Each card is independent. Copy one `.ls__card` and the rules/keyframes with its prefix
|
|
19
|
+
(`ls__skeleton`, `ls__progress`, `ls__dot`, `ls__success`). Without the player, the
|
|
20
|
+
`infinite` keyframes still loop on their own; keep the `prefers-reduced-motion` block.
|
|
21
|
+
- In a real app, swap the progress keyframe for a `width` you set from JavaScript, and show
|
|
22
|
+
the success state when your task actually finishes.
|
|
23
|
+
|
|
24
|
+
## Accessibility
|
|
25
|
+
|
|
26
|
+
The cards are decorative (`aria-hidden`). In production, pair a loader with
|
|
27
|
+
`role="status"` or `aria-busy="true"` on the region that is loading. Reduced motion and
|
|
28
|
+
no-JS show the resting states.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
.ls {
|
|
2
|
+
--bg: #0f1115; /* Stage background */
|
|
3
|
+
--card: #171a21; /* Card surface */
|
|
4
|
+
--text: #e8eaf0; /* Primary text */
|
|
5
|
+
--muted: #9ca3af; /* Secondary text */
|
|
6
|
+
--accent: #7cf3c4; /* Progress, spinner, and focus accent */
|
|
7
|
+
--line: #ffffff1f; /* Hairline borders */
|
|
8
|
+
width: min(100%, calc((100svh - 140px) * 16 / 9));
|
|
9
|
+
margin-inline: auto;
|
|
10
|
+
color: var(--text);
|
|
11
|
+
background: var(--bg);
|
|
12
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
13
|
+
}
|
|
14
|
+
.ls, .ls *, .ls *::before, .ls *::after { box-sizing: border-box; }
|
|
15
|
+
.ls__stage { position: relative; width: 100%; max-width: calc((100svh - 140px) * 16 / 9); margin-inline: auto; aspect-ratio: 16 / 9; container-type: inline-size; overflow: hidden; background: var(--bg); border: 1px solid var(--line); border-radius: clamp(16px, 2cqw, 28px); box-shadow: 0 24px 70px #00000038; }
|
|
16
|
+
.ls__overview, .ls__scene { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(10px, 1.4cqw, 18px); height: 100%; padding: clamp(12px, 2.2cqw, 28px); }
|
|
17
|
+
.ls__scene { display: none; }
|
|
18
|
+
.ls.is-ready:not(.is-reduced) .ls__overview { display: none; }
|
|
19
|
+
.ls.is-ready:not(.is-reduced) .ls__scene { display: grid; }
|
|
20
|
+
.ls__card { display: flex; min-width: 0; min-height: clamp(128px, 25cqw, 310px); flex-direction: column; align-items: center; justify-content: center; gap: clamp(10px, 1.5cqw, 20px); padding: clamp(12px, 2cqw, 26px); overflow: hidden; background: var(--card); border: 1px solid var(--line); border-radius: clamp(12px, 1.6cqw, 22px); box-shadow: inset 0 1px 0 #ffffff08; }
|
|
21
|
+
.ls__demo { display: grid; width: 100%; min-height: clamp(56px, 7cqw, 100px); place-items: center; }
|
|
22
|
+
.ls__label { margin: 0; color: var(--text); font-size: clamp(12px, 1.2cqw, 16px); font-weight: 650; letter-spacing: .01em; }
|
|
23
|
+
.ls__skeleton-card { position: relative; display: grid; width: clamp(180px, 52cqw, 340px); min-height: clamp(92px, 12cqw, 150px); grid-template-columns: auto minmax(0, 1fr); grid-template-rows: repeat(3, 1fr); align-items: center; column-gap: clamp(10px, 1.5cqw, 18px); row-gap: clamp(6px, .8cqw, 10px); padding: clamp(10px, 1.6cqw, 20px); overflow: hidden; background: #ffffff08; border: 1px solid #ffffff12; border-radius: clamp(10px, 1.4cqw, 18px); }
|
|
24
|
+
.ls__avatar { grid-column: 1; grid-row: 1 / 4; width: clamp(30px, 4.4cqw, 58px); aspect-ratio: 1; background: #ffffff20; border-radius: 50%; }
|
|
25
|
+
.ls__line { grid-column: 2; height: clamp(8px, 1.1cqw, 14px); background: #ffffff24; border-radius: 999px; }
|
|
26
|
+
.ls__line--long { width: 100%; }
|
|
27
|
+
.ls__line--medium { width: 78%; }
|
|
28
|
+
.ls__line--short { width: 55%; }
|
|
29
|
+
.ls__skeleton-card::after { content: ""; position: absolute; inset: -30% auto -30% 0; width: 42%; pointer-events: none; background: linear-gradient(90deg, transparent, #ffffff35, transparent); transform: translateX(-260%) skewX(-12deg); }
|
|
30
|
+
.ls__scene .ls__skeleton-card::after { animation: ls-skeleton-shimmer 2s ease-in-out infinite; }
|
|
31
|
+
.ls__progress-demo { width: clamp(180px, 52cqw, 340px); gap: clamp(12px, 1.8cqw, 22px); }
|
|
32
|
+
.ls__progress-track { position: relative; width: 100%; height: clamp(10px, 1.4cqw, 18px); overflow: hidden; background: #ffffff12; border-radius: 999px; }
|
|
33
|
+
.ls__progress-fill { display: block; width: 100%; height: 100%; background: linear-gradient(90deg, var(--accent), #c4ffe9); border-radius: inherit; transform: scaleX(1); transform-origin: left; }
|
|
34
|
+
.ls__scene .ls__progress-fill { transform: scaleX(0); animation: ls-progress-fill 6s cubic-bezier(.4, 0, .2, 1) infinite; }
|
|
35
|
+
.ls__ticks { position: relative; display: block; width: 82%; gap: clamp(4px, .65cqw, 8px); }
|
|
36
|
+
.ls__ticks-base, .ls__ticks-fill { grid-row: 1; grid-column: 1; display: grid; grid-template-columns: repeat(10, minmax(0, 1fr)); gap: inherit; }
|
|
37
|
+
.ls__ticks-fill { position: absolute; inset: 0; clip-path: none; }
|
|
38
|
+
.ls__tick { width: clamp(4px, .55cqw, 7px); aspect-ratio: 1; background: #ffffff24; border-radius: 50%; }
|
|
39
|
+
.ls__ticks-fill .ls__tick { background: var(--accent); }
|
|
40
|
+
.ls__scene .ls__ticks-fill { clip-path: inset(0 100% 0 0); animation: ls-progress-ticks 6s cubic-bezier(.4, 0, .2, 1) infinite; }
|
|
41
|
+
.ls__dots-demo { --bounce: clamp(18px, 3cqw, 36px); display: flex; align-items: center; justify-content: center; gap: clamp(10px, 1.6cqw, 20px); }
|
|
42
|
+
.ls__dot { width: clamp(12px, 1.8cqw, 22px); aspect-ratio: 1; background: var(--accent); border-radius: 50%; box-shadow: 0 0 18px #7cf3c438; }
|
|
43
|
+
.ls__scene .ls__dot { animation: ls-dot-bounce 1.5s cubic-bezier(.34, .01, .32, 1) infinite; }
|
|
44
|
+
.ls__scene .ls__dot:nth-child(2) { animation-delay: .2s; }
|
|
45
|
+
.ls__scene .ls__dot:nth-child(3) { animation-delay: .4s; }
|
|
46
|
+
.ls__success-demo { min-height: clamp(64px, 8cqw, 110px); }
|
|
47
|
+
.ls__success-icon { display: block; width: clamp(52px, 8cqw, 104px); overflow: visible; }
|
|
48
|
+
.ls__spinner { fill: none; stroke: var(--accent); stroke-width: 3; stroke-linecap: round; stroke-dasharray: 78 22; opacity: 0; transform-box: fill-box; transform-origin: center; }
|
|
49
|
+
.ls__check { fill: none; stroke: var(--accent); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 40; stroke-dashoffset: 0; opacity: 1; }
|
|
50
|
+
.ls__scene .ls__spinner { animation: ls-success-spinner 6s cubic-bezier(.4, 0, .2, 1) infinite; }
|
|
51
|
+
.ls__scene .ls__check { opacity: 0; stroke-dashoffset: 40; animation: ls-success-check 6s cubic-bezier(.4, 0, .2, 1) infinite; }
|
|
52
|
+
.ls__clock { opacity: 0; animation: mvl-clock 6s linear infinite; }
|
|
53
|
+
.ls__controls { display: flex; flex-flow: row wrap; align-items: center; gap: clamp(7px, .8vw, 12px); margin-top: 10px; padding: 10px; background: var(--card); border: 1px solid var(--line); border-radius: 14px; }
|
|
54
|
+
.ls__controls[hidden] { display: none; }
|
|
55
|
+
.ls__controls button, .ls__controls select { min-height: 36px; padding: 7px 11px; color: var(--text); background: #ffffff0d; border: 1px solid #ffffff24; border-radius: 9px; font: inherit; font-size: 13px; }
|
|
56
|
+
.ls__controls button { cursor: pointer; }
|
|
57
|
+
.ls__controls button:hover, .ls__controls select:hover { background: #ffffff16; border-color: #ffffff38; }
|
|
58
|
+
.ls__controls label { display: flex; align-items: center; }
|
|
59
|
+
.ls__controls label:first-of-type { flex: 1 1 10rem; }
|
|
60
|
+
.ls__controls input[data-motion-seek] { width: 100%; min-width: 7rem; height: 22px; margin: 0; appearance: none; cursor: pointer; background: linear-gradient(to right, var(--accent) var(--progress,0%), #ffffff33 0); background-position: center; background-repeat: no-repeat; background-size: 100% 6px; border-radius: 999px; }
|
|
61
|
+
.ls__controls input[data-motion-seek]::-webkit-slider-runnable-track { height: 6px; background: linear-gradient(to right, var(--accent) var(--progress,0%), #ffffff33 0); border-radius: 999px; }
|
|
62
|
+
.ls__controls input[data-motion-seek]::-moz-range-track { height: 6px; background: linear-gradient(to right, var(--accent) var(--progress,0%), #ffffff33 0); border-radius: 999px; }
|
|
63
|
+
.ls__controls input[data-motion-seek]::-webkit-slider-thumb { width: 16px; height: 16px; margin-top: -5px; appearance: none; background: var(--accent); border: 2px solid var(--bg); border-radius: 50%; box-shadow: 0 0 0 1px var(--accent); }
|
|
64
|
+
.ls__controls input[data-motion-seek]::-moz-range-thumb { width: 13px; height: 13px; background: var(--accent); border: 2px solid var(--bg); border-radius: 50%; box-shadow: 0 0 0 1px var(--accent); }
|
|
65
|
+
.ls__controls output { min-width: 5.5rem; color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; text-align: right; }
|
|
66
|
+
.ls__controls :is(button, select, input):focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
|
|
67
|
+
.ls__sr-only { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; border: 0 !important; }
|
|
68
|
+
@keyframes ls-skeleton-shimmer { from { transform: translateX(-260%) skewX(-12deg); } to { transform: translateX(260%) skewX(-12deg); } }
|
|
69
|
+
@keyframes ls-progress-fill { 0% { transform: scaleX(0); } 82% { transform: scaleX(1); } 92% { transform: scaleX(1); } 100% { transform: scaleX(0); } }
|
|
70
|
+
@keyframes ls-progress-ticks { 0% { clip-path: inset(0 100% 0 0); } 82% { clip-path: inset(0 0 0 0); } 92% { clip-path: inset(0 0 0 0); } 100% { clip-path: inset(0 100% 0 0); } }
|
|
71
|
+
@keyframes ls-dot-bounce { 0%, 100% { transform: translateY(0) scale(.92); } 42% { transform: translateY(calc(var(--bounce) * -1)) scale(1.06); } 72% { transform: translateY(0) scale(.98); } }
|
|
72
|
+
@keyframes ls-success-spinner { 0% { opacity: 1; transform: rotate(0deg) scale(1); stroke-dasharray: 78 22; } 42% { opacity: 1; transform: rotate(720deg) scale(1); stroke-dasharray: 78 22; } 49% { opacity: .2; transform: rotate(810deg) scale(.62); stroke-dasharray: 38 62; } 50%, 100% { opacity: 0; transform: rotate(810deg) scale(.62); stroke-dasharray: 38 62; } }
|
|
73
|
+
@keyframes ls-success-check { 0%, 46% { opacity: 0; stroke-dashoffset: 40; } 49% { opacity: 1; stroke-dashoffset: 40; } 68%, 88% { opacity: 1; stroke-dashoffset: 0; } 100% { opacity: 0; stroke-dashoffset: 40; } }
|
|
74
|
+
@keyframes mvl-clock { to { opacity: 1; } }
|
|
75
|
+
@media (max-width: 700px) {
|
|
76
|
+
.ls { width: 100%; }
|
|
77
|
+
.ls__stage { max-width: 100%; aspect-ratio: auto; }
|
|
78
|
+
.ls__overview, .ls__scene { height: auto; grid-template-columns: 1fr; padding: 12px; }
|
|
79
|
+
.ls__card { min-height: 128px; }
|
|
80
|
+
.ls__demo { min-height: 56px; }
|
|
81
|
+
.ls__controls { padding: 9px; }
|
|
82
|
+
.ls__controls output { text-align: left; }
|
|
83
|
+
}
|
|
84
|
+
@media (prefers-reduced-motion: reduce) {
|
|
85
|
+
.ls__overview { display: grid !important; }
|
|
86
|
+
.ls__scene, .ls__controls { display: none !important; }
|
|
87
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<meta name="color-scheme" content="dark">
|
|
7
|
+
<title>004 — Loading States | CSS Motion Library</title>
|
|
8
|
+
<script>if (new URLSearchParams(location.search).has('embed')) document.documentElement.classList.add('is-embedded');</script>
|
|
9
|
+
<link rel="stylesheet" href="../../src/demo.css">
|
|
10
|
+
<link rel="stylesheet" href="animation.css">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<nav class="demo-nav">
|
|
14
|
+
<a href="../../">← Library</a>
|
|
15
|
+
<span>004 / Loading States</span>
|
|
16
|
+
<a href="../005-orbit-diagram/">Next: Orbit Diagram ↗</a>
|
|
17
|
+
</nav>
|
|
18
|
+
<main>
|
|
19
|
+
<section class="ls" data-motion-player data-duration="6" aria-labelledby="ls-title">
|
|
20
|
+
<h1 id="ls-title" class="ls__sr-only">Loading States</h1>
|
|
21
|
+
<div class="ls__stage" data-motion-stage>
|
|
22
|
+
<div class="ls__overview">
|
|
23
|
+
<article class="ls__card">
|
|
24
|
+
<div class="ls__demo ls__skeleton-card" aria-hidden="true">
|
|
25
|
+
<span class="ls__avatar"></span>
|
|
26
|
+
<span class="ls__line ls__line--long"></span>
|
|
27
|
+
<span class="ls__line ls__line--medium"></span>
|
|
28
|
+
<span class="ls__line ls__line--short"></span>
|
|
29
|
+
</div>
|
|
30
|
+
<p class="ls__label">Skeleton</p>
|
|
31
|
+
</article>
|
|
32
|
+
<article class="ls__card">
|
|
33
|
+
<div class="ls__demo ls__progress-demo" aria-hidden="true">
|
|
34
|
+
<div class="ls__progress-track"><span class="ls__progress-fill"></span></div>
|
|
35
|
+
<div class="ls__ticks">
|
|
36
|
+
<span class="ls__ticks-base">
|
|
37
|
+
<span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span>
|
|
38
|
+
<span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span>
|
|
39
|
+
</span>
|
|
40
|
+
<span class="ls__ticks-fill">
|
|
41
|
+
<span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span>
|
|
42
|
+
<span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span>
|
|
43
|
+
</span>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
<p class="ls__label">Progress</p>
|
|
47
|
+
</article>
|
|
48
|
+
<article class="ls__card">
|
|
49
|
+
<div class="ls__demo ls__dots-demo" aria-hidden="true">
|
|
50
|
+
<span class="ls__dot"></span>
|
|
51
|
+
<span class="ls__dot"></span>
|
|
52
|
+
<span class="ls__dot"></span>
|
|
53
|
+
</div>
|
|
54
|
+
<p class="ls__label">Dots</p>
|
|
55
|
+
</article>
|
|
56
|
+
<article class="ls__card">
|
|
57
|
+
<div class="ls__demo ls__success-demo" aria-hidden="true">
|
|
58
|
+
<svg class="ls__success-icon" viewBox="0 0 24 24" focusable="false">
|
|
59
|
+
<circle class="ls__spinner" cx="12" cy="12" r="9" pathLength="100"></circle>
|
|
60
|
+
<path class="ls__check" pathLength="40" d="M5 12.5 10 17.5 20 7"></path>
|
|
61
|
+
</svg>
|
|
62
|
+
</div>
|
|
63
|
+
<p class="ls__label">Success</p>
|
|
64
|
+
</article>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div class="ls__scene" aria-hidden="true">
|
|
68
|
+
<article class="ls__card">
|
|
69
|
+
<div class="ls__demo ls__skeleton-card">
|
|
70
|
+
<span class="ls__avatar"></span>
|
|
71
|
+
<span class="ls__line ls__line--long"></span>
|
|
72
|
+
<span class="ls__line ls__line--medium"></span>
|
|
73
|
+
<span class="ls__line ls__line--short"></span>
|
|
74
|
+
</div>
|
|
75
|
+
<p class="ls__label">Skeleton</p>
|
|
76
|
+
</article>
|
|
77
|
+
<article class="ls__card">
|
|
78
|
+
<div class="ls__demo ls__progress-demo">
|
|
79
|
+
<div class="ls__progress-track"><span class="ls__progress-fill"></span></div>
|
|
80
|
+
<div class="ls__ticks">
|
|
81
|
+
<span class="ls__ticks-base">
|
|
82
|
+
<span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span>
|
|
83
|
+
<span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span>
|
|
84
|
+
</span>
|
|
85
|
+
<span class="ls__ticks-fill">
|
|
86
|
+
<span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span>
|
|
87
|
+
<span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span><span class="ls__tick"></span>
|
|
88
|
+
</span>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<p class="ls__label">Progress</p>
|
|
92
|
+
</article>
|
|
93
|
+
<article class="ls__card">
|
|
94
|
+
<div class="ls__demo ls__dots-demo">
|
|
95
|
+
<span class="ls__dot"></span>
|
|
96
|
+
<span class="ls__dot"></span>
|
|
97
|
+
<span class="ls__dot"></span>
|
|
98
|
+
</div>
|
|
99
|
+
<p class="ls__label">Dots</p>
|
|
100
|
+
</article>
|
|
101
|
+
<article class="ls__card">
|
|
102
|
+
<div class="ls__demo ls__success-demo">
|
|
103
|
+
<svg class="ls__success-icon" viewBox="0 0 24 24" focusable="false">
|
|
104
|
+
<circle class="ls__spinner" cx="12" cy="12" r="9" pathLength="100"></circle>
|
|
105
|
+
<path class="ls__check" pathLength="40" d="M5 12.5 10 17.5 20 7"></path>
|
|
106
|
+
</svg>
|
|
107
|
+
</div>
|
|
108
|
+
<p class="ls__label">Success</p>
|
|
109
|
+
</article>
|
|
110
|
+
</div>
|
|
111
|
+
<span class="ls__clock" aria-hidden="true"></span>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="ls__controls" data-motion-controls hidden><button type="button" data-motion-toggle aria-label="Pause animation">Ⅱ Pause</button><button type="button" data-motion-restart aria-label="Replay animation from the beginning">↺ Replay</button><label><span class="ls__sr-only">Animation position in seconds</span><input data-motion-seek type="range" min="0" max="6" step="0.1" value="0" aria-label="Animation position in seconds"></label><output data-motion-time>0:00 / 0:06</output><label><span class="ls__sr-only">Playback speed</span><select data-motion-speed aria-label="Playback speed"><option value="0.5">0.5×</option><option value="1" selected>1×</option><option value="1.5">1.5×</option></select></label></div>
|
|
114
|
+
</section>
|
|
115
|
+
</main>
|
|
116
|
+
<footer class="demo-footer">Four loading patterns · <a href="animation.css">View CSS</a></footer>
|
|
117
|
+
<script type="module" src="../../src/auto.js"></script>
|
|
118
|
+
</body>
|
|
119
|
+
</html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 005 — Orbit Diagram
|
|
2
|
+
|
|
3
|
+
A twelve-second explainer diagram. Four satellites ride a slowly rotating orbit around a
|
|
4
|
+
core; every three seconds one lights up, a connector draws in from the core and the caption
|
|
5
|
+
changes. Labels counter-rotate so they always read upright.
|
|
6
|
+
|
|
7
|
+
| Time | Highlight | Caption |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| 0–3s | Data | Data — collect what matters |
|
|
10
|
+
| 3–6s | Design | Design — shape the experience |
|
|
11
|
+
| 6–9s | Content | Content — say it clearly |
|
|
12
|
+
| 9–12s | Delivery | Delivery — ship and iterate |
|
|
13
|
+
|
|
14
|
+
The orbit makes one full turn per loop, so the loop restarts seamlessly.
|
|
15
|
+
|
|
16
|
+
## Customize
|
|
17
|
+
|
|
18
|
+
- Theme: `--bg`, `--line`, `--text`, `--accent`, `--accent-2` on `.od`.
|
|
19
|
+
- Sizes: `--orbit-size`, `--node-size`, `--core-size`.
|
|
20
|
+
- Labels and captions: edit the node labels, the caption list and the static overview list
|
|
21
|
+
together. For a different number of satellites, change the node positions, the connector
|
|
22
|
+
angles and the per-node keyframe windows (each node owns a `100% / n` slice of the loop).
|
|
23
|
+
- Every keyframe set starts with an explicit `0%` stop. Keep it that way: a keyframe that
|
|
24
|
+
starts at, say, `25%` interpolates from the element's resting style, which here would show
|
|
25
|
+
the connector fully drawn before its turn.
|
|
26
|
+
|
|
27
|
+
## Accessibility
|
|
28
|
+
|
|
29
|
+
The animated diagram is `aria-hidden`; the static overview carries the heading and the four
|
|
30
|
+
captions as a list. Reduced motion and no-JS show that overview.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/* Theme colors: --bg = navy surface, --line = muted structure, --text = labels, --accent = primary blue, --accent-2 = active amber. */
|
|
2
|
+
.od {
|
|
3
|
+
--bg: #0b1020;
|
|
4
|
+
--line: #2a3a66;
|
|
5
|
+
--text: #e6ecff;
|
|
6
|
+
--accent: #8fa8ff;
|
|
7
|
+
--accent-2: #ffd36e;
|
|
8
|
+
--node-size: clamp(3.5rem, 7cqw, 5.5rem);
|
|
9
|
+
--core-size: clamp(5rem, 10cqw, 8rem);
|
|
10
|
+
--orbit-size: clamp(17rem, 44cqw, 42rem);
|
|
11
|
+
width: 100%;
|
|
12
|
+
max-width: calc(177.7778svh - 248.8889px);
|
|
13
|
+
max-width: calc((100svh - 140px) * 16 / 9);
|
|
14
|
+
margin-inline: auto;
|
|
15
|
+
color: var(--text);
|
|
16
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
17
|
+
}
|
|
18
|
+
.od, .od * { box-sizing: border-box; }
|
|
19
|
+
.od__stage { position: relative; width: 100%; max-width: calc(177.7778svh - 248.8889px); max-width: calc((100svh - 140px) * 16 / 9); margin-inline: auto; aspect-ratio: 16 / 9; container-type: inline-size; overflow: hidden; isolation: isolate; border: 1px solid var(--line); border-radius: 24px; background: radial-gradient(circle at 50% 46%, #8fa8ff1f 0 12%, transparent 42%), var(--bg); box-shadow: 0 20px 70px #0006; }
|
|
20
|
+
.od__overview { position: absolute; inset: 0; display: grid; place-items: center; padding: clamp(1.25rem, 5cqw, 4rem); }
|
|
21
|
+
.od__overview-copy { width: min(100%, 44rem); }
|
|
22
|
+
.od__eyebrow { display: block; margin: 0 0 .5rem; color: var(--accent); font-size: .72rem; font-weight: 800; letter-spacing: .16em; text-transform: uppercase; }
|
|
23
|
+
.od__overview h3 { margin: 0 0 1.25rem; font-size: clamp(1.8rem, 4.5cqw, 3.5rem); line-height: 1.05; }
|
|
24
|
+
.od__overview-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .25rem 2rem; margin: 0; padding: 0; list-style: none; }
|
|
25
|
+
.od__overview-list li { position: relative; padding: .8rem 0 .8rem 1.5rem; border-top: 1px solid var(--line); }
|
|
26
|
+
.od__overview-list li::before { content: "→"; position: absolute; left: 0; color: var(--accent-2); }
|
|
27
|
+
.od__scene { position: absolute; inset: 0; display: none; overflow: hidden; pointer-events: none; }
|
|
28
|
+
.od__scene-heading { position: absolute; top: clamp(1rem, 3.5cqw, 3rem); left: clamp(1rem, 3.5cqw, 3rem); z-index: 6; display: grid; gap: .2rem; }
|
|
29
|
+
.od__scene-heading strong { font-size: clamp(1rem, 2cqw, 1.35rem); }
|
|
30
|
+
.od__orbit-frame { position: absolute; top: 50%; left: 50%; z-index: 2; width: var(--orbit-size); height: var(--orbit-size); transform: translate(-50%, -50%); }
|
|
31
|
+
.od__orbit { position: absolute; inset: 0; border: 2px dashed var(--line); border-radius: 50%; box-shadow: 0 0 0 1px #8fa8ff12, 0 0 60px #8fa8ff0d; animation: od-spin 12s linear infinite; }
|
|
32
|
+
.od__connector-frame { position: absolute; top: 50%; left: 50%; z-index: 1; width: 100%; height: 100%; transform: translate(-50%, -50%); }
|
|
33
|
+
.od__connectors { position: absolute; inset: 0; }
|
|
34
|
+
.od__node { position: absolute; z-index: 2; display: grid; place-items: center; width: var(--node-size); height: var(--node-size); transform: translate(-50%, -50%); }
|
|
35
|
+
.od__node--data { top: 0; left: 50%; }
|
|
36
|
+
.od__node--design { top: 50%; left: 100%; }
|
|
37
|
+
.od__node--content { top: 100%; left: 50%; }
|
|
38
|
+
.od__node--delivery { top: 50%; left: 0; }
|
|
39
|
+
.od__disc { position: relative; display: grid; place-items: center; width: 100%; height: 100%; border: 1px solid var(--line); border-radius: 50%; background: var(--bg); box-shadow: 0 8px 28px #0008; transform-origin: center; }
|
|
40
|
+
.od__disc::after { content: ""; position: absolute; z-index: 0; inset: -.3rem; border: 2px solid var(--accent-2); border-radius: 50%; box-shadow: 0 0 1.25rem var(--accent-2); opacity: 0; pointer-events: none; }
|
|
41
|
+
.od__label { position: relative; z-index: 1; font-size: clamp(.78rem, 1.35cqw, 1.05rem); font-weight: 750; line-height: 1; white-space: nowrap; transform-origin: center; animation: od-counter-spin 12s linear infinite; }
|
|
42
|
+
.od__core { position: absolute; top: 50%; left: 50%; z-index: 3; display: grid; place-items: center; width: var(--core-size); height: var(--core-size); border: 1px solid var(--accent); border-radius: 50%; background: radial-gradient(circle, #1b2850, var(--bg) 72%); box-shadow: 0 0 2rem #8fa8ff2e; transform: translate(-50%, -50%); }
|
|
43
|
+
.od__core span { font-size: clamp(.9rem, 1.7cqw, 1.15rem); font-weight: 800; }
|
|
44
|
+
.od__connector { position: absolute; top: 50%; left: 50%; width: 50%; height: 2px; transform-origin: left center; }
|
|
45
|
+
.od__connector--data { transform: rotate(-90deg); }
|
|
46
|
+
.od__connector--design { transform: rotate(0deg); }
|
|
47
|
+
.od__connector--content { transform: rotate(90deg); }
|
|
48
|
+
.od__connector--delivery { transform: rotate(180deg); }
|
|
49
|
+
.od__connector-line { display: block; width: 100%; height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-2)); box-shadow: 0 0 10px #8fa8ff66; transform-origin: left center; }
|
|
50
|
+
.od__captions { position: absolute; bottom: clamp(1rem, 3.5cqw, 3rem); left: clamp(1rem, 3.5cqw, 3rem); z-index: 5; width: min(34cqw, 27rem); height: clamp(5rem, 9cqw, 7rem); overflow: hidden; border: 1px solid var(--line); border-radius: 16px; background: #0b1020e8; box-shadow: 0 14px 40px #0005; backdrop-filter: blur(8px); }
|
|
51
|
+
.od__caption { position: absolute; inset: 1rem 1.2rem; display: flex; align-items: center; margin: 0; font-size: clamp(1rem, 1.9cqw, 1.35rem); font-weight: 650; line-height: 1.35; opacity: 0; transform: translateY(10px); }
|
|
52
|
+
.od__controls { display: flex; align-items: center; flex-wrap: wrap; gap: .65rem; width: 100%; margin-top: .75rem; padding: .7rem; border: 1px solid var(--line); border-radius: 14px; background: var(--bg); }
|
|
53
|
+
.od__controls[hidden] { display: none; }
|
|
54
|
+
.od__controls button, .od__controls select { min-height: 2.35rem; padding: .45rem .7rem; border: 1px solid var(--line); border-radius: .6rem; background: #111a30; color: var(--text); font: inherit; font-size: .86rem; }
|
|
55
|
+
.od__controls button:hover, .od__controls select:hover { border-color: var(--accent); }
|
|
56
|
+
.od__controls label { display: flex; align-items: center; }
|
|
57
|
+
.od__controls > label:first-of-type { flex: 1 1 12rem; }
|
|
58
|
+
.od__controls input[type="range"] { flex: 1; width: 100%; min-width: 8rem; height: .4rem; margin: 0 .25rem; appearance: none; border-radius: 999px; background: linear-gradient(to right, var(--accent) var(--progress,0%), #ffffff33 0); cursor: pointer; }
|
|
59
|
+
.od__controls input[type="range"]::-webkit-slider-runnable-track { height: .4rem; border-radius: 999px; background: transparent; }
|
|
60
|
+
.od__controls input[type="range"]::-webkit-slider-thumb { width: 1rem; height: 1rem; margin-top: -.3rem; appearance: none; border: 2px solid var(--bg); border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
|
|
61
|
+
.od__controls input[type="range"]::-moz-range-track { height: .4rem; border-radius: 999px; background: linear-gradient(to right, var(--accent) var(--progress,0%), #ffffff33 0); }
|
|
62
|
+
.od__controls input[type="range"]::-moz-range-progress { height: .4rem; background: transparent; }
|
|
63
|
+
.od__controls input[type="range"]::-moz-range-thumb { width: .85rem; height: .85rem; border: 2px solid var(--bg); border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
|
|
64
|
+
.od__controls :focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }
|
|
65
|
+
.od__controls output { min-width: 5.25rem; color: var(--text); font-size: .82rem; font-variant-numeric: tabular-nums; text-align: center; }
|
|
66
|
+
.od.is-ready:not(.is-reduced) .od__overview { display: none; }
|
|
67
|
+
.od.is-ready:not(.is-reduced) .od__scene { display: block; }
|
|
68
|
+
.od.is-reduced .od__overview { display: grid; }
|
|
69
|
+
.od.is-reduced .od__scene { display: none; }
|
|
70
|
+
.od.is-reduced .od__controls { display: none !important; }
|
|
71
|
+
.od__clock { position: absolute; display: block; width: 0; height: 0; opacity: 0; animation: mvl-clock 12s linear infinite; }
|
|
72
|
+
@media (max-width: 48rem) {
|
|
73
|
+
.od { --orbit-size: 64cqw; --node-size: clamp(3.25rem, 14cqw, 4rem); --core-size: clamp(4.5rem, 20cqw, 5.5rem); max-width: 100%; }
|
|
74
|
+
.od__stage { max-width: 100%; aspect-ratio: 3 / 4; border-radius: 18px; }
|
|
75
|
+
.od__overview-list { grid-template-columns: 1fr; }
|
|
76
|
+
.od__scene-heading { top: 1.25rem; left: 1.25rem; }
|
|
77
|
+
.od__captions { right: 1.25rem; bottom: 1.25rem; left: 1.25rem; width: auto; height: 6rem; }
|
|
78
|
+
.od__caption { inset: .85rem 1rem; font-size: 1rem; }
|
|
79
|
+
.od__controls > label:first-of-type { flex-basis: 100%; }
|
|
80
|
+
.od__controls input[type="range"] { max-width: none; }
|
|
81
|
+
.od__controls button { flex: 1 1 7rem; }
|
|
82
|
+
}
|
|
83
|
+
@media (prefers-reduced-motion: reduce) {
|
|
84
|
+
.od__overview { display: grid !important; }
|
|
85
|
+
.od__scene { display: none !important; }
|
|
86
|
+
.od__controls { display: none !important; }
|
|
87
|
+
}
|
|
88
|
+
@keyframes od-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
|
89
|
+
@keyframes od-counter-spin { from { transform: rotate(0deg); } to { transform: rotate(-360deg); } }
|
|
90
|
+
@keyframes od-highlight-data { 0% { transform: scale(1); border-color: var(--line); } 5%, 18% { transform: scale(1.13); border-color: var(--accent-2); } 25%, 100% { transform: scale(1); border-color: var(--line); } }
|
|
91
|
+
@keyframes od-highlight-design { 0%, 25% { transform: scale(1); border-color: var(--line); } 30%, 43% { transform: scale(1.13); border-color: var(--accent-2); } 50%, 100% { transform: scale(1); border-color: var(--line); } }
|
|
92
|
+
@keyframes od-highlight-content { 0%, 50% { transform: scale(1); border-color: var(--line); } 55%, 68% { transform: scale(1.13); border-color: var(--accent-2); } 75%, 100% { transform: scale(1); border-color: var(--line); } }
|
|
93
|
+
@keyframes od-highlight-delivery { 0%, 75% { transform: scale(1); border-color: var(--line); } 80%, 93% { transform: scale(1.13); border-color: var(--accent-2); } 100% { transform: scale(1); border-color: var(--line); } }
|
|
94
|
+
@keyframes od-glow-data { 0% { opacity: 0; transform: scale(.88); } 5%, 18% { opacity: .95; transform: scale(1); } 25%, 100% { opacity: 0; transform: scale(1.08); } }
|
|
95
|
+
@keyframes od-glow-design { 0%, 25% { opacity: 0; transform: scale(.88); } 30%, 43% { opacity: .95; transform: scale(1); } 50%, 100% { opacity: 0; transform: scale(1.08); } }
|
|
96
|
+
@keyframes od-glow-content { 0%, 50% { opacity: 0; transform: scale(.88); } 55%, 68% { opacity: .95; transform: scale(1); } 75%, 100% { opacity: 0; transform: scale(1.08); } }
|
|
97
|
+
@keyframes od-glow-delivery { 0%, 75% { opacity: 0; transform: scale(.88); } 80%, 93% { opacity: .95; transform: scale(1); } 100% { opacity: 0; transform: scale(1.08); } }
|
|
98
|
+
@keyframes od-line-data { 0% { transform: scaleX(0); } 7%, 20% { transform: scaleX(1); } 25%, 100% { transform: scaleX(0); } }
|
|
99
|
+
@keyframes od-line-design { 0%, 25% { transform: scaleX(0); } 32%, 45% { transform: scaleX(1); } 50%, 100% { transform: scaleX(0); } }
|
|
100
|
+
@keyframes od-line-content { 0%, 50% { transform: scaleX(0); } 57%, 70% { transform: scaleX(1); } 75%, 100% { transform: scaleX(0); } }
|
|
101
|
+
@keyframes od-line-delivery { 0%, 75% { transform: scaleX(0); } 82%, 95% { transform: scaleX(1); } 100% { transform: scaleX(0); } }
|
|
102
|
+
@keyframes od-caption-data { 0% { opacity: 0; transform: translateY(10px); } 4%, 20% { opacity: 1; transform: translateY(0); } 25%, 100% { opacity: 0; transform: translateY(-8px); } }
|
|
103
|
+
@keyframes od-caption-design { 0%, 25% { opacity: 0; transform: translateY(10px); } 29%, 45% { opacity: 1; transform: translateY(0); } 50%, 100% { opacity: 0; transform: translateY(-8px); } }
|
|
104
|
+
@keyframes od-caption-content { 0%, 50% { opacity: 0; transform: translateY(10px); } 54%, 70% { opacity: 1; transform: translateY(0); } 75%, 100% { opacity: 0; transform: translateY(-8px); } }
|
|
105
|
+
@keyframes od-caption-delivery { 0%, 75% { opacity: 0; transform: translateY(10px); } 79%, 95% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-8px); } }
|
|
106
|
+
@keyframes mvl-clock { to { opacity: 1; } }
|
|
107
|
+
.od__node--data .od__disc { animation: od-highlight-data 12s ease-in-out infinite; }
|
|
108
|
+
.od__node--design .od__disc { animation: od-highlight-design 12s ease-in-out infinite; }
|
|
109
|
+
.od__node--content .od__disc { animation: od-highlight-content 12s ease-in-out infinite; }
|
|
110
|
+
.od__node--delivery .od__disc { animation: od-highlight-delivery 12s ease-in-out infinite; }
|
|
111
|
+
.od__node--data .od__disc::after { animation: od-glow-data 12s ease-in-out infinite; }
|
|
112
|
+
.od__node--design .od__disc::after { animation: od-glow-design 12s ease-in-out infinite; }
|
|
113
|
+
.od__node--content .od__disc::after { animation: od-glow-content 12s ease-in-out infinite; }
|
|
114
|
+
.od__node--delivery .od__disc::after { animation: od-glow-delivery 12s ease-in-out infinite; }
|
|
115
|
+
.od__connector--data .od__connector-line { animation: od-line-data 12s ease-in-out infinite; }
|
|
116
|
+
.od__connector--design .od__connector-line { animation: od-line-design 12s ease-in-out infinite; }
|
|
117
|
+
.od__connector--content .od__connector-line { animation: od-line-content 12s ease-in-out infinite; }
|
|
118
|
+
.od__connector--delivery .od__connector-line { animation: od-line-delivery 12s ease-in-out infinite; }
|
|
119
|
+
.od__caption--data { animation: od-caption-data 12s ease-in-out infinite; }
|
|
120
|
+
.od__caption--design { animation: od-caption-design 12s ease-in-out infinite; }
|
|
121
|
+
.od__caption--content { animation: od-caption-content 12s ease-in-out infinite; }
|
|
122
|
+
.od__caption--delivery { animation: od-caption-delivery 12s ease-in-out infinite; }
|
|
123
|
+
.od__sr-only { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; border: 0 !important; }
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="dark"><title>005 — Orbit Diagram | CSS Motion Library</title><script>if (new URLSearchParams(location.search).has('embed')) document.documentElement.classList.add('is-embedded');</script><link rel="stylesheet" href="../../src/demo.css"><link rel="stylesheet" href="animation.css"></head><body><nav class="demo-nav"><a href="../../">← Library</a><span>005 / Orbit Diagram</span><a href="../001-commerce-storyboard/">Back to 001 ↗</a></nav><main>
|
|
2
|
+
<section class="od" data-motion-player data-duration="12" aria-labelledby="od-title">
|
|
3
|
+
<h2 class="od__sr-only" id="od-title">How the pieces connect</h2>
|
|
4
|
+
|
|
5
|
+
<div class="od__stage" data-motion-stage>
|
|
6
|
+
<div class="od__overview">
|
|
7
|
+
<div class="od__overview-copy">
|
|
8
|
+
<p class="od__eyebrow">Systems diagram</p>
|
|
9
|
+
<h3>How the pieces connect</h3>
|
|
10
|
+
<ul class="od__overview-list">
|
|
11
|
+
<li>Data — collect what matters</li>
|
|
12
|
+
<li>Design — shape the experience</li>
|
|
13
|
+
<li>Content — say it clearly</li>
|
|
14
|
+
<li>Delivery — ship and iterate</li>
|
|
15
|
+
</ul>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="od__scene" aria-hidden="true">
|
|
20
|
+
<div class="od__scene-heading">
|
|
21
|
+
<span class="od__eyebrow">System map</span>
|
|
22
|
+
<strong>How the pieces connect</strong>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="od__orbit-frame">
|
|
26
|
+
<div class="od__orbit">
|
|
27
|
+
<div class="od__connector-frame">
|
|
28
|
+
<div class="od__connectors">
|
|
29
|
+
<div class="od__connector od__connector--data"><span class="od__connector-line"></span></div>
|
|
30
|
+
<div class="od__connector od__connector--design"><span class="od__connector-line"></span></div>
|
|
31
|
+
<div class="od__connector od__connector--content"><span class="od__connector-line"></span></div>
|
|
32
|
+
<div class="od__connector od__connector--delivery"><span class="od__connector-line"></span></div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="od__node od__node--data">
|
|
37
|
+
<div class="od__disc"><span class="od__label">Data</span></div>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="od__node od__node--design">
|
|
40
|
+
<div class="od__disc"><span class="od__label">Design</span></div>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="od__node od__node--content">
|
|
43
|
+
<div class="od__disc"><span class="od__label">Content</span></div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="od__node od__node--delivery">
|
|
46
|
+
<div class="od__disc"><span class="od__label">Delivery</span></div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="od__core"><span>Core</span></div>
|
|
52
|
+
|
|
53
|
+
<div class="od__captions">
|
|
54
|
+
<p class="od__caption od__caption--data">Data — collect what matters</p>
|
|
55
|
+
<p class="od__caption od__caption--design">Design — shape the experience</p>
|
|
56
|
+
<p class="od__caption od__caption--content">Content — say it clearly</p>
|
|
57
|
+
<p class="od__caption od__caption--delivery">Delivery — ship and iterate</p>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<span class="od__clock" aria-hidden="true"></span>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="od__controls" data-motion-controls hidden><button type="button" data-motion-toggle aria-label="Pause animation">Ⅱ Pause</button><button type="button" data-motion-restart aria-label="Replay animation from the beginning">↺ Replay</button><label><span class="od__sr-only">Animation position in seconds</span><input data-motion-seek type="range" min="0" max="12" step="0.1" value="0" aria-label="Animation position in seconds"></label><output data-motion-time>0:00 / 0:12</output><label><span class="od__sr-only">Playback speed</span><select data-motion-speed aria-label="Playback speed"><option value="0.5">0.5×</option><option value="1" selected>1×</option><option value="1.5">1.5×</option></select></label></div>
|
|
65
|
+
</section>
|
|
66
|
+
</main><footer class="demo-footer">A rotating systems diagram with sequential highlights · <a href="animation.css">View CSS</a></footer><script type="module" src="../../src/auto.js"></script></body></html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "css-motion-library",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Copy-paste CSS animations with a tiny, accessible playback controller. No framework, no build step.",
|
|
5
|
+
"keywords": ["css", "animation", "motion", "keyframes", "web-animations", "accessibility", "reduced-motion"],
|
|
6
|
+
"homepage": "https://andrewrico.github.io/css-motion-library/",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Andrewrico/css-motion-library.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": "https://github.com/Andrewrico/css-motion-library/issues",
|
|
12
|
+
"author": "Andrew Rico",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"exports": {
|
|
16
|
+
"./player.js": "./src/player.js",
|
|
17
|
+
"./examples/*": "./examples/*",
|
|
18
|
+
"./catalog.json": "./catalog.json"
|
|
19
|
+
},
|
|
20
|
+
"files": ["src/", "examples/", "!examples/**/source.njk", "catalog.json", "LICENSE", "README.md"],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"dev": "node scripts/serve.mjs",
|
|
26
|
+
"test": "node --test",
|
|
27
|
+
"check": "node scripts/check.mjs",
|
|
28
|
+
"test:browser": "node scripts/browser-check.mjs"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"playwright": "^1.64.1"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/auto.js
ADDED
package/src/demo.css
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
:root { color-scheme: dark; --color-primary: #fffc00; --font-family-1: system-ui, sans-serif; --font-family-2: 'Arial Black', 'Helvetica Neue', system-ui, sans-serif; }
|
|
2
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
3
|
+
body { margin: 0; background: #090b0a; color: #edeee7; font-family: system-ui, sans-serif; }
|
|
4
|
+
button, input { font: inherit; }
|
|
5
|
+
a { color: inherit; }
|
|
6
|
+
.demo-nav { display: flex; gap: 24px; align-items: center; justify-content: space-between; padding: 22px 4vw; border-bottom: 1px solid #30372b; font-size: 12px; }
|
|
7
|
+
.demo-nav a { text-decoration: none; }
|
|
8
|
+
.demo-nav a:hover { color: #e6ee45; }
|
|
9
|
+
.demo-footer { padding: 24px 4vw; color: #a9b3a4; font-size: 12px; }
|
|
10
|
+
.demo-footer a { margin-left: 20px; }
|
|
11
|
+
.is-embedded .demo-nav, .is-embedded .demo-footer { display: none; }
|
|
12
|
+
@media (max-width: 600px) { .demo-nav > span { display: none; } }
|