chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
/**
|
||||
* ThinkingLoader gooey animation
|
||||
*
|
||||
* Metaball technique on the SVG alpha channel: ink shapes are blurred with
|
||||
* feGaussianBlur, then feColorMatrix crushes the soft alpha back to hard
|
||||
* edges. Unlike the CSS blur+contrast trick this needs no solid backdrop and
|
||||
* no blend mode, so the loader composites cleanly on any background.
|
||||
*
|
||||
* Every variant is authored in the shared 100x100 viewBox. All stages stack
|
||||
* inside one filtered group; because the alpha crush happens after the
|
||||
* stages composite, crossfading two stages reads as one pattern melting
|
||||
* into the next, which is what drives the cycling morph.
|
||||
*
|
||||
* Ink color rides on currentColor: near-black on light surfaces, off-white
|
||||
* in dark mode.
|
||||
*/
|
||||
|
||||
.frame {
|
||||
/* currentColor paints the squeeze ring's stroked arcs (the only non-filled
|
||||
geometry). It tracks the loader polarity — dark on light, light on dark —
|
||||
matching the gradient stops below. */
|
||||
color: #2c2c2c;
|
||||
flex: none;
|
||||
/* Loader fill is a radial gradient (center → edge) plus a soft white inner
|
||||
glow, per the Figma loader spec. Stops and glow opacity are theme vars,
|
||||
inherited by the gradient stops and feFlood inside the SVG, so one set of
|
||||
defs serves both themes. Light mode = the DARK-grey loader (reads on white):
|
||||
#2C2C2C → #5F5F5F, glow white 0.6. */
|
||||
--tl-grad-inner: #2c2c2c;
|
||||
--tl-grad-outer: #5f5f5f;
|
||||
--tl-glow: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
/* Gradient stops + glow flood read the theme vars via CSS (a raw `var()` in an
|
||||
SVG presentation attribute would not resolve — it must come through CSS).
|
||||
`stop-color`/`flood-color` are transitionable even when fed by a var, so a
|
||||
caller that re-points the ink vars (e.g. settling the loader's material into
|
||||
a surface it hands off to) gets a smooth color tween, not a hard flip. */
|
||||
.gradInner {
|
||||
stop-color: var(--tl-grad-inner);
|
||||
transition: stop-color 450ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.gradOuter {
|
||||
stop-color: var(--tl-grad-outer);
|
||||
transition: stop-color 450ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.glow {
|
||||
flood-color: var(--tl-glow);
|
||||
transition: flood-color 450ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
|
||||
/* Wall-clock phase lock: the component sets --tl-sync to a shared negative
|
||||
delay (now mod the common animation period), so every instance's keyframes
|
||||
line up no matter when it mounted. The doubled class out-specifies the
|
||||
shape classes' animation shorthand (implicit 0s delay) regardless of
|
||||
source order. */
|
||||
.frame.frame * {
|
||||
animation-delay: var(--tl-sync, 0s);
|
||||
}
|
||||
|
||||
:global(.dark) .frame {
|
||||
color: #d6d6d6;
|
||||
/* Dark mode = the LIGHT-grey loader (reads on dark): #A7A7A7 → #D6D6D6,
|
||||
glow white 0.9. */
|
||||
--tl-grad-inner: #a7a7a7;
|
||||
--tl-grad-outer: #d6d6d6;
|
||||
--tl-glow: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
/* An explicit `.light` wrapper (e.g. the always-light landing) re-asserts the
|
||||
dark-grey loader even when an outer `.dark` theme is on the document — the
|
||||
nearer theme wins. Ordered after the `.dark` rule so on equal specificity
|
||||
(both ancestors present) this takes precedence. */
|
||||
:global(.light) .frame {
|
||||
color: #2c2c2c;
|
||||
--tl-grad-inner: #2c2c2c;
|
||||
--tl-grad-outer: #5f5f5f;
|
||||
--tl-glow: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.labelRow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
/* gap + label size scale with the loader (vars set per-instance); defaults
|
||||
match the chat's size-20 loader (8px gap, 14px text). */
|
||||
gap: var(--tl-label-gap, 8px);
|
||||
/* The status phrase must never break onto a second line — the glyph + phrase
|
||||
stay on one row regardless of phrase length or how narrow the available
|
||||
width is (e.g. the hero loader docked near the card's right edge).
|
||||
white-space inherits, so this one declaration reaches both the shimmer
|
||||
(.label) and static (.labelStatic) text spans. */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* The sweeping-band treatment itself (gradient, timing, dark mode, reduced
|
||||
motion) is owned by the shared shimmer-text module; this class only adds the
|
||||
loader-scaled font sizing. Canonical normal weight per emcn rules: body text
|
||||
is 400, never medium. */
|
||||
.label {
|
||||
composes: shimmer from "./shimmer-text.module.css";
|
||||
font-size: var(--tl-label-size, 14px);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* Static label (shimmer off): the phrase in solid body ink, no gradient sweep. */
|
||||
.labelStatic {
|
||||
font-size: var(--tl-label-size, 14px);
|
||||
font-weight: 400;
|
||||
color: var(--text-body);
|
||||
}
|
||||
|
||||
/* Phrase crossfade — the incoming phrase rises and fades in while the outgoing
|
||||
one rises and fades out (stacked over it), so phrases rotate smoothly. */
|
||||
.labelStack {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
.labelLayer {
|
||||
display: inline-flex;
|
||||
}
|
||||
/* Incoming phrase wipes in left → right through an animated gradient mask
|
||||
(soft trailing edge), so it's revealed horizontally rather than fading up. */
|
||||
.labelIn {
|
||||
-webkit-mask-image: linear-gradient(90deg, #000 0, #000 70%, transparent 100%);
|
||||
mask-image: linear-gradient(90deg, #000 0, #000 70%, transparent 100%);
|
||||
-webkit-mask-size: 300% 100%;
|
||||
mask-size: 300% 100%;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
animation: label-reveal 460ms ease both;
|
||||
}
|
||||
.labelOut {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
animation: label-out 300ms ease both;
|
||||
}
|
||||
@keyframes label-reveal {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-mask-position: 100% 0;
|
||||
mask-position: 100% 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-mask-position: 0% 0;
|
||||
mask-position: 0% 0;
|
||||
}
|
||||
}
|
||||
@keyframes label-out {
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.stage {
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.stageActive {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* metaballs — two circles drift apart and melt back together */
|
||||
.metaballsA {
|
||||
animation: metaballs-a 1s infinite alternate;
|
||||
}
|
||||
.metaballsB {
|
||||
animation: metaballs-b 1s infinite alternate;
|
||||
}
|
||||
@keyframes metaballs-a {
|
||||
90%,
|
||||
100% {
|
||||
transform: translateX(28px);
|
||||
}
|
||||
}
|
||||
@keyframes metaballs-b {
|
||||
90%,
|
||||
100% {
|
||||
transform: translateX(-28px);
|
||||
}
|
||||
}
|
||||
|
||||
/* relay (Return) — always left → right: the ball emerges from the left post,
|
||||
travels across, and dissolves into the right post (the goo morphs it into
|
||||
that shape). It fades out at the right and in at the left, so the reset back
|
||||
to the start happens while invisible — no bounce, no hard snap. */
|
||||
.relayBall {
|
||||
animation: relay 1.3s linear infinite;
|
||||
}
|
||||
@keyframes relay {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
opacity: 1;
|
||||
}
|
||||
78% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(58px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* corners — four dots rotate around a center square */
|
||||
.cornersA {
|
||||
animation: corners-a 0.8s infinite;
|
||||
}
|
||||
.cornersB {
|
||||
animation: corners-b 0.8s infinite;
|
||||
}
|
||||
.cornersC {
|
||||
animation: corners-c 0.8s infinite;
|
||||
}
|
||||
.cornersD {
|
||||
animation: corners-d 0.8s infinite;
|
||||
}
|
||||
@keyframes corners-a {
|
||||
100% {
|
||||
transform: translate(46px, 0);
|
||||
}
|
||||
}
|
||||
@keyframes corners-b {
|
||||
100% {
|
||||
transform: translate(0, 46px);
|
||||
}
|
||||
}
|
||||
@keyframes corners-c {
|
||||
100% {
|
||||
transform: translate(-46px, 0);
|
||||
}
|
||||
}
|
||||
@keyframes corners-d {
|
||||
100% {
|
||||
transform: translate(0, -46px);
|
||||
}
|
||||
}
|
||||
|
||||
/* burst — four dots repeatedly fling out of a center cross */
|
||||
.burstUp {
|
||||
animation: burst-up 0.8s infinite;
|
||||
}
|
||||
.burstDown {
|
||||
animation: burst-down 0.8s infinite;
|
||||
}
|
||||
.burstLeft {
|
||||
animation: burst-left 0.8s infinite;
|
||||
}
|
||||
.burstRight {
|
||||
animation: burst-right 0.8s infinite;
|
||||
}
|
||||
@keyframes burst-up {
|
||||
100% {
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
}
|
||||
@keyframes burst-down {
|
||||
100% {
|
||||
transform: translateY(50px);
|
||||
}
|
||||
}
|
||||
@keyframes burst-left {
|
||||
100% {
|
||||
transform: translateX(-50px);
|
||||
}
|
||||
}
|
||||
@keyframes burst-right {
|
||||
100% {
|
||||
transform: translateX(50px);
|
||||
}
|
||||
}
|
||||
|
||||
/* compass — one dot tours the four cardinal points */
|
||||
.compassMover {
|
||||
animation: compass 2s infinite;
|
||||
}
|
||||
@keyframes compass {
|
||||
0% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
25% {
|
||||
transform: translate(27px, 27px);
|
||||
}
|
||||
50% {
|
||||
transform: translate(0, 54px);
|
||||
}
|
||||
75% {
|
||||
transform: translate(-27px, 27px);
|
||||
}
|
||||
100% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* squeeze — two bars pinch together inside the ring arcs */
|
||||
.squeezeBarL {
|
||||
animation: squeeze-l 0.6s infinite alternate;
|
||||
}
|
||||
.squeezeBarR {
|
||||
animation: squeeze-r 0.6s infinite alternate;
|
||||
}
|
||||
@keyframes squeeze-l {
|
||||
0%,
|
||||
30% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(10px);
|
||||
}
|
||||
}
|
||||
@keyframes squeeze-r {
|
||||
0%,
|
||||
30% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
/* thinking — three small blobs drift out from the core and back on offset
|
||||
periods (1.3/1.6/1.9s), so the merged cloud churns and never repeats:
|
||||
the Core weighing options. */
|
||||
.thinkA {
|
||||
animation: think-a 1.6s infinite alternate;
|
||||
}
|
||||
.thinkB {
|
||||
animation: think-b 1.9s infinite alternate;
|
||||
}
|
||||
.thinkC {
|
||||
animation: think-c 1.3s infinite alternate;
|
||||
}
|
||||
@keyframes think-a {
|
||||
100% {
|
||||
transform: translate(-20px, -14px);
|
||||
}
|
||||
}
|
||||
@keyframes think-b {
|
||||
100% {
|
||||
transform: translate(22px, -10px);
|
||||
}
|
||||
}
|
||||
@keyframes think-c {
|
||||
100% {
|
||||
transform: translate(2px, 22px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.frame *,
|
||||
.labelIn {
|
||||
animation: none;
|
||||
}
|
||||
.gradInner,
|
||||
.gradOuter,
|
||||
.glow {
|
||||
transition: none;
|
||||
}
|
||||
/* No crossfade — the incoming phrase just replaces the outgoing one. */
|
||||
.labelOut {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user