boldkit
stylestylessource: ok · refreshed
Styles
BoldKit neubrutalism CSS variables and utilities
npx shadcn add "https://boldkit.dev/r/styles.json"free
Visual style
brutalist
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Files2 · source cached 10/8/2026, 4:09:21 AM
registry/default/styles/globals.css
@import 'tailwindcss';
@import 'tw-animate-css';
@import './motion.css';
@custom-variant dark (&:is(.dark *));
/* BoldKit Theme Definition for Tailwind v4 */
@theme {
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
--color-success: hsl(var(--success));
--color-success-foreground: hsl(var(--success-foreground));
--color-warning: hsl(var(--warning));
--color-warning-foreground: hsl(var(--warning-foreground));
--color-info: hsl(var(--info));
--color-info-foreground: hsl(var(--info-foreground));
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
--color-chart-1: hsl(var(--chart-1));
--color-chart-2: hsl(var(--chart-2));
--color-chart-3: hsl(var(--chart-3));
--color-chart-4: hsl(var(--chart-4));
--color-chart-5: hsl(var(--chart-5));
/* Neon Colors */
--color-neon-pink: hsl(var(--neon-pink));
--color-neon-green: hsl(var(--neon-green));
--color-neon-blue: hsl(var(--neon-blue));
--color-neon-orange: hsl(var(--neon-orange));
--color-neon-purple: hsl(var(--neon-purple));
/* Clash Colors */
--color-clash-1: hsl(var(--clash-1));
--color-clash-2: hsl(var(--clash-2));
--color-clash-3: hsl(var(--clash-3));
--color-clash-4: hsl(var(--clash-4));
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
/* BoldKit Motion System — L1 Tokens (framework-agnostic, --bk-* namespace).
The snap duration/easing back the .bk-interactive transition used by the
core interactive components (Button, Card, Input, Toggle, Switch). */
--bk-ease-snap: cubic-bezier(0.22, 1, 0.3, 1.12);
--bk-dur-snap: 150ms;
--bk-ease-step: steps(4, end);
--bk-ease-step-2: steps(2, end);
--bk-ease-stamp: cubic-bezier(0.7, 0, 0.84, 0);
--bk-ease-rubber: cubic-bezier(0.34, 1.56, 0.64, 1);
--bk-ease-linear: linear;
--bk-dur-instant: 80ms;
--bk-dur-quick: 240ms;
--bk-dur-base: 400ms;
--bk-dur-slow: 700ms;
--bk-shadow-offset: 4px;
--bk-shadow-offset-press: 0px;
--bk-translate-press: 2px;
--bk-reveal-offset: 16px;
/* Easing tokens — custom curves per the animations.dev easing blueprint.
Built-in CSS easings are too weak; these override Tailwind's defaults so
every `ease-out` / `ease-in-out` / `transition-*` utility gets the
stronger acceleration.
- ease-out: enters + user-initiated interactions (dropdowns, modals)
- ease-in-out: elements already on screen moving/morphing
- ease-in: avoid (sluggish); kept only for exit symmetry
- linear: marquees / progress only */
--ease-in: cubic-bezier(0.55, 0.055, 0.675, 0.19); /* in-cubic */
--ease-out: cubic-bezier(0.165, 0.84, 0.44, 1); /* out-quart */
--ease-in-out: cubic-bezier(0.645, 0.045, 0.355, 1); /* in-out-cubic */
/* Named scale, weakest → strongest acceleration */
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
--ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
--ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
--ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
--ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
--ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
--ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
/* Bare `transition-*` utilities default to the responsive ease-out */
--default-transition-timing-function: var(--ease-out);
}
/* BoldKit - Neubrutalism Design System */
:root {
/* Base Colors */
--background: 60 9% 98%;
--foreground: 240 10% 10%;
/* Card */
--card: 0 0% 100%;
--card-foreground: 240 10% 10%;
/* Popover */
--popover: 0 0% 100%;
--popover-foreground: 240 10% 10%;
/* Primary - Coral Red */
--primary: 0 84% 71%;
--primary-foreground: 240 10% 10%;
/* Secondary - Teal */
--secondary: 174 62% 56%;
--secondary-foreground: 240 10% 10%;
/* Accent - Yellow */
--accent: 49 100% 71%;
--accent-foreground: 240 10% 10%;
/* Muted */
--muted: 60 5% 90%;
--muted-foreground: 240 5% 38%;
/* Destructive */
--destructive: 0 84% 47%;
--destructive-foreground: 0 0% 100%;
/* Success */
--success: 152 69% 69%;
--success-foreground: 240 10% 10%;
/* Warning */
--warning: 49 100% 60%;
--warning-foreground: 240 10% 10%;
/* Info */
--info: 212 100% 73%;
--info-foreground: 240 10% 10%;
/* Border & Input */
--border: 240 10% 10%;
--input: 240 10% 10%;
--ring: 240 10% 10%;
/* Radius - Minimal for neubrutalism */
--radius: 0rem;
/* BoldKit specific */
--shadow-color: 240 10% 10%;
--shadow-offset: 4px;
--border-width: 3px;
/* Chart colors */
--chart-1: 0 84% 71%;
--chart-2: 174 62% 56%;
--chart-3: 49 100% 71%;
--chart-4: 271 76% 53%;
--chart-5: 326 78% 60%;
/* Neon Colors - High saturation, vibrant */
--neon-pink: 330 100% 65%;
--neon-green: 120 100% 50%;
--neon-blue: 195 100% 50%;
--neon-orange: 25 100% 55%;
--neon-purple: 280 100% 60%;
/* Clash Colors - Unconventional pairings */
--clash-1: 15 85% 60%; /* Burnt orange */
--clash-2: 280 70% 50%; /* Deep purple */
--clash-3: 165 80% 45%; /* Cyan-teal */
--clash-4: 45 95% 55%; /* Mustard */
}
.dark {
--background: 240 10% 10%;
--foreground: 60 9% 98%;
--card: 240 10% 14%;
--card-foreground: 60 9% 98%;
--popover: 240 10% 14%;
--popover-foreground: 60 9% 98%;
--primary: 0 84% 71%;
--primary-foreground: 240 10% 10%;
--secondary: 174 62% 56%;
--secondary-foreground: 240 10% 10%;
--accent: 49 100% 71%;
--accent-foreground: 240 10% 10%;
--muted: 240 10% 20%;
--muted-foreground: 60 5% 65%;
--destructive: 0 84% 47%;
--destructive-foreground: 0 0% 100%;
--success: 152 69% 69%;
--success-foreground: 240 10% 10%;
--warning: 49 100% 60%;
--warning-foreground: 240 10% 10%;
--info: 212 100% 73%;
--info-foreground: 240 10% 10%;
--border: 60 9% 98%;
--input: 60 9% 98%;
--ring: 60 9% 98%;
--shadow-color: 0 0% 0%;
/* Neon Colors - Slightly adjusted for dark mode */
--neon-pink: 330 100% 70%;
--neon-green: 120 100% 55%;
--neon-blue: 195 100% 55%;
--neon-orange: 25 100% 60%;
--neon-purple: 280 100% 65%;
/* Clash Colors */
--clash-1: 15 85% 65%;
--clash-2: 280 70% 55%;
--clash-3: 165 80% 50%;
--clash-4: 45 95% 60%;
}
/* Base styles */
* {
@apply border-border;
}
body {
@apply bg-background text-foreground antialiased;
font-family: 'Outfit Variable', 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
/* BoldKit Motion — shared interactive timing. Routes the core components'
box-shadow+translate press onto the snap curve (buttery settle, hard look). */
.bk-interactive {
transition:
transform var(--bk-dur-snap) var(--bk-ease-snap),
box-shadow var(--bk-dur-snap) var(--bk-ease-snap),
background-color var(--bk-dur-snap) var(--ease-out),
border-color var(--bk-dur-snap) var(--ease-out),
color var(--bk-dur-snap) var(--ease-out),
opacity var(--bk-dur-snap) var(--ease-out);
}
/* Universal reduced-motion guard — neutralizes movement for users who request it.
Covers hand-rolled `transition-all` hover effects that don't use the .bk-* utilities.
Keeps end states; only removes the timing. (a11y — improve-animations #1) */
@media (prefers-reduced-motion: reduce) {
*,
::before,
::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
@media (prefers-reduced-motion: reduce) {
.bk-interactive { transition-duration: 0.01ms !important; }
}
/* BoldKit Shadow Utility */
.bk-shadow {
box-shadow: var(--shadow-offset) var(--shadow-offset) 0px hsl(var(--shadow-color));
}
.bk-shadow-sm {
box-shadow: 2px 2px 0px hsl(var(--shadow-color));
}
.bk-shadow-md {
box-shadow: 6px 6px 0px hsl(var(--shadow-color));
}
.bk-shadow-lg {
box-shadow: 8px 8px 0px hsl(var(--shadow-color));
}
.bk-shadow-xl {
box-shadow: 12px 12px 0px hsl(var(--shadow-color));
}
.bk-shadow-2xl {
box-shadow: 16px 16px 0px hsl(var(--shadow-color));
}
/* Colored Shadow Variants */
.bk-shadow-primary {
box-shadow: 4px 4px 0px hsl(var(--primary));
}
.bk-shadow-secondary {
box-shadow: 4px 4px 0px hsl(var(--secondary));
}
.bk-shadow-accent {
box-shadow: 4px 4px 0px hsl(var(--accent));
}
.bk-shadow-destructive {
box-shadow: 4px 4px 0px hsl(var(--destructive));
}
.bk-shadow-neon-pink {
box-shadow: 4px 4px 0px hsl(var(--neon-pink));
}
.bk-shadow-neon-green {
box-shadow: 4px 4px 0px hsl(var(--neon-green));
}
.bk-shadow-neon-blue {
box-shadow: 4px 4px 0px hsl(var(--neon-blue));
}
/* Double Shadow Effect - Layered depth */
.bk-shadow-double {
box-shadow:
4px 4px 0px hsl(var(--primary)),
8px 8px 0px hsl(var(--shadow-color));
}
.bk-shadow-double-accent {
box-shadow:
4px 4px 0px hsl(var(--accent)),
8px 8px 0px hsl(var(--shadow-color));
}
.bk-shadow-double-lg {
box-shadow:
6px 6px 0px hsl(var(--primary)),
12px 12px 0px hsl(var(--shadow-color));
}
/* Triple Shadow Effect - Maximum depth */
.bk-shadow-triple {
box-shadow:
3px 3px 0px hsl(var(--primary)),
6px 6px 0px hsl(var(--secondary)),
9px 9px 0px hsl(var(--shadow-color));
}
/* Hover transform effect */
.bk-hover:hover {
transform: translate(-2px, -2px);
}
.bk-hover:active {
transform: translate(2px, 2px);
box-shadow: 2px 2px 0px hsl(var(--shadow-color));
}
/* Enhanced hover with scale */
.bk-hover-scale:hover {
transform: translate(-2px, -2px) scale(1.02);
}
.bk-hover-scale:active {
transform: translate(2px, 2px) scale(0.98);
box-shadow: 2px 2px 0px hsl(var(--shadow-color));
}
/* Border Variations */
.border-4 {
border-width: 4px;
}
.border-5 {
border-width: 5px;
}
.border-6 {
border-width: 6px;
}
/* Double Border Effect - Sticker/Stamp style */
.bk-double-border {
outline: 3px solid hsl(var(--foreground));
outline-offset: 3px;
}
.bk-double-border-primary {
outline: 3px solid hsl(var(--primary));
outline-offset: 3px;
}
.bk-double-border-accent {
outline: 3px solid hsl(var(--accent));
outline-offset: 3px;
}
/* Dashed border for rough/hand-drawn feel */
.bk-dashed-border {
border-style: dashed;
border-width: 3px;
}
/* Sticker Effect - Rotated with double border */
.bk-sticker {
position: relative;
display: inline-block;
padding: 0.5rem 1rem;
border: 3px solid hsl(var(--foreground));
background: hsl(var(--accent));
box-shadow: 4px 4px 0px hsl(var(--shadow-color));
transform: rotate(-2deg);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.bk-sticker::before {
content: '';
position: absolute;
inset: -6px;
border: 2px dashed hsl(var(--foreground) / 0.5);
}
.bk-sticker-tilted {
transform: rotate(-6deg);
}
.bk-sticker-tilted-right {
transform: rotate(6deg);
}
/* Stamp Effect - Circular with rough edges */
.bk-stamp {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 1rem;
border: 4px solid hsl(var(--foreground));
border-radius: 50%;
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
font-weight: 900;
text-transform: uppercase;
letter-spacing: 0.1em;
box-shadow: 4px 4px 0px hsl(var(--shadow-color));
transform: rotate(-12deg);
}
/* Tape Effect - Decorative tape strips */
.bk-tape {
position: relative;
}
.bk-tape::before {
content: '';
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%) rotate(-2deg);
width: 60px;
height: 20px;
background: hsl(var(--accent) / 0.8);
border: 2px solid hsl(var(--foreground));
}
/* Selection */
::selection {
@apply bg-accent text-accent-foreground;
}
/* ==========================================
TYPOGRAPHY ENHANCEMENTS
========================================== */
/* Extra-wide letter spacing for emphasis */
.tracking-extra-wide {
letter-spacing: 0.2em;
}
.tracking-ultra-wide {
letter-spacing: 0.3em;
}
/* Text overflow effect - intentional overflow */
.bk-text-overflow {
overflow: visible;
white-space: nowrap;
}
.bk-text-overflow-clip {
position: relative;
}
.bk-text-overflow-clip::after {
content: '';
position: absolute;
right: -20px;
top: 0;
bottom: 0;
width: 40px;
background: linear-gradient(to right, transparent, hsl(var(--background)));
}
/* Dramatic text sizes */
.text-display {
font-size: 4rem;
line-height: 1;
font-weight: 900;
letter-spacing: -0.02em;
}
.text-display-lg {
font-size: 6rem;
line-height: 0.9;
font-weight: 900;
letter-spacing: -0.03em;
}
.text-display-xl {
font-size: 8rem;
line-height: 0.85;
font-weight: 900;
letter-spacing: -0.04em;
}
/* Outlined text */
.bk-text-outline {
-webkit-text-stroke: 2px hsl(var(--foreground));
color: transparent;
}
.bk-text-outline-thick {
-webkit-text-stroke: 3px hsl(var(--foreground));
color: transparent;
}
/* Text with shadow */
.bk-text-shadow {
text-shadow: 3px 3px 0px hsl(var(--shadow-color));
}
.bk-text-shadow-lg {
text-shadow: 5px 5px 0px hsl(var(--shadow-color));
}
.bk-text-shadow-colored {
text-shadow: 3px 3px 0px hsl(var(--primary));
}
/* ==========================================
TEXTURE & PATTERN UTILITIES
========================================== */
/* Noise/Grain texture overlay */
.bk-noise {
position: relative;
}
.bk-noise::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
opacity: 0.08;
pointer-events: none;
mix-blend-mode: overlay;
}
.bk-noise-heavy::before {
opacity: 0.15;
}
.bk-noise-light::before {
opacity: 0.04;
}
/* Halftone dot pattern */
.bk-halftone {
position: relative;
}
.bk-halftone::before {
content: '';
position: absolute;
inset: 0;
background-image: radial-gradient(circle, hsl(var(--foreground) / 0.15) 1px, transparent 1px);
background-size: 8px 8px;
pointer-events: none;
}
.bk-halftone-dense::before {
background-size: 4px 4px;
}
.bk-halftone-sparse::before {
background-size: 12px 12px;
}
/* Grid pattern variations */
.grid-pattern-sm {
background-image:
linear-gradient(hsl(var(--border) / 0.3) 1px, transparent 1px),
linear-gradient(90deg, hsl(var(--border) / 0.3) 1px, transparent 1px);
background-size: 20px 20px;
}
.grid-pattern-lg {
background-image:
linear-gradient(hsl(var(--border) / 0.3) 1px, transparent 1px),
linear-gradient(90deg, hsl(var(--border) / 0.3) 1px, transparent 1px);
background-size: 60px 60px;
}
.grid-pattern-bold {
background-image:
linear-gradient(hsl(var(--border) / 0.5) 2px, transparent 2px),
linear-gradient(90deg, hsl(var(--border) / 0.5) 2px, transparent 2px);
background-size: 40px 40px;
}
/* Diagonal lines pattern */
.bk-diagonal-lines {
background-image: repeating-linear-gradient(
45deg,
transparent,
transparent 10px,
hsl(var(--border) / 0.2) 10px,
hsl(var(--border) / 0.2) 11px
);
}
/* Checkerboard pattern */
.bk-checkerboard {
background-image:
linear-gradient(45deg, hsl(var(--muted)) 25%, transparent 25%),
linear-gradient(-45deg, hsl(var(--muted)) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, hsl(var(--muted)) 75%),
linear-gradient(-45deg, transparent 75%, hsl(var(--muted)) 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
}
/* Focus visible */
:focus-visible {
@apply outline-none ring-2 ring-ring ring-offset-2 ring-offset-background;
}
/* Remove focus ring from command input */
[cmdk-input]:focus,
[cmdk-input]:focus-visible {
outline: none !important;
box-shadow: none !important;
ring: none !important;
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track {
@apply bg-muted;
}
::-webkit-scrollbar-thumb {
@apply bg-foreground border-3 border-muted;
}
/* Grid pattern for hero sections */
.grid-pattern {
background-image:
linear-gradient(hsl(var(--border) / 0.3) 1px, transparent 1px),
linear-gradient(90deg, hsl(var(--border) / 0.3) 1px, transparent 1px);
background-size: 40px 40px;
}
/* Animation Keyframes */
@keyframes accordion-down {
from {
height: 0;
opacity: 0;
}
to {
height: var(--radix-accordion-content-height);
opacity: 1;
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
opacity: 1;
}
to {
height: 0;
opacity: 0;
}
}
@keyframes collapsible-down {
from {
height: 0;
opacity: 0;
}
to {
height: var(--radix-collapsible-content-height);
opacity: 1;
}
}
@keyframes collapsible-up {
from {
height: var(--radix-collapsible-content-height);
opacity: 1;
}
to {
height: 0;
opacity: 0;
}
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-4px); }
75% { transform: translateX(4px); }
}
@keyframes bounce-in {
0% { transform: scale(0.95); opacity: 0; }
50% { transform: scale(1.02); }
100% { transform: scale(1); opacity: 1; }
}
@keyframes pulse-shadow {
0%, 100% { box-shadow: 4px 4px 0px hsl(var(--shadow-color)); }
50% { box-shadow: 6px 6px 0px hsl(var(--shadow-color)); }
}
/* Wiggle animation - playful error state */
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(-3deg); }
50% { transform: rotate(3deg); }
75% { transform: rotate(-3deg); }
}
/* Pop animation - success/attention */
@keyframes pop {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
/* Rubber band effect */
@keyframes rubber-band {
0% { transform: scale(1); }
30% { transform: scaleX(1.25) scaleY(0.75); }
40% { transform: scaleX(0.75) scaleY(1.25); }
50% { transform: scaleX(1.15) scaleY(0.85); }
65% { transform: scaleX(0.95) scaleY(1.05); }
75% { transform: scaleX(1.05) scaleY(0.95); }
100% { transform: scale(1); }
}
/* Jello wobble */
@keyframes jello {
0%, 100% { transform: skewX(0deg) skewY(0deg); }
30% { transform: skewX(-12.5deg) skewY(-12.5deg); }
40% { transform: skewX(6.25deg) skewY(6.25deg); }
50% { transform: skewX(-3.125deg) skewY(-3.125deg); }
65% { transform: skewX(1.5625deg) skewY(1.5625deg); }
75% { transform: skewX(-0.78125deg) skewY(-0.78125deg); }
}
/* Marquee scroll animation */
@keyframes marquee {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
@keyframes marquee-reverse {
0% { transform: translateX(-50%); }
100% { transform: translateX(0); }
}
/* Stagger fade in */
@keyframes stagger-fade-in {
0% { opacity: 0; transform: translateY(10px); }
100% { opacity: 1; transform: translateY(0); }
}
/* Pulse scale */
@keyframes pulse-scale {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
/* Glitch effect */
@keyframes glitch {
0%, 100% { transform: translate(0); }
20% { transform: translate(-2px, 2px); }
40% { transform: translate(-2px, -2px); }
60% { transform: translate(2px, 2px); }
80% { transform: translate(2px, -2px); }
}
/* Float animation */
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
/* Typewriter cursor blink */
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
/* Animation Classes */
.animate-accordion-down {
animation: accordion-down 200ms var(--ease-out);
}
.animate-accordion-up {
animation: accordion-up 200ms var(--ease-out);
}
.animate-collapsible-down {
animation: collapsible-down 200ms var(--ease-out);
}
.animate-collapsible-up {
animation: collapsible-up 200ms var(--ease-out);
}
.animate-shake {
animation: shake 200ms var(--ease-in-out);
}
.animate-bounce-in {
animation: bounce-in 300ms var(--ease-out);
}
.animate-pulse-shadow {
animation: pulse-shadow 2s var(--ease-in-out) infinite;
}
.animate-wiggle {
animation: wiggle 300ms var(--ease-in-out);
}
.animate-pop {
animation: pop 300ms var(--ease-out);
}
.animate-rubber-band {
animation: rubber-band 800ms var(--ease-out);
}
.animate-jello {
animation: jello 800ms var(--ease-out);
}
.animate-marquee {
animation: marquee 20s linear infinite;
}
.animate-marquee-fast {
animation: marquee 10s linear infinite;
}
.animate-marquee-slow {
animation: marquee 40s linear infinite;
}
.animate-marquee-reverse {
animation: marquee-reverse 20s linear infinite;
}
.animate-stagger-fade-in {
animation: stagger-fade-in 300ms var(--ease-out) forwards;
opacity: 0;
}
/* Stagger delay utilities */
.stagger-1 { animation-delay: 50ms; }
.stagger-2 { animation-delay: 100ms; }
.stagger-3 { animation-delay: 150ms; }
.stagger-4 { animation-delay: 200ms; }
.stagger-5 { animation-delay: 250ms; }
.stagger-6 { animation-delay: 300ms; }
.stagger-7 { animation-delay: 350ms; }
.stagger-8 { animation-delay: 400ms; }
.animate-pulse-scale {
animation: pulse-scale 2s var(--ease-in-out) infinite;
}
.animate-glitch {
animation: glitch 200ms var(--ease-in-out);
}
.animate-float {
animation: float 3s var(--ease-in-out) infinite;
}
.animate-blink {
animation: blink 1s step-end infinite;
}
/* Hover-triggered animations */
.hover-wiggle:hover {
animation: wiggle 300ms var(--ease-in-out);
}
.hover-pop:hover {
animation: pop 300ms var(--ease-out);
}
.hover-jello:hover {
animation: jello 800ms var(--ease-out);
}
.hover-rubber-band:hover {
animation: rubber-band 800ms var(--ease-out);
}
/* Tooltip & Popover Animations */
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes zoom-in {
from { transform: scale(0.95); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
@keyframes zoom-out {
from { transform: scale(1); opacity: 1; }
to { transform: scale(0.95); opacity: 0; }
}
@keyframes slide-in-from-top {
from { transform: translateY(-4px); }
to { transform: translateY(0); }
}
@keyframes slide-in-from-bottom {
from { transform: translateY(4px); }
to { transform: translateY(0); }
}
@keyframes slide-in-from-left {
from { transform: translateX(-4px); }
to { transform: translateX(0); }
}
@keyframes slide-in-from-right {
from { transform: translateX(4px); }
to { transform: translateX(0); }
}
/* Radix UI animation states */
[data-state="open"] {
animation: zoom-in 150ms var(--ease-out);
}
[data-state="closed"] {
animation: zoom-out 100ms var(--ease-in);
}
/* Tooltip specific animations */
[data-radix-popper-content-wrapper] [data-side="top"] {
animation: fade-in 150ms var(--ease-out), slide-in-from-bottom 150ms var(--ease-out);
}
[data-radix-popper-content-wrapper] [data-side="bottom"] {
animation: fade-in 150ms var(--ease-out), slide-in-from-top 150ms var(--ease-out);
}
[data-radix-popper-content-wrapper] [data-side="left"] {
animation: fade-in 150ms var(--ease-out), slide-in-from-right 150ms var(--ease-out);
}
[data-radix-popper-content-wrapper] [data-side="right"] {
animation: fade-in 150ms var(--ease-out), slide-in-from-left 150ms var(--ease-out);
}
/* ==========================================
MARQUEE COMPONENT UTILITIES
========================================== */
.bk-marquee {
display: flex;
overflow: hidden;
user-select: none;
gap: 1rem;
}
.bk-marquee-content {
display: flex;
gap: 1rem;
flex-shrink: 0;
animation: marquee 20s linear infinite;
}
.bk-marquee:hover .bk-marquee-content {
animation-play-state: paused;
}
.bk-marquee-vertical {
flex-direction: column;
height: 100%;
}
.bk-marquee-vertical .bk-marquee-content {
flex-direction: column;
animation: marquee-vertical 20s linear infinite;
}
@keyframes marquee-vertical {
0% { transform: translateY(0); }
100% { transform: translateY(-50%); }
}
/* ==========================================
LAYERED CARD UTILITIES
========================================== */
.bk-layered {
position: relative;
}
.bk-layered::before,
.bk-layered::after {
content: '';
position: absolute;
inset: 0;
border: 3px solid hsl(var(--foreground));
background: hsl(var(--card));
z-index: -1;
}
.bk-layered::before {
transform: translate(6px, 6px);
}
.bk-layered::after {
transform: translate(12px, 12px);
}
.bk-layered-sm::before {
transform: translate(4px, 4px);
}
.bk-layered-sm::after {
transform: translate(8px, 8px);
}
.bk-layered-lg::before {
transform: translate(8px, 8px);
}
.bk-layered-lg::after {
transform: translate(16px, 16px);
}
/* Single layer variant */
.bk-layered-single::after {
display: none;
}
/* Colored layers */
.bk-layered-primary::before {
background: hsl(var(--primary));
}
.bk-layered-accent::before {
background: hsl(var(--accent));
}
.bk-layered-secondary::before {
background: hsl(var(--secondary));
}
/* ==========================================
NEON GLOW UTILITIES
========================================== */
.bk-glow-neon-pink {
box-shadow:
0 0 5px hsl(var(--neon-pink)),
0 0 10px hsl(var(--neon-pink)),
0 0 20px hsl(var(--neon-pink) / 0.5);
}
.bk-glow-neon-green {
box-shadow:
0 0 5px hsl(var(--neon-green)),
0 0 10px hsl(var(--neon-green)),
0 0 20px hsl(var(--neon-green) / 0.5);
}
.bk-glow-neon-blue {
box-shadow:
0 0 5px hsl(var(--neon-blue)),
0 0 10px hsl(var(--neon-blue)),
0 0 20px hsl(var(--neon-blue) / 0.5);
}
/* Combined neon + brutal shadow */
.bk-shadow-neon-brutal-pink {
box-shadow:
4px 4px 0px hsl(var(--shadow-color)),
0 0 10px hsl(var(--neon-pink) / 0.5);
}
.bk-shadow-neon-brutal-green {
box-shadow:
4px 4px 0px hsl(var(--shadow-color)),
0 0 10px hsl(var(--neon-green) / 0.5);
}
/* ==========================================
UTILITY HELPERS
========================================== */
/* Force neubrutalism style reset */
.bk-brutal {
border-radius: 0 !important;
border-width: 3px;
border-style: solid;
border-color: hsl(var(--foreground));
}
/* Pressed state utility */
.bk-pressed {
transform: translate(4px, 4px);
box-shadow: none !important;
}
/* Lifted state utility */
.bk-lifted {
transform: translate(-4px, -4px);
box-shadow: 8px 8px 0px hsl(var(--shadow-color));
}
/* ==========================================
BUTTON ANIMATION KEYFRAMES
========================================== */
@keyframes brutal-pulse {
0%, 100% {
box-shadow: 4px 4px 0px hsl(var(--shadow-color));
transform: translate(0, 0);
}
50% {
box-shadow: 6px 6px 0px hsl(var(--shadow-color));
transform: translate(-2px, -2px);
}
}
@keyframes brutal-bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-6px);
}
}
@keyframes brutal-shake {
0%, 100% {
transform: translateX(0);
}
20% {
transform: translateX(-4px) rotate(-2deg);
}
40% {
transform: translateX(4px) rotate(2deg);
}
60% {
transform: translateX(-4px) rotate(-2deg);
}
80% {
transform: translateX(4px) rotate(2deg);
}
}
@keyframes brutal-wiggle {
0%, 100% {
transform: rotate(0deg);
}
25% {
transform: rotate(-5deg);
}
75% {
transform: rotate(5deg);
}
}
@keyframes brutal-pop {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
/* Respect reduced-motion: disable enter/exit overlay animations */
@media (prefers-reduced-motion: reduce) {
.animate-in,
.animate-out {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
}
/* ── Shape Animations ────────────────────────────────── */
@keyframes shape-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes shape-pulse {
0%, 100% { transform: scale(1); filter: drop-shadow(0 0 0 hsl(var(--primary))); }
50% { transform: scale(1.12); filter: drop-shadow(0 0 8px hsl(var(--primary))); }
}
@keyframes shape-bounce {
0%, 100% { transform: translateY(0) scaleX(1); }
40% { transform: translateY(-16px) scaleX(0.95); }
60% { transform: translateY(-10px) scaleX(1.05); }
}
/* slow / normal / fast speed modifiers */
.shape-animate-spin { animation: shape-spin 2s linear infinite; }
.shape-animate-spin-slow { animation: shape-spin 4s linear infinite; }
.shape-animate-spin-fast { animation: shape-spin 0.8s linear infinite; }
.shape-animate-pulse { animation: shape-pulse 2s var(--ease-in-out) infinite; }
.shape-animate-pulse-slow { animation: shape-pulse 3.5s var(--ease-in-out) infinite; }
.shape-animate-pulse-fast { animation: shape-pulse 1s var(--ease-in-out) infinite; }
.shape-animate-float { animation: float 3s var(--ease-in-out) infinite; }
.shape-animate-float-slow { animation: float 5s var(--ease-in-out) infinite; }
.shape-animate-float-fast { animation: float 1.5s var(--ease-in-out) infinite; }
.shape-animate-wiggle { animation: wiggle 0.8s var(--ease-in-out) infinite; }
.shape-animate-wiggle-slow { animation: wiggle 1.5s var(--ease-in-out) infinite; }
.shape-animate-wiggle-fast { animation: wiggle 0.4s var(--ease-in-out) infinite; }
.shape-animate-bounce { animation: shape-bounce 1.2s var(--ease-in-out) infinite; }
.shape-animate-bounce-slow { animation: shape-bounce 2.2s var(--ease-in-out) infinite; }
.shape-animate-bounce-fast { animation: shape-bounce 0.6s var(--ease-in-out) infinite; }
.shape-animate-glitch { animation: glitch 2s steps(1) infinite; }
.shape-animate-glitch-slow { animation: glitch 4s steps(1) infinite; }
.shape-animate-glitch-fast { animation: glitch 0.8s steps(1) infinite; }
/* ══════════════════════════════════════════════════════
BoldKit Motion System v3.5 — Part 2
Stepped shape presets · brutalist loading states ·
page-transition recipes.
Source of truth: registry/default/styles/motion.css
══════════════════════════════════════════════════════ */
/* ──────────────────────────────────────────────────────
v3.5 — Keyframes
────────────────────────────────────────────────────── */
/* Shape presets */
@keyframes bk-spin-step {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes bk-pulse-hard {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.14); }
}
@keyframes bk-marquee-stamp {
from { transform: translateX(0); }
to { transform: translateX(-100%); }
}
/* Loading states */
@keyframes bk-skeleton-stamp {
0%, 100% { opacity: 1; }
50% { opacity: 0.35; }
}
@keyframes bk-skeleton-blocks {
from { background-position: 0 0; }
to { background-position: var(--bk-skeleton-cell) 0; }
}
@keyframes bk-skeleton-scan {
from { transform: translateX(-100%); }
to { transform: translateX(100%); }
}
@keyframes bk-progress-marquee {
from { transform: translateX(-100%); }
to { transform: translateX(400%); }
}
/* Page transitions */
@keyframes bk-vt-wipe-in {
from { clip-path: inset(0 100% 0 0); }
to { clip-path: inset(0 0 0 0); }
}
@keyframes bk-vt-wipe-out {
from { clip-path: inset(0 0 0 0); }
to { clip-path: inset(0 0 0 100%); }
}
@keyframes bk-vt-block-in {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
@keyframes bk-vt-block-out {
from { transform: translateY(0); }
to { transform: translateY(-100%); }
}
/* ──────────────────────────────────────────────────────
v3.5 — Shape animation presets (stepped)
Extends the .shape-animate-* family with hard, non-eased
motion. Consumed by the `animation` prop on all shapes.
────────────────────────────────────────────────────── */
.shape-animate-spin-step { animation: bk-spin-step 2s steps(8, end) infinite; }
.shape-animate-spin-step-slow { animation: bk-spin-step 4s steps(8, end) infinite; }
.shape-animate-spin-step-fast { animation: bk-spin-step 0.9s steps(8, end) infinite; }
.shape-animate-pulse-hard { animation: bk-pulse-hard 1s steps(2, end) infinite; }
.shape-animate-pulse-hard-slow { animation: bk-pulse-hard 1.8s steps(2, end) infinite; }
.shape-animate-pulse-hard-fast { animation: bk-pulse-hard 0.5s steps(2, end) infinite; }
.shape-animate-marquee-stamp { animation: bk-marquee-stamp 3s steps(12, end) infinite; }
.shape-animate-marquee-stamp-slow { animation: bk-marquee-stamp 6s steps(12, end) infinite; }
.shape-animate-marquee-stamp-fast { animation: bk-marquee-stamp 1.5s steps(12, end) infinite; }
/* ──────────────────────────────────────────────────────
v3.5 — Brutalist loading states
────────────────────────────────────────────────────── */
/* SKELETON — stamp: hard on/off, no interpolated fade. */
.bk-skeleton-stamp {
animation: bk-skeleton-stamp var(--bk-dur-slow) var(--bk-ease-step-2) infinite;
}
/* SKELETON — blocks: marching cells on a stepped loop. */
.bk-skeleton-blocks {
--bk-skeleton-cell: 16px;
background-image: repeating-linear-gradient(
90deg,
hsl(var(--foreground, 240 10% 10%) / 0.16) 0,
hsl(var(--foreground, 240 10% 10%) / 0.16) calc(var(--bk-skeleton-cell) / 2),
transparent calc(var(--bk-skeleton-cell) / 2),
transparent var(--bk-skeleton-cell)
);
background-size: var(--bk-skeleton-cell) 100%;
animation: bk-skeleton-blocks 0.8s steps(4, end) infinite;
}
/* SKELETON — scanline: a hard bar sweeping the block. */
.bk-skeleton-scan {
position: relative;
overflow: hidden;
}
.bk-skeleton-scan::after {
content: '';
position: absolute;
inset: 0;
width: 40%;
background: hsl(var(--foreground, 240 10% 10%) / 0.18);
animation: bk-skeleton-scan 1.1s var(--bk-ease-step) infinite;
pointer-events: none;
}
/* PROGRESS — stepped: the indicator snaps in discrete notches. */
.bk-progress-stepped {
transition-timing-function: steps(10, end) !important;
}
/* PROGRESS — marquee: indeterminate travelling block. */
.bk-progress-marquee {
width: 25% !important;
animation: bk-progress-marquee 1.4s var(--bk-ease-step) infinite;
}
/* ──────────────────────────────────────────────────────
v3.5 — Page transition recipes (View Transitions API)
Opt in per navigation by passing the recipe name to startViewTransition()
from @boldkit/motion-core — it applies <html data-bk-transition> for that
navigation only and restores the previous value when the transition ends:
startViewTransition(() => navigate('/next'), 'hard-wipe')
────────────────────────────────────────────────────── */
/* HARD WIPE — new view wipes across in stepped notches. */
html[data-bk-transition='hard-wipe']::view-transition-old(root) {
animation: bk-vt-wipe-out var(--bk-dur-base) var(--bk-ease-step) both;
z-index: 0;
}
html[data-bk-transition='hard-wipe']::view-transition-new(root) {
animation: bk-vt-wipe-in var(--bk-dur-base) var(--bk-ease-step) both;
z-index: 1;
}
/* COLOR BLOCK — outgoing view lifts away, incoming slams up from below. */
html[data-bk-transition='color-block']::view-transition-old(root) {
animation: bk-vt-block-out var(--bk-dur-quick) var(--bk-ease-stamp) both;
z-index: 0;
}
html[data-bk-transition='color-block']::view-transition-new(root) {
animation: bk-vt-block-in var(--bk-dur-base) var(--bk-ease-snap) both;
z-index: 1;
}
/* STAMP — incoming view stamps down over the old one. */
html[data-bk-transition='stamp']::view-transition-old(root) {
animation: none;
z-index: 0;
}
html[data-bk-transition='stamp']::view-transition-new(root) {
animation: bk-stamp-in var(--bk-dur-base) var(--bk-ease-snap) both;
z-index: 1;
}
/* The same recipes per element, for React 19.3 <ViewTransition>:
<ViewTransition enter="bk-stamp" exit="bk-hard-wipe">…</ViewTransition>
React tags the snapshot with view-transition-class, which these match. */
::view-transition-old(.bk-hard-wipe) {
animation: bk-vt-wipe-out var(--bk-dur-base) var(--bk-ease-step) both;
}
::view-transition-new(.bk-hard-wipe) {
animation: bk-vt-wipe-in var(--bk-dur-base) var(--bk-ease-step) both;
}
::view-transition-old(.bk-color-block) {
animation: bk-vt-block-out var(--bk-dur-quick) var(--bk-ease-stamp) both;
}
::view-transition-new(.bk-color-block) {
animation: bk-vt-block-in var(--bk-dur-base) var(--bk-ease-snap) both;
}
::view-transition-old(.bk-stamp) {
animation: none;
}
::view-transition-new(.bk-stamp) {
animation: bk-stamp-in var(--bk-dur-base) var(--bk-ease-snap) both;
}
/* ──────────────────────────────────────────────────────
v3.5 — A11y guard
────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
[class*='shape-animate-spin-step'],
[class*='shape-animate-pulse-hard'],
[class*='shape-animate-marquee-stamp'],
[class*='bk-skeleton-'],
[class*='bk-skeleton-']::after,
[class*='bk-progress-'] {
animation: none !important;
transition-duration: 0.01ms !important;
}
/* Keep an indeterminate bar legible when its motion is removed. */
.bk-progress-marquee {
width: 100% !important;
opacity: 0.6;
}
html[data-bk-transition]::view-transition-old(root),
html[data-bk-transition]::view-transition-new(root) {
animation: none !important;
}
::view-transition-old(.bk-hard-wipe),
::view-transition-new(.bk-hard-wipe),
::view-transition-old(.bk-color-block),
::view-transition-new(.bk-color-block),
::view-transition-new(.bk-stamp) {
animation: none !important;
}
}
/* ===== BoldKit v3.6 utilities — keep byte-identical in all three globals.css ===== */
/* Brutalist native scrollbar: thin, square, foreground-coloured thumb.
Plain CSS rather than Tailwind 4.3's scrollbar-* utilities so it works on
any Tailwind v4 minor. */
.bk-scrollbar {
scrollbar-width: thin;
scrollbar-color: hsl(var(--foreground)) transparent;
}
/* Customizable <select> (appearance: base-select, Chromium 134+). Other
browsers keep the native picker; the closed control looks the same either way. */
@supports (appearance: base-select) {
.bk-native-select,
.bk-native-select::picker(select) {
appearance: base-select;
}
.bk-native-select::picker-icon {
display: none;
}
.bk-native-select::picker(select) {
margin-block: 6px;
padding: 0;
border: 3px solid hsl(var(--foreground));
border-radius: 0;
background: hsl(var(--background));
box-shadow: 4px 4px 0 hsl(var(--shadow-color));
}
.bk-native-select option {
padding: 0.5rem 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.025em;
}
.bk-native-select option:hover,
.bk-native-select option:focus-visible {
background: hsl(var(--accent));
color: hsl(var(--accent-foreground));
outline: none;
}
.bk-native-select option:checked {
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
.bk-native-select option::checkmark {
display: none;
}
}
/* Chat kit — scroll-aware edge fades. Static fallback: both edges always faded. */
@property --bk-fade-top {
syntax: '<length>';
inherits: false;
initial-value: 0px;
}
@property --bk-fade-bottom {
syntax: '<length>';
inherits: false;
initial-value: 0px;
}
.bk-scroll-fade {
--bk-fade-size: 2rem;
-webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--bk-fade-size), #000 calc(100% - var(--bk-fade-size)), transparent 100%);
mask-image: linear-gradient(to bottom, transparent 0, #000 var(--bk-fade-size), #000 calc(100% - var(--bk-fade-size)), transparent 100%);
}
/* Where scroll-driven animations exist, fade only the edge that has more
content past it. A box that can't scroll has an inactive timeline, so the
registered initial values (0px) apply and nothing fades. */
@supports (animation-timeline: scroll()) {
.bk-scroll-fade {
-webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--bk-fade-top), #000 calc(100% - var(--bk-fade-bottom)), transparent 100%);
mask-image: linear-gradient(to bottom, transparent 0, #000 var(--bk-fade-top), #000 calc(100% - var(--bk-fade-bottom)), transparent 100%);
animation: bk-scroll-fade linear both;
animation-timeline: scroll(self); /* must follow the shorthand */
}
}
@keyframes bk-scroll-fade {
0% { --bk-fade-top: 0px; --bk-fade-bottom: var(--bk-fade-size); }
8% { --bk-fade-top: var(--bk-fade-size); }
92% { --bk-fade-bottom: var(--bk-fade-size); }
100% { --bk-fade-top: var(--bk-fade-size); --bk-fade-bottom: 0px; }
}
/* Chat kit — animated text shimmer for live status labels. */
.bk-shimmer {
--bk-shimmer-base: hsl(var(--muted-foreground));
--bk-shimmer-highlight: hsl(var(--foreground));
color: var(--bk-shimmer-base);
background-image: linear-gradient(90deg, var(--bk-shimmer-base) 0%, var(--bk-shimmer-base) 42%, var(--bk-shimmer-highlight) 50%, var(--bk-shimmer-base) 58%, var(--bk-shimmer-base) 100%);
background-size: 300% 100%;
background-repeat: no-repeat;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: bk-shimmer 1.8s linear infinite;
}
@keyframes bk-shimmer {
from { background-position: 100% 0; }
to { background-position: 0% 0; }
}
@media (prefers-reduced-motion: reduce) {
.bk-shimmer { animation: none; background: none; -webkit-text-fill-color: currentColor; }
}
@media (forced-colors: active) {
.bk-shimmer { background: none; -webkit-text-fill-color: currentColor; }
}
/* ===== end BoldKit v3.6 utilities ===== */
registry/default/styles/motion.css
/*!
* BoldKit Motion System — L1 tokens + L2 recipes
* https://boldkit.dev
*
* Framework-agnostic. Works with React, Vue, Svelte, Solid,
* Astro, plain HTML — anywhere CSS works.
*
* Honors prefers-reduced-motion automatically.
*
* Install via shadcn CLI:
* npx shadcn@latest add "https://boldkit.dev/r/motion.json"
*
* Or import directly:
* import "@boldkit/motion-css";
*/
/* ──────────────────────────────────────────────────────
L1 — Motion Tokens
────────────────────────────────────────────────────── */
:where(:root) {
/* Easing — stepped & hard, not smooth. Brutalism's soul. */
--bk-ease-step: steps(4, end);
--bk-ease-step-2: steps(2, end);
--bk-ease-snap: cubic-bezier(0.22, 1, 0.3, 1.12);
--bk-ease-stamp: cubic-bezier(0.7, 0, 0.84, 0);
--bk-ease-rubber: cubic-bezier(0.34, 1.56, 0.64, 1);
--bk-ease-linear: linear;
/* Durations */
--bk-dur-instant: 80ms;
--bk-dur-snap: 150ms;
--bk-dur-quick: 240ms;
--bk-dur-base: 400ms;
--bk-dur-slow: 700ms;
/* Distances */
--bk-shadow-offset: 4px;
--bk-shadow-offset-press: 0px;
--bk-translate-press: 2px;
--bk-reveal-offset: 16px;
}
/* ──────────────────────────────────────────────────────
L1 — Keyframes (bk-* namespaced)
────────────────────────────────────────────────────── */
@keyframes bk-stamp-in {
0% { opacity: 0; transform: scale(0.6) rotate(-4deg); }
60% { opacity: 1; transform: scale(1.04) rotate(0.5deg); }
100% { opacity: 1; transform: scale(1) rotate(0); }
}
@keyframes bk-press-shadow {
0% { transform: translate(var(--bk-shadow-offset), var(--bk-shadow-offset)); }
100% { transform: translate(var(--bk-shadow-offset-press), var(--bk-shadow-offset-press)); }
}
@keyframes bk-reveal-up {
from { opacity: 0; transform: translateY(var(--bk-reveal-offset)); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes bk-reveal-down {
from { opacity: 0; transform: translateY(calc(var(--bk-reveal-offset) * -1)); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes bk-reveal-left {
from { opacity: 0; transform: translateX(var(--bk-reveal-offset)); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes bk-reveal-right {
from { opacity: 0; transform: translateX(calc(var(--bk-reveal-offset) * -1)); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes bk-slide-hard-x {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@keyframes bk-slide-hard-y {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
@keyframes bk-shake-x {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-6px); }
40% { transform: translateX(6px); }
60% { transform: translateX(-4px); }
80% { transform: translateX(4px); }
}
@keyframes bk-pulse-shadow {
0%, 100% { transform: translate(var(--bk-shadow-offset), var(--bk-shadow-offset)); }
50% { transform: translate(calc(var(--bk-shadow-offset) + 2px), calc(var(--bk-shadow-offset) + 2px)); }
}
/* ──────────────────────────────────────────────────────
L2 — Recipes
────────────────────────────────────────────────────── */
/* 1. PRESS — hard offset-shadow choreography on the snap curve. Mobile-safe.
Animates transform + box-shadow (not a negative-z pseudo) so it composes
correctly with a background colour: hover lifts and grows the shadow, active
presses into the page and collapses it. */
.bk-press {
box-shadow: var(--bk-shadow-offset) var(--bk-shadow-offset) 0 hsl(var(--shadow-color, 240 10% 10%));
transition: transform var(--bk-dur-snap) var(--bk-ease-snap),
box-shadow var(--bk-dur-snap) var(--bk-ease-snap);
}
.bk-press:hover {
transform: translate(calc(var(--bk-translate-press) * -1), calc(var(--bk-translate-press) * -1));
box-shadow: calc(var(--bk-shadow-offset) + var(--bk-translate-press)) calc(var(--bk-shadow-offset) + var(--bk-translate-press)) 0 hsl(var(--shadow-color, 240 10% 10%));
}
.bk-press:active {
transform: translate(var(--bk-shadow-offset), var(--bk-shadow-offset));
box-shadow: var(--bk-shadow-offset-press) var(--bk-shadow-offset-press) 0 hsl(var(--shadow-color, 240 10% 10%));
transition-duration: var(--bk-dur-instant);
}
@media (hover: none) {
.bk-press {
transition-duration: var(--bk-dur-instant);
}
}
/* 2. STAMP-IN — hard entrance */
.bk-stamp-in { animation: bk-stamp-in var(--bk-dur-base) var(--bk-ease-snap) both; }
.bk-stamp-in-quick { animation-duration: var(--bk-dur-quick); }
.bk-stamp-in-slow { animation-duration: var(--bk-dur-slow); }
/* 3. SHAKE-X — error state */
.bk-shake-x { animation: bk-shake-x var(--bk-dur-quick) var(--bk-ease-step-2); }
/* 4. REVEAL — scroll-triggered (paired with L3 IntersectionObserver) */
.bk-reveal { opacity: 0; }
.bk-reveal-in { animation: bk-reveal-up var(--bk-dur-quick) var(--bk-ease-snap) both; }
.bk-reveal-up.bk-reveal-in { animation-name: bk-reveal-up; }
.bk-reveal-down.bk-reveal-in { animation-name: bk-reveal-down; }
.bk-reveal-left.bk-reveal-in { animation-name: bk-reveal-left; }
.bk-reveal-right.bk-reveal-in { animation-name: bk-reveal-right; }
/* 5. SLIDE-HARD — stepped slide for drawers/sheets/tabs */
.bk-slide-hard-x { animation: bk-slide-hard-x var(--bk-dur-quick) var(--bk-ease-step) both; }
.bk-slide-hard-y { animation: bk-slide-hard-y var(--bk-dur-quick) var(--bk-ease-step) both; }
/* 6. PULSE-SHADOW — attention/loading */
.bk-pulse-shadow { position: relative; }
.bk-pulse-shadow::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
background: hsl(var(--shadow-color, 240 10% 10%));
transform: translate(var(--bk-shadow-offset), var(--bk-shadow-offset));
animation: bk-pulse-shadow 1.6s var(--bk-ease-snap) infinite;
pointer-events: none;
}
/* ──────────────────────────────────────────────────────
A11y — Universal reduced-motion guard
────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
[class*="bk-press"],
[class*="bk-stamp-in"],
[class*="bk-shake"],
[class*="bk-reveal"],
[class*="bk-slide-hard"],
[class*="bk-pulse-shadow"] {
animation: none !important;
transition-duration: 0.01ms !important;
}
}
/* ══════════════════════════════════════════════════════
BoldKit Motion System v3.5 — Part 2
Stepped shape presets · brutalist loading states ·
page-transition recipes.
Source of truth: registry/default/styles/motion.css
══════════════════════════════════════════════════════ */
/* ──────────────────────────────────────────────────────
v3.5 — Keyframes
────────────────────────────────────────────────────── */
/* Shape presets */
@keyframes bk-spin-step {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes bk-pulse-hard {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.14); }
}
@keyframes bk-marquee-stamp {
from { transform: translateX(0); }
to { transform: translateX(-100%); }
}
/* Loading states */
@keyframes bk-skeleton-stamp {
0%, 100% { opacity: 1; }
50% { opacity: 0.35; }
}
@keyframes bk-skeleton-blocks {
from { background-position: 0 0; }
to { background-position: var(--bk-skeleton-cell) 0; }
}
@keyframes bk-skeleton-scan {
from { transform: translateX(-100%); }
to { transform: translateX(100%); }
}
@keyframes bk-progress-marquee {
from { transform: translateX(-100%); }
to { transform: translateX(400%); }
}
/* Page transitions */
@keyframes bk-vt-wipe-in {
from { clip-path: inset(0 100% 0 0); }
to { clip-path: inset(0 0 0 0); }
}
@keyframes bk-vt-wipe-out {
from { clip-path: inset(0 0 0 0); }
to { clip-path: inset(0 0 0 100%); }
}
@keyframes bk-vt-block-in {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
@keyframes bk-vt-block-out {
from { transform: translateY(0); }
to { transform: translateY(-100%); }
}
/* ──────────────────────────────────────────────────────
v3.5 — Shape animation presets (stepped)
Extends the .shape-animate-* family with hard, non-eased
motion. Consumed by the `animation` prop on all shapes.
────────────────────────────────────────────────────── */
.shape-animate-spin-step { animation: bk-spin-step 2s steps(8, end) infinite; }
.shape-animate-spin-step-slow { animation: bk-spin-step 4s steps(8, end) infinite; }
.shape-animate-spin-step-fast { animation: bk-spin-step 0.9s steps(8, end) infinite; }
.shape-animate-pulse-hard { animation: bk-pulse-hard 1s steps(2, end) infinite; }
.shape-animate-pulse-hard-slow { animation: bk-pulse-hard 1.8s steps(2, end) infinite; }
.shape-animate-pulse-hard-fast { animation: bk-pulse-hard 0.5s steps(2, end) infinite; }
.shape-animate-marquee-stamp { animation: bk-marquee-stamp 3s steps(12, end) infinite; }
.shape-animate-marquee-stamp-slow { animation: bk-marquee-stamp 6s steps(12, end) infinite; }
.shape-animate-marquee-stamp-fast { animation: bk-marquee-stamp 1.5s steps(12, end) infinite; }
/* ──────────────────────────────────────────────────────
v3.5 — Brutalist loading states
────────────────────────────────────────────────────── */
/* SKELETON — stamp: hard on/off, no interpolated fade. */
.bk-skeleton-stamp {
animation: bk-skeleton-stamp var(--bk-dur-slow) var(--bk-ease-step-2) infinite;
}
/* SKELETON — blocks: marching cells on a stepped loop. */
.bk-skeleton-blocks {
--bk-skeleton-cell: 16px;
background-image: repeating-linear-gradient(
90deg,
hsl(var(--foreground, 240 10% 10%) / 0.16) 0,
hsl(var(--foreground, 240 10% 10%) / 0.16) calc(var(--bk-skeleton-cell) / 2),
transparent calc(var(--bk-skeleton-cell) / 2),
transparent var(--bk-skeleton-cell)
);
background-size: var(--bk-skeleton-cell) 100%;
animation: bk-skeleton-blocks 0.8s steps(4, end) infinite;
}
/* SKELETON — scanline: a hard bar sweeping the block. */
.bk-skeleton-scan {
position: relative;
overflow: hidden;
}
.bk-skeleton-scan::after {
content: '';
position: absolute;
inset: 0;
width: 40%;
background: hsl(var(--foreground, 240 10% 10%) / 0.18);
animation: bk-skeleton-scan 1.1s var(--bk-ease-step) infinite;
pointer-events: none;
}
/* PROGRESS — stepped: the indicator snaps in discrete notches. */
.bk-progress-stepped {
transition-timing-function: steps(10, end) !important;
}
/* PROGRESS — marquee: indeterminate travelling block. */
.bk-progress-marquee {
width: 25% !important;
animation: bk-progress-marquee 1.4s var(--bk-ease-step) infinite;
}
/* ──────────────────────────────────────────────────────
v3.5 — Page transition recipes (View Transitions API)
Opt in per navigation by passing the recipe name to startViewTransition()
from @boldkit/motion-core — it applies <html data-bk-transition> for that
navigation only and restores the previous value when the transition ends:
startViewTransition(() => navigate('/next'), 'hard-wipe')
────────────────────────────────────────────────────── */
/* HARD WIPE — new view wipes across in stepped notches. */
html[data-bk-transition='hard-wipe']::view-transition-old(root) {
animation: bk-vt-wipe-out var(--bk-dur-base) var(--bk-ease-step) both;
z-index: 0;
}
html[data-bk-transition='hard-wipe']::view-transition-new(root) {
animation: bk-vt-wipe-in var(--bk-dur-base) var(--bk-ease-step) both;
z-index: 1;
}
/* COLOR BLOCK — outgoing view lifts away, incoming slams up from below. */
html[data-bk-transition='color-block']::view-transition-old(root) {
animation: bk-vt-block-out var(--bk-dur-quick) var(--bk-ease-stamp) both;
z-index: 0;
}
html[data-bk-transition='color-block']::view-transition-new(root) {
animation: bk-vt-block-in var(--bk-dur-base) var(--bk-ease-snap) both;
z-index: 1;
}
/* STAMP — incoming view stamps down over the old one. */
html[data-bk-transition='stamp']::view-transition-old(root) {
animation: none;
z-index: 0;
}
html[data-bk-transition='stamp']::view-transition-new(root) {
animation: bk-stamp-in var(--bk-dur-base) var(--bk-ease-snap) both;
z-index: 1;
}
/* The same recipes per element, for React 19.3 <ViewTransition>:
<ViewTransition enter="bk-stamp" exit="bk-hard-wipe">…</ViewTransition>
React tags the snapshot with view-transition-class, which these match. */
::view-transition-old(.bk-hard-wipe) {
animation: bk-vt-wipe-out var(--bk-dur-base) var(--bk-ease-step) both;
}
::view-transition-new(.bk-hard-wipe) {
animation: bk-vt-wipe-in var(--bk-dur-base) var(--bk-ease-step) both;
}
::view-transition-old(.bk-color-block) {
animation: bk-vt-block-out var(--bk-dur-quick) var(--bk-ease-stamp) both;
}
::view-transition-new(.bk-color-block) {
animation: bk-vt-block-in var(--bk-dur-base) var(--bk-ease-snap) both;
}
::view-transition-old(.bk-stamp) {
animation: none;
}
::view-transition-new(.bk-stamp) {
animation: bk-stamp-in var(--bk-dur-base) var(--bk-ease-snap) both;
}
/* ──────────────────────────────────────────────────────
v3.5 — A11y guard
────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
[class*='shape-animate-spin-step'],
[class*='shape-animate-pulse-hard'],
[class*='shape-animate-marquee-stamp'],
[class*='bk-skeleton-'],
[class*='bk-skeleton-']::after,
[class*='bk-progress-'] {
animation: none !important;
transition-duration: 0.01ms !important;
}
/* Keep an indeterminate bar legible when its motion is removed. */
.bk-progress-marquee {
width: 100% !important;
opacity: 0.6;
}
html[data-bk-transition]::view-transition-old(root),
html[data-bk-transition]::view-transition-new(root) {
animation: none !important;
}
::view-transition-old(.bk-hard-wipe),
::view-transition-new(.bk-hard-wipe),
::view-transition-old(.bk-color-block),
::view-transition-new(.bk-color-block),
::view-transition-new(.bk-stamp) {
animation: none !important;
}
}
/* ──────────────────────────────────────────────────────
v3.6 — Chat kit (MessageScroller fade, Marker shimmer).
Also in the v3.6 block of each globals.css.
────────────────────────────────────────────────────── */
/* Chat kit — scroll-aware edge fades. Static fallback: both edges always faded. */
@property --bk-fade-top {
syntax: '<length>';
inherits: false;
initial-value: 0px;
}
@property --bk-fade-bottom {
syntax: '<length>';
inherits: false;
initial-value: 0px;
}
.bk-scroll-fade {
--bk-fade-size: 2rem;
-webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--bk-fade-size), #000 calc(100% - var(--bk-fade-size)), transparent 100%);
mask-image: linear-gradient(to bottom, transparent 0, #000 var(--bk-fade-size), #000 calc(100% - var(--bk-fade-size)), transparent 100%);
}
/* Where scroll-driven animations exist, fade only the edge that has more
content past it. A box that can't scroll has an inactive timeline, so the
registered initial values (0px) apply and nothing fades. */
@supports (animation-timeline: scroll()) {
.bk-scroll-fade {
-webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--bk-fade-top), #000 calc(100% - var(--bk-fade-bottom)), transparent 100%);
mask-image: linear-gradient(to bottom, transparent 0, #000 var(--bk-fade-top), #000 calc(100% - var(--bk-fade-bottom)), transparent 100%);
animation: bk-scroll-fade linear both;
animation-timeline: scroll(self); /* must follow the shorthand */
}
}
@keyframes bk-scroll-fade {
0% { --bk-fade-top: 0px; --bk-fade-bottom: var(--bk-fade-size); }
8% { --bk-fade-top: var(--bk-fade-size); }
92% { --bk-fade-bottom: var(--bk-fade-size); }
100% { --bk-fade-top: var(--bk-fade-size); --bk-fade-bottom: 0px; }
}
/* Chat kit — animated text shimmer for live status labels. */
.bk-shimmer {
--bk-shimmer-base: hsl(var(--muted-foreground));
--bk-shimmer-highlight: hsl(var(--foreground));
color: var(--bk-shimmer-base);
background-image: linear-gradient(90deg, var(--bk-shimmer-base) 0%, var(--bk-shimmer-base) 42%, var(--bk-shimmer-highlight) 50%, var(--bk-shimmer-base) 58%, var(--bk-shimmer-base) 100%);
background-size: 300% 100%;
background-repeat: no-repeat;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: bk-shimmer 1.8s linear infinite;
}
@keyframes bk-shimmer {
from { background-position: 100% 0; }
to { background-position: 0% 0; }
}
@media (prefers-reduced-motion: reduce) {
.bk-shimmer { animation: none; background: none; -webkit-text-fill-color: currentColor; }
}
@media (forced-colors: active) {
.bk-shimmer { background: none; -webkit-text-fill-color: currentColor; }
}