feat: text & slider animations

This commit is contained in:
2026-06-02 12:18:26 +03:00
parent 4919bc26e5
commit 49d6ac8a4e
4 changed files with 392 additions and 13 deletions
+105
View File
@@ -224,6 +224,94 @@ textarea.modern-sk-field {
padding-left: 34px;
}
/* ---------- TYPING ANIMATION (osu!-lazer style char in/out) ---------- */
/* The real field renders transparent over a mirrored per-letter overlay. */
.modern-sk-field-wrap {
position: relative;
width: 100%;
}
.modern-sk-search .modern-sk-field-wrap {
width: 100%;
}
.modern-sk-field--animated {
color: transparent;
caret-color: var(--fg-1);
}
.modern-sk-field--animated::placeholder {
color: var(--fg-3);
}
.modern-sk-field-overlay {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
/* transparent border mirrors the field's 1px border so content aligns */
border: 1px solid transparent;
padding: var(--field-pad-y) var(--field-pad-x);
font-family: var(--font-sans);
font-size: 14px;
line-height: normal;
color: var(--fg-1);
white-space: pre;
text-align: left;
will-change: transform;
}
.modern-sk-field-overlay--multiline {
white-space: pre-wrap;
overflow-wrap: break-word;
line-height: 1.5;
}
.modern-sk-search .modern-sk-field-overlay {
padding-left: 34px;
}
.modern-sk-field-char {
/* inline (not inline-block) so the overlay wraps exactly like the field;
inherit white-space so multiline (pre-wrap) wraps while input (pre) won't.
'pre' on the span itself would suppress wrapping at its boundaries. */
position: relative;
white-space: inherit;
animation: modern-sk-char-in var(--dur-base) var(--ease-snap) both;
}
.modern-sk-field-char--leaving {
display: inline-block;
position: absolute;
white-space: pre;
animation: modern-sk-char-out var(--dur-base) var(--ease-out) forwards;
}
/* inline elements ignore transform, so the rise uses top instead */
@keyframes modern-sk-char-in {
from {
opacity: 0;
top: -0.32em;
}
to {
opacity: 1;
top: 0;
}
}
@keyframes modern-sk-char-out {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(0.7em) scale(0.9);
}
}
@media (prefers-reduced-motion: reduce) {
.modern-sk-field-char {
animation: none;
}
.modern-sk-field-char--leaving {
animation: modern-sk-char-out 1ms linear forwards;
}
}
/* ---------- SELECT (Radix Select, styled as the glossy key) ---------- */
.modern-sk-select {
font-family: var(--font-sans);
@@ -538,6 +626,11 @@ textarea.modern-sk-field {
border-radius: 3px;
background: linear-gradient(90deg, var(--lime-deep), var(--lime));
box-shadow: 0 0 10px rgba(190, 242, 100, 0.4);
/* Radix positions the range via left/right offsets (not width); ease those
so the fill glides between discrete steps while dragging. */
transition:
left 0.12s ease-out,
right 0.12s ease-out;
}
.modern-sk-slider__thumb {
display: block;
@@ -551,6 +644,18 @@ textarea.modern-sk-field {
cursor: pointer;
outline: none;
}
/* Radix sets the step position (left) on a WRAPPER span around the thumb, not on
the thumb element itself — so the transition must live on that wrapper. The
wrapper is the slider's direct child span that isn't the track. */
.modern-sk-slider > span:not(.modern-sk-slider__track) {
transition: left 0.12s ease-out;
}
@media (prefers-reduced-motion: reduce) {
.modern-sk-slider > span:not(.modern-sk-slider__track),
.modern-sk-slider__range {
transition: none;
}
}
.modern-sk-slider--knob-square .modern-sk-slider__thumb {
--ms-thumb-w: 14px;
}