project-bifrost-platform/design/preview/motion.html
2026-04-18 16:09:49 +02:00

228 lines
7.5 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Motion</title>
<link rel="stylesheet" href="../colors_and_type.css" />
<style>
:root {
--ease: cubic-bezier(0.2, 0, 0, 1);
}
html, body { margin: 0; background: var(--background); font-family: var(--font-sans); }
.card { padding: 20px 24px; box-sizing: border-box; display: grid; grid-template-columns: repeat(2, 1fr); grid-auto-rows: 150px; gap: 14px; }
.tile {
position: relative;
background: var(--surface-container);
border-radius: 12px;
padding: 14px 16px;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.k { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--on-surface-variant); }
.v { font-family: var(--font-serif); font-style: italic; color: var(--on-surface); font-size: 13px; }
.stage {
flex: 1;
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin: 4px 0;
}
.note { font-size: 10px; color: #9a8679; }
/* Easing demo — a block slides left→right on loop, one easing only */
.ease-track {
position: relative;
width: 100%;
height: 2px;
background: rgba(186,186,176,0.4);
border-radius: 2px;
}
.ease-dot {
position: absolute;
top: 50%;
left: 0;
width: 12px; height: 12px;
margin-top: -6px; margin-left: -6px;
background: #785f53;
border-radius: 999px;
animation: easeMove 2.4s var(--ease) infinite;
}
@keyframes easeMove {
0% { left: 0%; }
45% { left: 100%; }
55% { left: 100%; }
100% { left: 0%; }
}
/* Durations — three bars of 140 / 240 / 420 ms */
.bars { display: flex; flex-direction: column; gap: 8px; width: 100%; }
.bar { position: relative; height: 6px; border-radius: 3px; background: rgba(186,186,176,0.3); }
.bar::before {
content: "";
position: absolute;
left: 0; top: 0; bottom: 0;
border-radius: 3px;
background: #785f53;
animation-iteration-count: infinite;
animation-timing-function: var(--ease);
}
.bar-140::before { animation-name: fillOnce; animation-duration: 1.4s; }
.bar-240::before { animation-name: fillOnce; animation-duration: 1.8s; }
.bar-420::before { animation-name: fillOnce; animation-duration: 2.4s; }
@keyframes fillOnce {
0% { width: 0%; }
28% { width: 100%; }
72% { width: 100%; }
100% { width: 0%; }
}
.bar-row { display: flex; align-items: center; gap: 8px; }
.bar-row .tag { font-size: 10px; color: var(--on-surface-variant); width: 58px; letter-spacing: 0.04em; }
.bar-row .bar { flex: 1; }
/* Entrance — fade + 4px translate up, repeating */
.entrance {
display: flex; gap: 10px; align-items: flex-end; justify-content: center;
}
.entrance .chip {
background: #fffcf7;
border-radius: 8px;
padding: 10px 12px;
font-family: var(--font-serif);
font-size: 12px;
color: var(--on-surface);
box-shadow: 0 6px 14px -10px rgba(56,56,49,0.25);
opacity: 0;
transform: translateY(4px);
animation: enter 2.8s var(--ease) infinite;
}
.entrance .chip:nth-child(2) { animation-delay: 0.15s; }
.entrance .chip:nth-child(3) { animation-delay: 0.30s; }
@keyframes enter {
0%, 15% { opacity: 0; transform: translateY(4px); }
35%, 75% { opacity: 1; transform: translateY(0); }
90%, 100% { opacity: 0; transform: translateY(4px); }
}
/* Press — button dips 1px translateY, no scale */
.press-btn {
background: #785f53;
color: #fffcf7;
font: 500 13px/1 var(--font-sans);
border-radius: 10px;
padding: 11px 18px;
border: 0;
animation: press 2s var(--ease) infinite;
}
@keyframes press {
0%, 35% { transform: translateY(0); background: #785f53; }
45%, 60% { transform: translateY(1px); background: #6b5348; }
70%, 100% { transform: translateY(0); background: #785f53; }
}
/* Focus ring — 2px secondary @ 40% with 3px offset, appears and fades */
.focus-stage {
display: flex; align-items: center; justify-content: center; width: 100%;
}
.focus-field {
background: #e7e1d0;
border-radius: 10px;
padding: 10px 14px;
font-family: var(--font-serif);
font-size: 13px;
color: var(--on-surface-variant);
position: relative;
animation: focusBlink 3.4s var(--ease) infinite;
}
@keyframes focusBlink {
0%, 40% { box-shadow: 0 0 0 0 rgba(120,95,83,0), 0 0 0 0 rgba(120,95,83,0); }
50%, 75% { box-shadow: 0 0 0 3px var(--background), 0 0 0 5px rgba(120,95,83,0.4); }
85%, 100% { box-shadow: 0 0 0 0 rgba(120,95,83,0), 0 0 0 0 rgba(120,95,83,0); }
}
/* Tonal shift — surface tier animates through the scale */
.tonal-shift {
width: 72px; height: 52px; border-radius: 10px;
animation: tonalShift 4s var(--ease) infinite;
}
@keyframes tonalShift {
0%, 100% { background: #fffcf7; }
25% { background: #faf6ee; }
50% { background: #efeadc; }
75% { background: #ddd6c3; }
}
.stat { display: flex; align-items: baseline; gap: 6px; }
.stat b { font-family: var(--font-serif); font-style: italic; color: var(--on-surface); font-size: 13px; font-weight: 400; }
</style>
</head>
<body>
<div class="card">
<!-- Easing -->
<div class="tile">
<div><div class="k">Standard ease</div><div class="v">cubic-bezier(0.2, 0, 0, 1)</div></div>
<div class="stage">
<div class="ease-track"><span class="ease-dot"></span></div>
</div>
<div class="note">One curve for the entire system. No bounces, no springs.</div>
</div>
<!-- Durations -->
<div class="tile">
<div><div class="k">Durations</div><div class="v">140 · 240 · 420 ms</div></div>
<div class="stage">
<div class="bars">
<div class="bar-row"><span class="tag">140 · micro</span><div class="bar bar-140"></div></div>
<div class="bar-row"><span class="tag">240 · default</span><div class="bar bar-240"></div></div>
<div class="bar-row"><span class="tag">420 · layout</span><div class="bar bar-420"></div></div>
</div>
</div>
<div class="note">Fades and tonal shifts only.</div>
</div>
<!-- Entrance -->
<div class="tile">
<div><div class="k">Entrance</div><div class="v">Fade + 4px translate-up</div></div>
<div class="stage">
<div class="entrance">
<div class="chip">§ 1</div>
<div class="chip">§ 2</div>
<div class="chip">§ 3</div>
</div>
</div>
<div class="note">Staggered. Never scale. Never off-screen.</div>
</div>
<!-- Press -->
<div class="tile">
<div><div class="k">Press</div><div class="v">1px translateY, no scale</div></div>
<div class="stage">
<button class="press-btn" type="button">Open the archive</button>
</div>
<div class="note">Fill darkens one step on press.</div>
</div>
<!-- Focus -->
<div class="tile">
<div><div class="k">Focus</div><div class="v">2px ring · 3px offset</div></div>
<div class="stage">
<div class="focus-stage"><div class="focus-field">Search the archive</div></div>
</div>
<div class="note">Secondary brown at 40% opacity. Never a solid border.</div>
</div>
<!-- Tonal shift -->
<div class="tile">
<div><div class="k">Tonal shift</div><div class="v">Background tier, not shadow</div></div>
<div class="stage">
<div class="tonal-shift"></div>
</div>
<div class="note">Depth is a paper tier. Elevation is never a box-shadow first.</div>
</div>
</div>
</body>
</html>