Download frontend/src/ui/motion/motion.css from ruslanmv/HomePilot: direct link, hf CLI and curl.
- Browser
- Download file 15.1 kB
-
https://huggingface.co/spaces/ruslanmv/HomePilot/resolve/main/frontend/src/ui/motion/motion.css
- Command line
-
hf download hf://spaces/ruslanmv/HomePilot/frontend/src/ui/motion/motion.css
-
curl -L -o motion.css https://huggingface.co/spaces/ruslanmv/HomePilot/resolve/main/frontend/src/ui/motion/motion.css
15.1 kB
| /* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| HomePilot motion system | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| One restrained vocabulary of animations used by chat, voice, dialogs and | |
| navigation. Everything here is additive: a class does nothing until a | |
| component opts in, and the existing animations elsewhere are untouched. | |
| <html data-hp-motion="enterprise|minimal|off"> (set from Settings β Motion) | |
| chooses how much plays; the OS "reduce motion" setting always wins. | |
| Numbers follow the catalogue in docs/chat-motion.md (1β22). | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| :root { | |
| --hp-ease-out: cubic-bezier(0.2, 0.8, 0.2, 1); | |
| --hp-ease-in: cubic-bezier(0.4, 0, 1, 1); | |
| --hp-ease-drawer: cubic-bezier(0.32, 0.72, 0, 1); | |
| --hp-dur-fast: 140ms; | |
| --hp-dur: 220ms; | |
| --hp-dur-slow: 320ms; | |
| --hp-motion-ink: rgba(255, 255, 255, 0.5); /* 5.3:1 on the app background */ | |
| --hp-motion-ink-peak: rgba(255, 255, 255, 0.95); | |
| } | |
| /* 1 β Text shimmer: a band of light sweeps across a working label. */ | |
| .hp-shimmer-text { | |
| color: var(--hp-motion-ink); | |
| background-image: linear-gradient( | |
| 100deg, | |
| var(--hp-motion-ink) 0%, | |
| var(--hp-motion-ink) 42%, | |
| var(--hp-motion-ink-peak) 50%, | |
| var(--hp-motion-ink) 58%, | |
| var(--hp-motion-ink) 100% | |
| ); | |
| background-size: 250% 100%; | |
| background-position: 100% 0; | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| animation: hp-shimmer 2.2s linear infinite; | |
| } | |
| @keyframes hp-shimmer { | |
| from { background-position: 100% 0; } | |
| to { background-position: 0% 0; } | |
| } | |
| /* 2 β Text opacity pulse: a label gently brightens and dims. */ | |
| .hp-pulse-text { animation: hp-opacity-pulse 1.8s ease-in-out infinite; } | |
| @keyframes hp-opacity-pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| /* 3 + 16 β Streaming text / message reveal: the cursor rides the end of the | |
| last line while an answer is revealed (see StreamReveal.tsx). */ | |
| [data-hp-caret]::after, | |
| .hp-caret { | |
| content: ''; | |
| display: inline-block; | |
| width: 0.55em; | |
| height: 0.55em; | |
| margin-left: 0.3em; | |
| vertical-align: 0.05em; | |
| border-radius: 50%; | |
| background: currentColor; | |
| animation: hp-caret 1s ease-in-out infinite; | |
| } | |
| /* 4 β Typing cursor */ | |
| @keyframes hp-caret { | |
| 0%, 100% { opacity: 1; transform: scale(1); } | |
| 50% { opacity: 0.35; transform: scale(0.8); } | |
| } | |
| .hp-stream { cursor: default; } | |
| /* 5 β Loading dots */ | |
| .hp-dots { display: inline-flex; align-items: center; gap: 4px; height: 1em; } | |
| .hp-dots > span { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background: currentColor; | |
| opacity: 0.3; | |
| animation: hp-dot 1.2s var(--hp-ease-out) infinite; | |
| } | |
| .hp-dots > span:nth-child(2) { animation-delay: 0.15s; } | |
| .hp-dots > span:nth-child(3) { animation-delay: 0.3s; } | |
| @keyframes hp-dot { | |
| 0%, 80%, 100% { opacity: 0.3; transform: translateY(0); } | |
| 40% { opacity: 1; transform: translateY(-3px); } | |
| } | |
| /* 6 β Loading spinner (inline size; the large one is .hp-spinner) */ | |
| .hp-spin { | |
| display: inline-block; | |
| flex: 0 0 auto; | |
| width: var(--hp-spin-size, 16px); | |
| height: var(--hp-spin-size, 16px); | |
| border-radius: 50%; | |
| border: 2px solid rgba(255, 255, 255, 0.18); | |
| border-top-color: currentColor; | |
| animation: hp-spin 0.8s linear infinite; | |
| } | |
| /* 7 β Skeleton shimmer */ | |
| .hp-skeleton { | |
| position: relative; | |
| display: block; | |
| overflow: hidden; | |
| background: rgba(255, 255, 255, 0.06); | |
| border-radius: 8px; | |
| } | |
| .hp-skeleton::after { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| transform: translateX(-100%); | |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent); | |
| animation: hp-skeleton 1.4s ease-in-out infinite; | |
| } | |
| @keyframes hp-skeleton { to { transform: translateX(100%); } } | |
| /* 8 + 9 β Fade in / fade out */ | |
| .hp-enter-fade { animation: hp-fade-in var(--hp-dur) var(--hp-ease-out) backwards; } | |
| .hp-exit-fade { animation: hp-fade-out var(--hp-dur-fast) var(--hp-ease-in) forwards; } | |
| @keyframes hp-fade-in { from { opacity: 0; } } | |
| @keyframes hp-fade-out { to { opacity: 0; } } | |
| /* 10 + 11 β Slide in / slide out */ | |
| .hp-enter-slide-left { animation: hp-slide-from-left var(--hp-dur) var(--hp-ease-out) backwards; } | |
| .hp-exit-slide-left { animation: hp-slide-to-left var(--hp-dur-fast) var(--hp-ease-in) forwards; } | |
| .hp-enter-slide-up { animation: hp-slide-from-below var(--hp-dur) var(--hp-ease-out) backwards; } | |
| .hp-exit-slide-down { animation: hp-slide-to-below var(--hp-dur-fast) var(--hp-ease-in) forwards; } | |
| @keyframes hp-slide-from-left { from { opacity: 0; transform: translateX(-16px); } } | |
| @keyframes hp-slide-to-left { to { opacity: 0; transform: translateX(-16px); } } | |
| @keyframes hp-slide-from-below { from { opacity: 0; transform: translateY(16px); } } | |
| @keyframes hp-slide-to-below { to { opacity: 0; transform: translateY(16px); } } | |
| /* 12 + 13 β Accordion expand / collapse (Collapsible, and <details class="hp-accordion">) */ | |
| .hp-collapsible { | |
| display: grid; | |
| grid-template-rows: 0fr; | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: grid-template-rows var(--hp-dur) var(--hp-ease-out), opacity var(--hp-dur) var(--hp-ease-out), visibility 0s linear var(--hp-dur); | |
| } | |
| .hp-collapsible[data-open='true'] { | |
| grid-template-rows: 1fr; | |
| opacity: 1; | |
| visibility: visible; | |
| transition: grid-template-rows var(--hp-dur) var(--hp-ease-out), opacity var(--hp-dur) var(--hp-ease-out), visibility 0s; | |
| } | |
| .hp-collapsible > * { min-height: 0; overflow: hidden; } | |
| details.hp-accordion > summary { cursor: pointer; list-style: none; } | |
| details.hp-accordion > summary::-webkit-details-marker { display: none; } | |
| details.hp-accordion > summary::before { | |
| content: ''; | |
| display: inline-block; | |
| width: 0.45em; | |
| height: 0.45em; | |
| margin-right: 0.5em; | |
| border-right: 1.5px solid currentColor; | |
| border-bottom: 1.5px solid currentColor; | |
| transform: rotate(-45deg); | |
| transition: transform var(--hp-dur) var(--hp-ease-out); | |
| } | |
| details.hp-accordion[open] > summary::before { transform: rotate(45deg); } | |
| @supports selector(::details-content) { | |
| details.hp-accordion { interpolate-size: allow-keywords; } | |
| details.hp-accordion::details-content { | |
| block-size: 0; | |
| overflow-y: clip; | |
| opacity: 0; | |
| transition: block-size var(--hp-dur) var(--hp-ease-out), opacity var(--hp-dur) var(--hp-ease-out), | |
| content-visibility var(--hp-dur) allow-discrete; | |
| } | |
| details.hp-accordion[open]::details-content { block-size: auto; opacity: 1; } | |
| } | |
| /* 14 + 15 β Button hover transition and press feedback, for every button that | |
| does not already declare its own. `scale` composes with any transform a | |
| button already has, and :where() keeps this weaker than any utility class. */ | |
| :where(button, [role='button'], a, summary) { | |
| transition-property: background-color, border-color, color, box-shadow, opacity, scale; | |
| transition-duration: var(--hp-dur-fast); | |
| transition-timing-function: ease-out; | |
| } | |
| :where(button, [role='button']):not(:disabled, [aria-disabled='true'], [data-hp-press='off']):active { scale: 0.97; } | |
| /* 16 β Message reveal: new turns rise into place. */ | |
| .hp-msg-in { animation: hp-msg-in var(--hp-dur-slow) var(--hp-ease-out) backwards; } | |
| .hp-msg-in--user { animation-name: hp-msg-in-user; transform-origin: 100% 100%; } | |
| @keyframes hp-msg-in { from { opacity: 0; transform: translateY(8px); } } | |
| @keyframes hp-msg-in-user { from { opacity: 0; transform: translateY(6px) scale(0.98); } } | |
| /* 17 β Tool status transition: the old label leaves upward as the new one arrives. */ | |
| .hp-status-swap { display: inline-grid; vertical-align: bottom; overflow: hidden; } | |
| .hp-status-swap > * { grid-area: 1 / 1; white-space: nowrap; } | |
| .hp-status-swap__in { animation: hp-status-in var(--hp-dur) var(--hp-ease-out) backwards; } | |
| .hp-status-swap__out { animation: hp-status-out var(--hp-dur) var(--hp-ease-in) forwards; } | |
| @keyframes hp-status-in { from { opacity: 0; transform: translateY(60%); } } | |
| @keyframes hp-status-out { to { opacity: 0; transform: translateY(-60%); } } | |
| /* 18 β Voice orb. --hp-level (0..1) is the live audio level. */ | |
| .hp-orb { | |
| --hp-level: 0; | |
| position: relative; | |
| width: var(--hp-orb-size, 128px); | |
| aspect-ratio: 1; | |
| border-radius: 50%; | |
| isolation: isolate; | |
| background: | |
| radial-gradient(circle at 34% 28%, rgba(255, 255, 255, 0.95) 0%, rgba(232, 236, 248, 0.75) 18%, rgba(160, 172, 205, 0.45) 46%, rgba(70, 78, 102, 0.35) 72%, rgba(30, 32, 40, 0.6) 100%); | |
| box-shadow: | |
| inset 0 -10px 24px rgba(0, 0, 0, 0.35), | |
| 0 0 calc(18px + var(--hp-level) * 46px) rgba(200, 210, 240, calc(0.12 + var(--hp-level) * 0.28)); | |
| transform: scale(calc(1 + var(--hp-level) * 0.14)); | |
| transition: transform 90ms linear, box-shadow 120ms linear; | |
| } | |
| .hp-orb::before, | |
| .hp-orb::after { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| border-radius: 50%; | |
| pointer-events: none; | |
| } | |
| .hp-orb[data-state='idle'] { animation: hp-orb-breathe 4.5s ease-in-out infinite; } | |
| .hp-orb[data-state='thinking']::before { | |
| background: conic-gradient(from 0deg, transparent 0 60%, rgba(255, 255, 255, 0.45) 75%, transparent 90%); | |
| mask: radial-gradient(circle, transparent 58%, #000 60%, #000 70%, transparent 72%); | |
| -webkit-mask: radial-gradient(circle, transparent 58%, #000 60%, #000 70%, transparent 72%); | |
| animation: hp-orb-orbit 1.6s linear infinite; | |
| } | |
| .hp-orb[data-state='speaking']::after { | |
| border: 1.5px solid rgba(220, 228, 248, 0.5); | |
| animation: hp-orb-ring 1.8s var(--hp-ease-out) infinite; | |
| } | |
| .hp-orb[data-state='listening']::after { | |
| border: 1px solid rgba(220, 228, 248, calc(0.15 + var(--hp-level) * 0.6)); | |
| transform: scale(calc(1.08 + var(--hp-level) * 0.2)); | |
| transition: transform 90ms linear; | |
| } | |
| @keyframes hp-orb-breathe { | |
| 0%, 100% { scale: 1; } | |
| 50% { scale: 1.035; } | |
| } | |
| @keyframes hp-orb-orbit { to { transform: rotate(360deg); } } | |
| @keyframes hp-orb-ring { | |
| from { opacity: 0.8; transform: scale(1); } | |
| to { opacity: 0; transform: scale(1.45); } | |
| } | |
| /* 19 β Audio waveform. Each bar's height follows the level; a slow ripple | |
| (on `scale`, which composes with the level transform) keeps it alive. */ | |
| .hp-wave { display: inline-flex; align-items: center; gap: 3px; height: var(--hp-wave-h, 28px); } | |
| .hp-wave > span { | |
| width: 3px; | |
| height: 100%; | |
| border-radius: 999px; | |
| background: currentColor; | |
| transform-origin: 50% 50%; | |
| transition: transform 90ms linear; | |
| } | |
| .hp-wave[data-active='true'] > span { animation: hp-wave-ripple 1.1s ease-in-out infinite; } | |
| .hp-wave[data-active='false'] > span { opacity: 0.45; } | |
| @keyframes hp-wave-ripple { | |
| 0%, 100% { scale: 1 0.82; } | |
| 50% { scale: 1 1; } | |
| } | |
| /* 20 β Modal transition (ModalSheet): rises in; leaves as a fading copy. */ | |
| .hp-sheet-backdrop { animation: hp-fade-in var(--hp-dur) var(--hp-ease-out) backwards; } | |
| .hp-sheet { animation: hp-sheet-in var(--hp-dur) var(--hp-ease-out) backwards; } | |
| .hp-sheet-backdrop[data-leaving] { animation: hp-fade-out 180ms var(--hp-ease-in) forwards; pointer-events: none; } | |
| .hp-sheet-backdrop[data-leaving] .hp-sheet { animation: hp-sheet-out 180ms var(--hp-ease-in) forwards; } | |
| @keyframes hp-sheet-in { from { opacity: 0; transform: translateY(10px) scale(0.985); } } | |
| @keyframes hp-sheet-out { to { opacity: 0; transform: translateY(6px) scale(0.985); } } | |
| @media (max-width: 639.98px) { | |
| .hp-sheet { animation-name: hp-sheet-up; } | |
| .hp-sheet-backdrop[data-leaving] .hp-sheet { animation-name: hp-sheet-down; } | |
| @keyframes hp-sheet-up { from { transform: translateY(24px); opacity: 0; } } | |
| @keyframes hp-sheet-down { to { transform: translateY(24px); opacity: 0; } } | |
| } | |
| /* 21 β Sidebar transition (mobile drawer and its backdrop) */ | |
| .hp-drawer { transition: transform var(--hp-dur-slow) var(--hp-ease-drawer), visibility 0s linear var(--hp-dur-slow); } | |
| .hp-drawer[data-open='true'] { transition: transform var(--hp-dur-slow) var(--hp-ease-drawer), visibility 0s; } | |
| .hp-drawer[data-open='false'] { visibility: hidden; } | |
| .hp-drawer-backdrop { | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity var(--hp-dur-slow) var(--hp-ease-drawer); | |
| } | |
| .hp-drawer-backdrop[data-open='true'] { opacity: 1; pointer-events: auto; } | |
| /* 22 β Progress indicator */ | |
| .hp-progress { | |
| position: relative; | |
| display: block; | |
| width: 100%; | |
| height: 3px; | |
| overflow: hidden; | |
| border-radius: 999px; | |
| background: rgba(255, 255, 255, 0.1); | |
| } | |
| .hp-progress > span { | |
| display: block; | |
| height: 100%; | |
| border-radius: inherit; | |
| background: var(--hp-color-text, #f5f5f5); | |
| transition: width var(--hp-dur-slow) var(--hp-ease-out); | |
| } | |
| .hp-progress[data-indeterminate='true'] > span { | |
| width: 38%; | |
| animation: hp-progress-sweep 1.3s var(--hp-ease-out) infinite; | |
| } | |
| @keyframes hp-progress-sweep { | |
| from { transform: translateX(-100%); } | |
| to { transform: translateX(265%); } | |
| } | |
| /* ββ Levels βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| minimal: no sweeps, slides, orbits or press scaling β fades and pulses only. */ | |
| html[data-hp-motion='minimal'] .hp-shimmer-text { | |
| background: none; | |
| -webkit-text-fill-color: currentColor; | |
| animation: hp-opacity-pulse 1.8s ease-in-out infinite; | |
| } | |
| html[data-hp-motion='minimal'] :is(.hp-enter-slide-left, .hp-enter-slide-up, .hp-msg-in, .hp-sheet) { animation-name: hp-fade-in; } | |
| html[data-hp-motion='minimal'] :is(.hp-exit-slide-left, .hp-exit-slide-down) { animation-name: hp-fade-out; } | |
| html[data-hp-motion='minimal'] .hp-sheet-backdrop[data-leaving] .hp-sheet { animation-name: hp-fade-out; } | |
| html[data-hp-motion='minimal'] :is(.hp-skeleton::after, .hp-orb::before, .hp-orb::after) { animation: none; } | |
| html[data-hp-motion='minimal'] :where(button, [role='button']):active { scale: none; } | |
| /* off (and the OS reduce-motion setting): nothing moves; working labels stay readable. */ | |
| html[data-hp-motion='off'] *, | |
| html[data-hp-motion='off'] *::before, | |
| html[data-hp-motion='off'] *::after { | |
| animation-duration: 0.01ms ; | |
| animation-iteration-count: 1 ; | |
| transition-duration: 0.01ms ; | |
| scroll-behavior: auto ; | |
| } | |
| html[data-hp-motion='off'] :where(button, [role='button']):active { scale: none; } | |
| html[data-hp-motion='off'] .hp-shimmer-text { background: none; -webkit-text-fill-color: currentColor; } | |
| @media (prefers-reduced-motion: reduce) { | |
| .hp-shimmer-text { background: none; -webkit-text-fill-color: currentColor; } | |
| :where(button, [role='button']):active { scale: none; } | |
| } | |
| /* The orb at rest when voice is off: present, but clearly asleep. */ | |
| .hp-orb[data-state='off'] { opacity: 0.55; filter: saturate(0.5); } | |