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 `${instances}` +} + +const TILE_DATA_URL = `data:image/svg+xml,${encodeURIComponent(buildTileSvg())}` export const MenuBackdrop = () => ( - +