Remove the food-icon backdrop, back to a plain empty state
Drop MenuBackdrop entirely and put the icon, title, text, and CTA directly back on the empty state's own solid background — no separate overlay card needed once there's no pattern behind it to cover up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvYdJ3os7tumimsLdLu2j9
This commit is contained in:
@@ -1,11 +0,0 @@
|
|||||||
.backdrop {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
z-index: -1;
|
|
||||||
overflow: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
filter: blur(3px) grayscale(1);
|
|
||||||
opacity: 0.6;
|
|
||||||
background-repeat: repeat;
|
|
||||||
background-size: 340px 340px;
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import styles from './MenuBackdrop.module.scss'
|
|
||||||
|
|
||||||
// Rendered once as a tiny repeating SVG tile (not as individual DOM icons)
|
|
||||||
// set as a CSS background-image, rather than one element per icon.
|
|
||||||
const pizza = (color: string) => `
|
|
||||||
<path d="M12 3 3 20h18L12 3Z" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
<path d="M4.5 17.5c2.5-1.5 4.5-1.5 7.5-.5s5 1 7.5-.5" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" />
|
|
||||||
<circle cx="10" cy="14" r="0.9" fill="${color}" />
|
|
||||||
<circle cx="14.5" cy="15.5" r="0.9" fill="${color}" />
|
|
||||||
`
|
|
||||||
|
|
||||||
const burger = (color: string) => `
|
|
||||||
<path d="M4 10c0-3 3.5-5 8-5s8 2 8 5" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
<path d="M3 10.5h18" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
<path d="M4 13.5h16" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
<path d="M3.5 16.5h17" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
<path d="M4 19c0 1 .8 1.5 2.2 1.5h11.6c1.4 0 2.2-.5 2.2-1.5" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
`
|
|
||||||
|
|
||||||
const fries = (color: string) => `
|
|
||||||
<path d="M6 9 5 21h14l-1-12Z" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
<path d="M9 4.5v6M12 3.5v7M15 4.5v6" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
`
|
|
||||||
|
|
||||||
const ICON_VARIANTS = [pizza, burger, fries]
|
|
||||||
const ICON_COLORS = [
|
|
||||||
'#e2725b', // tomato
|
|
||||||
'#d9a441', // crust
|
|
||||||
'#e8c468', // cheese
|
|
||||||
'#7a9d6e', // basil
|
|
||||||
'#a5678f', // berry
|
|
||||||
'#8a5a44', // cocoa
|
|
||||||
]
|
|
||||||
|
|
||||||
// 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 `<g transform="${transform}">${icon}</g>`
|
|
||||||
},
|
|
||||||
).join('')
|
|
||||||
|
|
||||||
return `<svg xmlns="http://www.w3.org/2000/svg" width="${TILE_SIZE}" height="${TILE_SIZE}" viewBox="0 0 ${TILE_SIZE} ${TILE_SIZE}">${instances}</svg>`
|
|
||||||
}
|
|
||||||
|
|
||||||
const TILE_DATA_URL = `data:image/svg+xml,${encodeURIComponent(buildTileSvg())}`
|
|
||||||
|
|
||||||
export const MenuBackdrop = () => (
|
|
||||||
<div
|
|
||||||
className={styles.backdrop}
|
|
||||||
aria-hidden="true"
|
|
||||||
style={{ backgroundImage: `url("${TILE_DATA_URL}")` }}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
@@ -22,8 +22,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
position: relative;
|
|
||||||
z-index: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -33,15 +31,12 @@
|
|||||||
border: 1px dashed $color-border;
|
border: 1px dashed $color-border;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
background: $color-surface;
|
background: $color-surface;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
animation: empty-state-enter 0.32s cubic-bezier(0.2, 0, 0, 1) both;
|
animation: empty-state-enter 0.32s cubic-bezier(0.2, 0, 0, 1) both;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct child only — a plain descendant selector would also catch the
|
svg {
|
||||||
// backdrop's own icons (see MenuBackdrop), overriding their per-icon color.
|
|
||||||
> svg {
|
|
||||||
width: 2.5rem;
|
width: 2.5rem;
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
margin-bottom: $space-2;
|
margin-bottom: $space-2;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
import { MenuBackdrop } from '#/components/ordering/MenuBackdrop.tsx'
|
|
||||||
import type { CustomerOrderSummary } from '#/domain/ordering.ts'
|
import type { CustomerOrderSummary } from '#/domain/ordering.ts'
|
||||||
import { formatPrice } from '#/lib/format.ts'
|
import { formatPrice } from '#/lib/format.ts'
|
||||||
|
|
||||||
@@ -65,7 +64,6 @@ export const OrdersListView = ({ orders }: OrdersListViewProps) => {
|
|||||||
<h1 className={styles.heading}>Your orders</h1>
|
<h1 className={styles.heading}>Your orders</h1>
|
||||||
{displayedOrders.length === 0 ? (
|
{displayedOrders.length === 0 ? (
|
||||||
<div className={styles.empty}>
|
<div className={styles.empty}>
|
||||||
<MenuBackdrop />
|
|
||||||
<OrdersIcon />
|
<OrdersIcon />
|
||||||
<p className={styles.emptyTitle}>No orders yet</p>
|
<p className={styles.emptyTitle}>No orders yet</p>
|
||||||
<p className={styles.emptyText}>
|
<p className={styles.emptyText}>
|
||||||
|
|||||||
@@ -20,8 +20,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
position: relative;
|
|
||||||
z-index: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -31,11 +29,8 @@
|
|||||||
border: 1px dashed $color-border;
|
border: 1px dashed $color-border;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
background: $color-surface;
|
background: $color-surface;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
// Direct child only — a plain descendant selector would also catch the
|
svg {
|
||||||
// backdrop's own icons (see MenuBackdrop), overriding their per-icon color.
|
|
||||||
> svg {
|
|
||||||
width: 2.5rem;
|
width: 2.5rem;
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
margin-bottom: $space-2;
|
margin-bottom: $space-2;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { AnimatePresence, motion, useReducedMotion } from 'framer-motion'
|
|||||||
|
|
||||||
import { BasketIcon } from '#/components/basket/BasketLink.tsx'
|
import { BasketIcon } from '#/components/basket/BasketLink.tsx'
|
||||||
import { CheckoutSection } from '#/components/ordering/CheckoutSection.tsx'
|
import { CheckoutSection } from '#/components/ordering/CheckoutSection.tsx'
|
||||||
import { MenuBackdrop } from '#/components/ordering/MenuBackdrop.tsx'
|
|
||||||
import { AnimatedNumber } from '#/components/ui/AnimatedNumber.tsx'
|
import { AnimatedNumber } from '#/components/ui/AnimatedNumber.tsx'
|
||||||
import type { MenuItemWithRecipe } from '#/domain/ordering.ts'
|
import type { MenuItemWithRecipe } from '#/domain/ordering.ts'
|
||||||
import { useBasket } from '#/lib/basket.tsx'
|
import { useBasket } from '#/lib/basket.tsx'
|
||||||
@@ -72,7 +71,6 @@ const BasketPage = () => {
|
|||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
transition={transition}
|
transition={transition}
|
||||||
>
|
>
|
||||||
<MenuBackdrop />
|
|
||||||
<BasketIcon />
|
<BasketIcon />
|
||||||
<p className={styles.emptyTitle}>Your basket is empty</p>
|
<p className={styles.emptyTitle}>Your basket is empty</p>
|
||||||
<p className={styles.emptyText}>
|
<p className={styles.emptyText}>
|
||||||
|
|||||||
Reference in New Issue
Block a user