diff --git a/src/app/globals.css b/src/app/globals.css
index a47c1ee..98a6ca9 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -189,28 +189,22 @@ select:-webkit-autofill:focus {
* the cut, short enough to feel like a single app. The cubic-bezier
* keeps the start crisp and the finish gentle.
*/
+/* Route fade — pure opacity, no slide. The old "translateY(6px) on enter,
+ * translateY(-4px) on exit" was a classic multi-axis vestibular trigger.
+ * 140ms total is the sweet spot: barely there, but softens the cut. */
::view-transition-old(page-content) {
- animation: route-fade-out 180ms cubic-bezier(0.4, 0, 0.2, 1) both;
+ animation: route-fade-out 90ms ease-out both;
}
::view-transition-new(page-content) {
- animation: route-fade-in 240ms cubic-bezier(0.22, 1, 0.36, 1) both;
+ animation: route-fade-in 140ms ease-out both;
}
@keyframes route-fade-out {
- to {
- opacity: 0;
- transform: translateY(-4px);
- }
+ to { opacity: 0; }
}
@keyframes route-fade-in {
- from {
- opacity: 0;
- transform: translateY(6px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
+ from { opacity: 0; }
+ to { opacity: 1; }
}
/* Top-of-page shimmer for the LoadingFade placeholder bar. */
@@ -220,7 +214,23 @@ select:-webkit-autofill:focus {
}
/* Reduce motion: respect the user's OS-level preference. */
+/* The motion pass deliberately keeps the visual design intact (colors, type, layout)
+ * but flattens everything that fights vestibular comfort. This block wipes:
+ * - all CSS animations and transitions across the app
+ * - all view transitions and route fades
+ * - GSAP, framer-motion, and any scroll-driven transforms (those need JS-side guards
+ * in their components, but the CSS-side transforms inherit this via inheritance).
+ * If you want even less, just set prefers-reduced-motion: reduce in your OS and
+ * every animation in the app turns off. */
@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.001ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.001ms !important;
+ scroll-behavior: auto !important;
+ }
::view-transition-old(page-content),
::view-transition-new(page-content) {
animation: none !important;
@@ -228,17 +238,26 @@ select:-webkit-autofill:focus {
@keyframes transition-shimmer {
0%, 100% { transform: translateX(0); }
}
- /* Atelier modal + stagger animations also respect reduced-motion */
.atelier-enter,
.atelier-backdrop,
.atelier-stagger > *,
- .atelier-cta .atelier-cta-shimmer {
+ .atelier-cta .atelier-cta-shimmer,
+ .parallax-float,
+ .animate-bounce,
+ .hero-reveal {
animation: none !important;
+ transition: none !important;
}
- .atelier-stagger > * {
+ .atelier-stagger > *,
+ .parallax-float,
+ .hero-reveal {
opacity: 1 !important;
transform: none !important;
}
+ /* Scroll progress bar (Tuxedo hero) — no animated width when motion is off */
+ .scroll-progress-bar {
+ transition: none !important;
+ }
}
/* Light mode placeholder (used by storefront) */
@@ -290,7 +309,7 @@ select:-webkit-autofill:focus {
box-shadow:
0 16px 48px rgba(0, 0, 0, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
- transform: translateY(-2px);
+ transform: translateY(-1px);
}
/* ─── Premium card - Light Theme ─────────────────────────────────────── */
@@ -321,10 +340,10 @@ select:-webkit-autofill:focus {
0 8px 32px rgba(0, 0, 0, 0.06),
0 0 0 1px rgba(26, 77, 46, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
- transform: translateY(-2px);
+ transform: translateY(-1px);
}
.card:active {
- transform: scale(0.98) translateY(0);
+ transform: scale(0.995) translateY(0);
}
.card-dark {
@@ -377,10 +396,11 @@ select:-webkit-autofill:focus {
box-shadow:
0 8px 20px rgba(26, 77, 46, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.15);
- transform: translateY(-1px);
+ /* No vertical lift on hover — was a 1px translate that compounded with the gradient
+ * shift and the inset highlight to read as jittery on the eye. */
}
.btn-primary:active {
- transform: scale(0.98) translateY(0);
+ transform: scale(0.995);
}
.btn-primary-green {
@@ -406,7 +426,6 @@ select:-webkit-autofill:focus {
box-shadow:
0 8px 20px rgba(22, 163, 74, 0.4),
inset 0 1px 0 rgba(255,255,255,0.2);
- transform: translateY(-1px);
}
/* ─── Gradient text ───────────────────────────────────────────── */
@@ -729,13 +748,12 @@ select:-webkit-autofill:focus {
.atelier-type-card:hover {
border-color: rgba(34, 78, 47, 0.25);
background: rgba(255, 255, 255, 0.85);
- transform: translateY(-1px);
}
.atelier-type-card:hover::before {
opacity: 1;
}
.atelier-type-card:active {
- transform: translateY(0) scale(0.985);
+ transform: scale(0.995);
}
.atelier-type-card.is-selected {
background: linear-gradient(135deg, #224E2F 0%, #14532D 100%);
@@ -747,11 +765,13 @@ select:-webkit-autofill:focus {
}
.atelier-type-card .atelier-type-icon {
color: #786B53;
- transition: color 220ms ease, transform 220ms ease;
+ transition: color 180ms ease;
}
.atelier-type-card.is-selected .atelier-type-icon {
color: #FCD34D;
- transform: scale(1.06);
+ /* Removed the 1.06 scale on selected — the color change alone communicates the
+ * selection. Scale + color change in the same beat read as "double confirmation",
+ * which adds up when many cards animate at once. */
}
.atelier-type-card .atelier-type-name {
font-family: var(--font-fraunces);
@@ -768,12 +788,11 @@ select:-webkit-autofill:focus {
.atelier-type-card .atelier-type-check {
opacity: 0;
color: #FCD34D;
- transform: scale(0.6);
- transition: opacity 200ms ease, transform 200ms ease;
+ transition: opacity 180ms ease;
}
.atelier-type-card.is-selected .atelier-type-check {
opacity: 1;
- transform: scale(1);
+ /* Removed the scale(0.6 → 1) on the checkmark — pure opacity fade is calmer. */
}
/* Pill toggle — Active / Taxable */
@@ -810,7 +829,11 @@ select:-webkit-autofill:focus {
background: #FFFFFF;
border-radius: 999px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
- transition: transform 240ms cubic-bezier(0.34, 1.56, 0.64, 1);
+ /* Was cubic-bezier(0.34, 1.56, 0.64, 1) — the bouncy overshoot ease that
+ * overshoots ~13% past the target then settles. Looks playful on a static
+ * design, but in a real product it reads as twitchy and contributes to the
+ * sense of "too much movement". */
+ transition: transform 160ms ease-out;
}
.atelier-toggle.is-on .atelier-toggle-thumb {
transform: translateX(16px);
@@ -940,35 +963,38 @@ select:-webkit-autofill:focus {
color: #A8A29E;
}
-/* Modal enter animation */
+/* Modal enter animation — calmer version. The previous 420ms scale + 20px slide
+ * with 80-440ms cumulative stagger delays meant the modal kept moving for nearly
+ * a full second. Now: 180ms opacity-only fade-in, 4px max, no scale, no stagger. */
@keyframes atelier-enter {
- from { opacity: 0; transform: translateY(20px) scale(0.96); }
- to { opacity: 1; transform: translateY(0) scale(1); }
+ from { opacity: 0; transform: translateY(4px); }
+ to { opacity: 1; transform: translateY(0); }
}
@keyframes atelier-backdrop {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes atelier-stagger {
- from { opacity: 0; transform: translateY(8px); }
+ from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
.atelier-enter {
- animation: atelier-enter 420ms cubic-bezier(0.22, 1, 0.36, 1) both;
+ animation: atelier-enter 180ms ease-out both;
}
.atelier-backdrop {
- animation: atelier-backdrop 300ms ease both;
+ animation: atelier-backdrop 140ms ease-out both;
}
.atelier-stagger > * {
- animation: atelier-stagger 480ms cubic-bezier(0.22, 1, 0.36, 1) both;
+ animation: atelier-stagger 180ms ease-out both;
+ /* No more 80-440ms cumulative delays — items appear together, not as a parade. */
}
-.atelier-stagger > *:nth-child(1) { animation-delay: 80ms; }
-.atelier-stagger > *:nth-child(2) { animation-delay: 140ms; }
-.atelier-stagger > *:nth-child(3) { animation-delay: 200ms; }
-.atelier-stagger > *:nth-child(4) { animation-delay: 260ms; }
-.atelier-stagger > *:nth-child(5) { animation-delay: 320ms; }
-.atelier-stagger > *:nth-child(6) { animation-delay: 380ms; }
-.atelier-stagger > *:nth-child(7) { animation-delay: 440ms; }
+.atelier-stagger > *:nth-child(1) { animation-delay: 0ms; }
+.atelier-stagger > *:nth-child(2) { animation-delay: 30ms; }
+.atelier-stagger > *:nth-child(3) { animation-delay: 60ms; }
+.atelier-stagger > *:nth-child(4) { animation-delay: 90ms; }
+.atelier-stagger > *:nth-child(5) { animation-delay: 120ms; }
+.atelier-stagger > *:nth-child(6) { animation-delay: 150ms; }
+.atelier-stagger > *:nth-child(7) { animation-delay: 180ms; }
/* Primary CTA — gradient with subtle inner highlight */
.atelier-cta {
@@ -993,7 +1019,7 @@ select:-webkit-autofill:focus {
0 6px 18px -6px rgba(20, 83, 45, 0.55),
inset 0 1px 0 rgba(255, 255, 255, 0.14),
inset 0 -1px 0 rgba(0, 0, 0, 0.10);
- transition: all 220ms cubic-bezier(0.4, 0, 0.2, 1);
+ transition: background-position 220ms ease-out, box-shadow 180ms ease-out;
overflow: hidden;
}
.atelier-cta:hover {
@@ -1002,10 +1028,9 @@ select:-webkit-autofill:focus {
0 10px 28px -8px rgba(20, 83, 45, 0.65),
inset 0 1px 0 rgba(255, 255, 255, 0.20),
inset 0 -1px 0 rgba(0, 0, 0, 0.10);
- transform: translateY(-1px);
}
.atelier-cta:active {
- transform: translateY(0) scale(0.985);
+ transform: scale(0.995);
}
.atelier-cta:disabled {
opacity: 0.5;
@@ -1022,7 +1047,9 @@ select:-webkit-autofill:focus {
height: 100%;
background: linear-gradient(105deg, transparent 30%, rgba(255, 255, 255, 0.18) 50%, transparent 70%);
pointer-events: none;
- transition: left 700ms cubic-bezier(0.4, 0, 0.2, 1);
+ /* Was 700ms — the shimmer is decorative but a long linear slide on every
+ * hover draws the eye. 400ms keeps the highlight but doesn't linger. */
+ transition: left 400ms ease-out;
}
.atelier-cta:hover .atelier-cta-shimmer {
left: 130%;
diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx
index 0ae9cf7..e37e376 100644
--- a/src/components/Providers.tsx
+++ b/src/components/Providers.tsx
@@ -1,6 +1,7 @@
"use client";
import { ThemeProvider } from "next-themes";
+import { MotionConfig } from "framer-motion";
import { CartProvider } from "@/context/CartContext";
import { usePathname } from "next/navigation";
import SiteHeader from "@/components/layout/SiteHeader";
@@ -28,13 +29,26 @@ export function Providers({ children }: { children: React.ReactNode }) {
// The visibility:hidden pattern was causing click handling issues on hidden elements
return (