diff --git a/src/components/ordering/MenuBackdrop.module.scss b/src/components/ordering/MenuBackdrop.module.scss
index b86e2cf..c7d19ac 100644
--- a/src/components/ordering/MenuBackdrop.module.scss
+++ b/src/components/ordering/MenuBackdrop.module.scss
@@ -1,12 +1,11 @@
-@use '../../styles/variables' as *;
-
-@keyframes menu-backdrop-float {
- 0%,
- 100% {
- transform: translateY(0) rotate(var(--icon-rotate, 0deg));
+@keyframes menu-backdrop-scroll {
+ from {
+ background-position: 0 0;
}
- 50% {
- transform: translateY(-6px) rotate(calc(var(--icon-rotate, 0deg) + 6deg));
+ to {
+ // Exactly one tile (see TILE_SIZE in MenuBackdrop.tsx) — moving by
+ // precisely that distance loops seamlessly since the background repeats.
+ background-position: 240px 240px;
}
}
@@ -16,21 +15,12 @@
z-index: -1;
overflow: hidden;
pointer-events: none;
- filter: blur(2px);
-}
-
-.icon {
- position: absolute;
- color: $color-primary;
- opacity: 0.16;
- transform: rotate(var(--icon-rotate, 0deg));
+ filter: blur(4px) grayscale(1);
+ opacity: 0.6;
+ background-repeat: repeat;
+ background-size: 240px 240px;
@media (prefers-reduced-motion: no-preference) {
- animation: menu-backdrop-float 5s ease-in-out infinite;
- }
-
- svg {
- width: 100%;
- height: 100%;
+ animation: menu-backdrop-scroll 20s linear infinite;
}
}
diff --git a/src/components/ordering/MenuBackdrop.tsx b/src/components/ordering/MenuBackdrop.tsx
index cfd96a1..0c7b287 100644
--- a/src/components/ordering/MenuBackdrop.tsx
+++ b/src/components/ordering/MenuBackdrop.tsx
@@ -1,51 +1,30 @@
-import type { CSSProperties } from 'react'
-
import styles from './MenuBackdrop.module.scss'
-const PizzaIcon = () => (
-
-)
+// Rendered once as a tiny repeating SVG tile (not as individual DOM icons)
+// so the whole pattern can scroll diagonally forever via a plain
+// `background-position` CSS animation — moving by exactly one tile per
+// loop wraps seamlessly since the background repeats.
+const pizza = (color: string) => `
+
+
+
+
+`
-const BurgerIcon = () => (
-
-)
+const burger = (color: string) => `
+
+
+
+
+
+`
-const FriesIcon = () => (
-
-)
+const fries = (color: string) => `
+
+
+`
-const ICONS = [PizzaIcon, BurgerIcon, FriesIcon]
+const ICON_VARIANTS = [pizza, burger, fries]
const ICON_COLORS = [
'#e2725b', // tomato
'#d9a441', // crust
@@ -55,45 +34,41 @@ const ICON_COLORS = [
'#8a5a44', // cocoa
]
-// Fixed, hand-placed positions rather than random ones — random values would
-// differ between the server-rendered and first client-rendered HTML and
-// trigger a hydration mismatch. Positioned as percentages of the containing
-// card, not the viewport, so this only ever covers whatever box renders it.
-const PLACEMENTS = [
- { top: '10%', left: '8%', size: 32, rotate: -14, delay: 0 },
- { top: '14%', left: '82%', size: 26, rotate: 18, delay: 0.5 },
- { top: '40%', left: '20%', size: 24, rotate: 8, delay: 1.1 },
- { top: '55%', left: '80%', size: 30, rotate: -10, delay: 0.3 },
- { top: '78%', left: '14%', size: 26, rotate: 16, delay: 1.6 },
- { top: '82%', left: '58%', size: 22, rotate: -20, delay: 0.8 },
- { top: '22%', left: '48%', size: 20, rotate: 6, delay: 1.3 },
- { top: '62%', left: '44%', size: 24, rotate: -6, delay: 0.2 },
-]
+// One icon centered in each grid cell within the tile, so they line up
+// evenly instead of clustering.
+const TILE_SIZE = 240
+const GRID_COLS = 4
+const GRID_ROWS = 4
+const CELL_WIDTH = TILE_SIZE / GRID_COLS
+const CELL_HEIGHT = TILE_SIZE / GRID_ROWS
+const ICON_SIZE = 30
+const ICON_SCALE = ICON_SIZE / 24
+
+const buildTileSvg = () => {
+ const instances = Array.from(
+ { length: GRID_COLS * GRID_ROWS },
+ (_, index) => {
+ const col = index % GRID_COLS
+ const row = Math.floor(index / GRID_COLS)
+ const x = col * CELL_WIDTH + CELL_WIDTH / 2
+ const y = row * CELL_HEIGHT + CELL_HEIGHT / 2
+ const rotate = ((index * 29) % 48) - 24
+ const color = ICON_COLORS[index % ICON_COLORS.length]
+ const icon = ICON_VARIANTS[index % ICON_VARIANTS.length](color)
+ const transform = `translate(${x.toFixed(1)} ${y.toFixed(1)}) rotate(${rotate}) scale(${ICON_SCALE.toFixed(3)}) translate(-12 -12)`
+ return `${icon}`
+ },
+ ).join('')
+
+ return ``
+}
+
+const TILE_DATA_URL = `data:image/svg+xml,${encodeURIComponent(buildTileSvg())}`
export const MenuBackdrop = () => (
-
- {PLACEMENTS.map((placement, index) => {
- const Icon = ICONS[index % ICONS.length]
- const color = ICON_COLORS[index % ICON_COLORS.length]
- return (
-
-
-
- )
- })}
-
+
)