shadimage
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; }
}

More from boldkit

Browse all components in this library →