diff --git a/public/icons/icon-192.png b/public/icons/icon-192.png new file mode 100644 index 0000000..2941da4 Binary files /dev/null and b/public/icons/icon-192.png differ diff --git a/public/icons/icon-512.png b/public/icons/icon-512.png new file mode 100644 index 0000000..adff547 Binary files /dev/null and b/public/icons/icon-512.png differ diff --git a/public/icons/icon.svg b/public/icons/icon.svg new file mode 100644 index 0000000..2e8d7f0 --- /dev/null +++ b/public/icons/icon.svg @@ -0,0 +1 @@ + diff --git a/public/images/garden.jpg b/public/images/garden.jpg new file mode 100644 index 0000000..e6f7daf Binary files /dev/null and b/public/images/garden.jpg differ diff --git a/public/images/kitchen.jpg b/public/images/kitchen.jpg new file mode 100644 index 0000000..54cacb4 Binary files /dev/null and b/public/images/kitchen.jpg differ diff --git a/public/images/soup.jpg b/public/images/soup.jpg new file mode 100644 index 0000000..6abd3a4 Binary files /dev/null and b/public/images/soup.jpg differ diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..d92451f --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,21 @@ +{ + "id": "/", + "name": "HamHam – Dein persönliches Kochbuch", + "short_name": "HamHam", + "description": "Lieblingsrezepte sammeln und entspannt kochen.", + "lang": "de", + "start_url": "/", + "scope": "/", + "display": "standalone", + "background_color": "#faf8f4", + "theme_color": "#365747", + "icons": [ + { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" }, + { + "src": "/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ] +} diff --git a/public/offline.html b/public/offline.html new file mode 100644 index 0000000..ec4c0ec --- /dev/null +++ b/public/offline.html @@ -0,0 +1,70 @@ + + + HamHam · Offline +
+

HAMHAM · DEIN KOCHBUCH

+

Ein Moment ohne Netz.

+

+ Diese Seite ist noch nicht auf deinem Gerät gespeichert. Bereits online geöffnete Rezept- und + Kochseiten findest du hier. Für neue Rezepte und die Verwaltung brauchst du eine Verbindung. +

+ + +
+ + diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..58d18e5 --- /dev/null +++ b/public/sw.js @@ -0,0 +1,94 @@ +const CACHE = 'hamham-public-v1'; +async function savePage(address) { + const url = new URL(address, self.location.origin); + if (url.origin !== self.location.origin) return; + const match = /^\/rezepte\/([a-f0-9-]{36})(?:\/kochen)?$/.exec(url.pathname); + if (match) { + const recipe = await fetch(`/api/recipes/${match[1]}`).then((r) => (r.ok ? r.json() : null)); + if (!recipe?.published) return; + } else if (!['/', '/favoriten', '/einkaufsliste'].includes(url.pathname)) return; + const response = await fetch(url.href, { headers: { Accept: 'text/html' } }); + if (!response.ok || !response.headers.get('content-type')?.includes('text/html')) return; + const cache = await caches.open(CACHE), + html = await response.clone().text(); + await cache.put(url.href, response); + const paths = [ + ...new Set( + Array.from( + html.matchAll(/(?:src|href)="(\/(?:_next\/static\/|images\/|icons\/|media\/)[^"<>]+)"/g), + (m) => m[1].replaceAll('&', '&'), + ), + ), + ]; + await Promise.all( + paths.map(async (path) => { + try { + const r = await fetch(path); + if (r.ok) await cache.put(path, r); + } catch {} + }), + ); +} +self.addEventListener('message', (event) => { + if (event.data?.type === 'CACHE_PAGE' && typeof event.data.url === 'string') + event.waitUntil(savePage(event.data.url).catch(() => {})); +}); +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE).then((cache) => cache.addAll(['/offline.html', '/icons/icon-192.png'])), + ); + self.skipWaiting(); +}); +self.addEventListener('activate', (event) => { + event.waitUntil( + caches + .keys() + .then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))), + ); + self.clients.claim(); +}); +self.addEventListener('fetch', (event) => { + const req = event.request, + url = new URL(req.url); + if ( + req.method !== 'GET' || + url.origin !== self.location.origin || + url.pathname.startsWith('/admin') || + url.pathname.startsWith('/api') || + url.searchParams.has('_rsc') || + req.headers.get('rsc') + ) + return; + const asset = /^\/(?:_next\/static\/|images\/|icons\/|media\/)/.test(url.pathname); + const recipe = /^\/rezepte\/([a-f0-9-]{36})(?:\/kochen)?$/.exec(url.pathname); + if (!asset && req.mode !== 'navigate') return; + event.respondWith( + (async () => { + const cache = await caches.open(CACHE); + if (asset) { + const cached = await cache.match(req); + if (cached) return cached; + } + try { + const response = await fetch(req); + if (response.ok && asset) await cache.put(req, response.clone()); + if (response.ok && recipe) { + // Never persist a draft or an administrator page. Only published recipes. + const data = await fetch(`/api/recipes/${recipe[1]}`) + .then((r) => (r.ok ? r.json() : null)) + .catch(() => null); + if (data?.published) await cache.put(req, response.clone()); + else await cache.delete(req); + } + return response; + } catch { + const saved = + (await cache.match(req)) ?? + (req.mode === 'navigate' ? await cache.match(req, { ignoreSearch: true }) : null); + if (saved) return saved; + if (req.mode === 'navigate') return await cache.match('/offline.html'); + return new Response('', { status: 503 }); + } + })(), + ); +}); diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx new file mode 100644 index 0000000..56978ad --- /dev/null +++ b/src/app/admin/page.tsx @@ -0,0 +1,13 @@ +import { admin } from '@/lib/server'; +import { listRecipes } from '@/lib/repository'; +import { Login, AdminDashboard } from '@/components/admin'; +export const dynamic = 'force-dynamic'; +export const metadata = { title: 'Deine Küche', robots: { index: false, follow: false } }; +export default async function Admin() { + const user = await admin(); + return user ? ( + + ) : ( + + ); +} diff --git a/src/app/admin/rezepte/[id]/page.tsx b/src/app/admin/rezepte/[id]/page.tsx new file mode 100644 index 0000000..309bb79 --- /dev/null +++ b/src/app/admin/rezepte/[id]/page.tsx @@ -0,0 +1,14 @@ +import { redirect, notFound } from 'next/navigation'; +import { admin } from '@/lib/server'; +import { getRecipe } from '@/lib/repository'; +import { RecipeEditor } from '@/components/editor'; +export const dynamic = 'force-dynamic'; +export const metadata = { title: 'Rezepteditor', robots: { index: false, follow: false } }; +export default async function Edit({ params }: { params: Promise<{ id: string }> }) { + if (!(await admin())) redirect('/admin'); + const { id } = await params; + if (id === 'neu') return ; + const recipe = await getRecipe(id, true); + if (!recipe) notFound(); + return ; +} diff --git a/src/app/einkaufsliste/page.tsx b/src/app/einkaufsliste/page.tsx new file mode 100644 index 0000000..b36d4bc --- /dev/null +++ b/src/app/einkaufsliste/page.tsx @@ -0,0 +1,97 @@ +'use client'; +import Link from 'next/link'; +import { Check, Copy, Printer, ShoppingBasket, Trash2, ArrowRight } from 'lucide-react'; +import { useLocal, toast } from '@/lib/client'; +import { formatAmount, type ShoppingItem } from '@/lib/calculator'; +export default function Shopping() { + const [items, setItems] = useLocal('hamham:shopping', []); + const label = (i: ShoppingItem) => + `${formatAmount(i.amount)} ${i.unit} ${i.name}${i.note ? ` (${i.note})` : ''}`.trim(); + return ( +
+
+ GUT VORBEREITET SCHMECKT’S BESSER +

+ Deine Einkaufsliste. +

+

Alles an einem Ort. Passende Mengen werden automatisch zusammengezählt.

+
+ {items.length ? ( + <> +
+ + {items.filter((i) => i.checked).length} von {items.length} erledigt + +
+ + +
+
+
    + {items.map((i) => ( +
  • + + +
  • + ))} +
+ + + ) : ( +
+ +

Platz für frische Ideen.

+

+ Füge Zutaten direkt aus einem Rezept hinzu. +
+ Deine eingestellten Portionen werden übernommen. +

+ + Rezepte entdecken + +
+ )} +
+ ); +} diff --git a/src/app/error.tsx b/src/app/error.tsx new file mode 100644 index 0000000..93c54c4 --- /dev/null +++ b/src/app/error.tsx @@ -0,0 +1,14 @@ +'use client'; +export default function ErrorPage({ reset }: { reset: () => void }) { + return ( +
+

Ein kleiner Küchenstopp.

+

+ Die Seite konnte nicht geladen werden. Bitte prüfe die Verbindung und versuche es erneut. +

+ +
+ ); +} diff --git a/src/app/favoriten/page.tsx b/src/app/favoriten/page.tsx new file mode 100644 index 0000000..3390d28 --- /dev/null +++ b/src/app/favoriten/page.tsx @@ -0,0 +1,12 @@ +import { Suspense } from 'react'; +import { Library } from '@/components/library'; +import { listRecipes } from '@/lib/repository'; +export const dynamic = 'force-dynamic'; +export const metadata = { title: 'Lieblingsrezepte' }; +export default async function Favorites() { + return ( + + + + ); +} diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..fb4f240 --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,3147 @@ +:root { + --bg: #faf8f4; + --surface: #fffefa; + --surface-2: #f0eee6; + --ink: #253b32; + --muted: #686f63; + --line: #e3e5da; + --green: #365b46; + --green-hover: #274a37; + --soft-green: #e8eddf; + --accent: #ae5135; + --sand: #eee2ce; + --shadow: 0 8px 30px #263b3008; + --radius: 20px; + color-scheme: light; +} +[data-theme='dark'] { + --bg: #19241f; + --surface: #222f28; + --surface-2: #2b3930; + --ink: #eef0e5; + --muted: #afb7a7; + --line: #3d4c40; + --green: #bdd2a8; + --green-hover: #d2e2c0; + --soft-green: #324631; + --accent: #e7a180; + --sand: #4b4434; + --shadow: 0 8px 30px #0002; + color-scheme: dark; +} +* { + box-sizing: border-box; +} +html { + scroll-behavior: smooth; + scroll-padding-top: 110px; +} +body { + margin: 0; + background: var(--bg); + color: var(--ink); + font-family: 'DM Sans', sans-serif; + font-size: 15px; + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} +a { + color: inherit; + text-decoration: none; +} +button, +input, +select, +textarea { + font: inherit; +} +button, +a, +input, +textarea, +select { + -webkit-tap-highlight-color: transparent; +} +button { + cursor: pointer; + border: 0; + color: inherit; + background: none; +} +button:disabled { + opacity: 0.45; + cursor: not-allowed; +} +button:focus-visible, +a:focus-visible, +input:focus-visible, +select:focus-visible, +textarea:focus-visible, +summary:focus-visible { + outline: 3px solid var(--accent); + outline-offset: 4px; +} +button, +a { + touch-action: manipulation; +} +h1, +h2, +h3, +p { + margin-top: 0; +} +h1, +h2, +h3, +.serif { + font-family: 'Fraunces', Georgia, serif; + font-weight: 400; + line-height: 1.15; + letter-spacing: -0.045em; +} +h1 { + font-size: clamp(36px, 4vw, 58px); + margin-bottom: 22px; +} +h2 { + font-size: 32px; + margin-bottom: 18px; +} +h3 { + font-size: 23px; + margin-bottom: 12px; +} +p { + line-height: 1.75; +} +em { + font-style: normal; + color: var(--accent); +} +img { + display: block; + max-width: 100%; + object-fit: cover; +} +svg { + flex-shrink: 0; +} +small, +.small { + font-size: 12px; +} +.muted { + color: var(--muted); +} +.accent { + color: var(--accent); +} +.eyebrow { + font-size: 10px; + letter-spacing: 2px; + font-weight: 700; + display: flex; + align-items: center; + gap: 9px; + line-height: 1.6; +} +.live-dot { + display: inline-block; + width: 6px; + height: 6px; + background: var(--accent); + border-radius: 50%; + flex-shrink: 0; +} +.tiny-dot { + width: 3px; + height: 3px; + background: var(--muted); + border-radius: 100%; +} +.container { + max-width: 1240px; + margin: 0 auto; + padding: 0 40px; +} +.narrow { + max-width: 880px; +} +.main { + min-height: calc(100vh - 200px); +} +.site-header { + height: 88px; + border-bottom: 1px solid var(--line); + background: var(--bg); + position: relative; + z-index: 20; +} +.header-inner { + max-width: 1360px; + margin: auto; + height: 100%; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 48px; +} +.brand { + display: inline-flex; + align-items: center; + gap: 9px; + font-family: 'Fraunces', serif; + font-size: 32px; + font-weight: 500; + letter-spacing: -2px; +} +.brand-dot { + color: var(--accent); + margin-left: -9px; +} +.brand-icon { + display: grid; + place-items: center; + color: var(--green); + margin-right: 2px; +} +.desktop-nav { + display: flex; + align-items: center; + gap: 38px; + height: 100%; + font-size: 13px; + font-weight: 500; +} +.desktop-nav a { + height: 100%; + display: flex; + align-items: center; + position: relative; + color: var(--muted); +} +.desktop-nav a.active { + color: var(--ink); +} +.desktop-nav a.active:after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 3px; + background: var(--green); +} +.header-actions, +.button-row { + display: flex; + align-items: center; + gap: 10px; +} +.icon-button { + width: 42px; + height: 42px; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +.icon-button:hover { + background: var(--surface-2); +} +.bordered { + border: 1px solid var(--line); +} +.button { + min-height: 46px; + border-radius: 9px; + padding: 12px 20px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 10px; + font-size: 12px; + line-height: 1.5; + font-weight: 600; + transition: + background 0.18s, + transform 0.18s; + white-space: nowrap; +} +.button:hover { + transform: translateY(-1px); +} +.primary { + background: var(--green); + color: var(--bg); +} +.primary:hover { + background: var(--green-hover); +} +.secondary { + background: var(--surface); + border: 1px solid var(--line); +} +.secondary:hover { + background: var(--surface-2); +} +.text-button { + display: inline-flex; + gap: 8px; + align-items: center; + font-size: 13px; + font-weight: 600; + color: var(--green); + padding: 8px 0; +} +.full { + width: 100%; +} +.selected { + color: var(--accent); +} +.site-footer { + max-width: 1240px; + margin: 70px auto 0; + padding: 30px 40px; + border-top: 1px solid var(--line); + display: flex; + justify-content: space-between; + gap: 20px; + font-size: 11px; + color: var(--muted); +} +.site-footer .serif { + font-size: 22px; + color: var(--ink); +} +.mobile-nav { + display: none; +} +.toast { + position: fixed; + bottom: 30px; + left: 50%; + transform: translateX(-50%); + background: var(--ink); + color: var(--bg); + padding: 15px 18px; + border-radius: 12px; + box-shadow: 0 10px 40px #0002; + z-index: 200; + display: flex; + gap: 20px; + align-items: center; + max-width: calc(100% - 32px); + font-size: 13px; +} +.toast button { + display: flex; + color: inherit; +} +.offline-banner { + padding: 10px 20px; + background: var(--sand); + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 12px; +} +.back-link { + display: inline-flex; + align-items: center; + gap: 7px; + color: var(--muted); + font-size: 12px; + margin: 27px 0; +} +.page-heading { + padding: 65px 0 32px; +} +.page-heading .eyebrow { + margin-bottom: 18px; +} +.page-heading p { + color: var(--muted); + max-width: 620px; +} +.empty-state { + padding: 70px 25px; + text-align: center; + background: var(--surface); + border: 1px dashed var(--line); + border-radius: var(--radius); + margin: 28px auto; + max-width: 100%; +} +.empty-state > svg { + margin: 0 auto 22px; + color: var(--green); +} +.empty-state p { + color: var(--muted); + max-width: 530px; + margin: 0 auto 25px; +} +.empty-state .eyebrow { + justify-content: center; +} +.empty-state h1 { + font-size: 38px; +} +.empty-state h3 { + font-size: 28px; +} +.notice { + padding: 15px 20px; + background: var(--sand); + border-radius: 12px; + font-size: 13px; + margin: 20px 0; +} +.notice button { + text-decoration: underline; +} +.error { + white-space: pre-wrap; + overflow-wrap: anywhere; + background: #b3433010; + border: 1px solid #b3433040; + border-radius: 10px; + color: #b34330; + padding: 14px; + font: inherit; + font-size: 13px; + margin: 15px 0; +} +[data-theme='dark'] .error { + color: #ffb29e; +} +.danger { + color: var(--accent); +} +.danger-solid { + background: #ad442f; + color: #fff; +} +.skeleton { + background: linear-gradient(90deg, var(--surface-2), var(--surface), var(--surface-2)); + background-size: 200% 100%; + animation: shimmer 1.6s infinite; + border-radius: 20px; +} +.skeleton-hero { + height: 420px; + margin: 35px 0; +} +.skeleton-card { + height: 350px; +} +@keyframes shimmer { + to { + background-position: -200% 0; + } +} +.visually-hidden { + position: absolute !important; + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; +} +/* The cookbook */ +.home-hero { + display: grid; + grid-template-columns: 1.05fr 1fr; + margin-top: 36px; + min-height: 455px; + border-radius: 20px; + overflow: hidden; + background: var(--surface-2); +} +.hero-copy { + padding: 48px 35px 35px 44px; + position: relative; +} +.hero-copy h1 { + font-size: clamp(40px, 4.35vw, 59px); + margin: 24px 0 22px; + line-height: 1.08; + letter-spacing: -2.5px; +} +.hero-copy p { + font-size: 13px; + color: var(--muted); + margin-bottom: 25px; + line-height: 1.8; +} +.hero-copy .button { + padding: 13px 21px; +} +.hero-note { + display: flex; + gap: 10px; + align-items: center; + font-size: 10px; + color: var(--muted); + margin-top: 27px; +} +.note-icon { + width: 38px; + height: 38px; + border-radius: 50%; + background: var(--soft-green); + color: var(--green); + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +.hero-visual { + position: relative; + min-width: 0; +} +.hero-visual > img { + width: 100%; + height: 100%; + position: absolute; + object-position: center; +} +.hero-image-shade { + position: absolute; + inset: 0; + background: linear-gradient(0deg, #102516b3, transparent 65%); +} +.hero-stamp { + position: absolute; + right: 24px; + top: 26px; + width: 100px; + height: 100px; + border: 1px solid #fff8; + outline: 5px solid #fff2; + border-radius: 50%; + background: #f6f3e9ec; + color: #3e5740; + font-size: 9px; + letter-spacing: 2px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + transform: rotate(11deg); +} +.hero-stamp span { + font-family: 'Fraunces'; + font-size: 20px; + letter-spacing: -1px; +} +.hero-caption { + position: absolute; + bottom: 29px; + left: 33px; + color: #fff9ed; +} +.hero-caption > span { + font-size: 8px; + letter-spacing: 2px; +} +.hero-caption p { + font-family: 'Fraunces'; + font-size: 28px; + line-height: 1.15; + margin: 10px 0 0; + letter-spacing: -1px; +} +.library-section { + padding-top: 56px; +} +.section-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + margin-bottom: 24px; +} +.section-heading h2 { + margin: 7px 0 0; +} +.section-heading h3 { + margin: 0; +} +.section-heading p { + margin-bottom: 0; +} +.count-badge { + display: inline-flex; + align-items: center; + justify-content: center; + vertical-align: middle; + border: 1px solid var(--line); + border-radius: 50%; + font: 11px 'DM Sans'; + width: 28px; + height: 28px; + margin-left: 8px; + color: var(--muted); +} +.handwritten { + font-family: 'Fraunces', serif; + font-size: 14px; + font-style: italic; + color: var(--accent); + transform: rotate(-5deg); + padding: 15px 5px 0; +} +.handwritten span { + display: inline-block; + font-size: 31px; + vertical-align: middle; + padding-left: 8px; +} +.search-row { + display: flex; + gap: 12px; +} +.search-box { + flex: 1; + min-width: 0; + display: flex; + align-items: center; + gap: 11px; + background: var(--surface); + border: 1px solid var(--line); + border-radius: 10px; + padding: 0 16px; + color: var(--muted); + margin: 0 !important; +} +.search-box input { + padding: 12px 0; + border: 0; + background: none; + outline: none; + width: 100%; + font-size: 12px; +} +.search-box button { + display: flex; +} +.search-row > select { + width: 180px; + font-size: 12px; +} +.category-chips { + display: flex; + gap: 8px; + overflow-x: auto; + padding: 23px 0 27px; + scrollbar-width: none; +} +.chip { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 7px; + border: 1px solid var(--line); + border-radius: 30px; + padding: 8px 17px; + font-size: 11px; + white-space: nowrap; + background: transparent; + min-height: 36px; +} +.chip:hover { + background: var(--surface-2); +} +.chip.active { + background: var(--green); + color: var(--bg); + border-color: var(--green); +} +.filter-panel { + display: flex; + gap: 20px; + padding: 18px; + background: var(--surface-2); + margin-top: 14px; + border-radius: 12px; + align-items: flex-end; +} +.filter-panel label { + flex: 1; + margin: 0; +} +.filter-panel .text-button { + margin-bottom: 10px; +} +.recipe-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 25px; +} +.recipe-card { + position: relative; + min-width: 0; + background: var(--surface); + border: 1px solid var(--line); + border-radius: 16px; + overflow: hidden; + box-shadow: var(--shadow); + transition: box-shadow 0.2s; +} +.recipe-card:hover { + box-shadow: 0 10px 35px #273b3014; +} +.card-image { + height: 220px; + display: block; + position: relative; + overflow: hidden; +} +.card-image img { + width: 100%; + height: 100%; + transition: transform 0.6s; +} +.recipe-card:hover .card-image img { + transform: scale(1.035); +} +.card-category { + position: absolute; + bottom: 14px; + left: 16px; + background: #fffcf1ed; + color: #304937; + font-size: 9px; + padding: 5px 10px; + border-radius: 5px; + letter-spacing: 0.3px; +} +.favorite-button { + position: absolute; + right: 14px; + top: 14px; + width: 34px; + height: 34px; + border-radius: 50%; + background: #fffcf1ef; + color: #3d4b3e; + display: grid; + place-items: center; +} +.favorite-button.selected { + color: #b3593e; +} +.card-body { + padding: 20px 22px 14px; +} +.card-meta { + display: flex; + align-items: center; + gap: 10px; + font-size: 10px; + color: var(--muted); + margin-bottom: 10px; +} +.card-meta > span { + display: inline-flex; + align-items: center; + gap: 5px; +} +.card-body h3 { + font-size: 24px; + margin-bottom: 9px; + line-height: 1.2; +} +.card-body > p { + color: var(--muted); + font-size: 11px; + line-height: 1.7; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + height: 38px; + margin: 0 0 13px; +} +.card-bottom { + display: flex; + justify-content: space-between; + align-items: center; + border-top: 1px solid var(--line); + padding-top: 11px; + font-size: 9px; + color: var(--muted); +} +.card-bottom a { + color: var(--green); +} +.recipe-art { + background: radial-gradient(ellipse at 70% 20%, #cfdbc766, transparent 65%), var(--sand); + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 15px; + color: var(--green); +} +.recipe-art span { + font-family: 'Fraunces'; + font-size: 18px; +} +.home-bottom { + margin-top: 48px; + border-radius: 14px; + background: var(--soft-green); + display: flex; + align-items: center; + gap: 20px; + padding: 28px 30px; +} +.home-bottom .note-icon { + background: var(--surface); + width: 55px; + height: 55px; +} +.home-bottom h3 { + font-size: 23px; + margin-bottom: 6px; +} +.home-bottom p { + font-size: 11px; + color: var(--muted); + margin: 0; +} +.home-bottom > a { + margin-left: auto; + font-size: 11px; +} +.search-box { + flex-direction: row; +} +/* Recipe & scaling */ +.recipe-hero { + display: grid; + grid-template-columns: 1.1fr 1fr; + gap: 50px; + align-items: center; + margin: 10px 0 60px; +} +.recipe-intro .eyebrow { + margin-bottom: 20px; +} +.recipe-intro h1 { + font-size: 48px; + line-height: 1.06; + margin-bottom: 18px; +} +.recipe-subtitle { + font-family: 'Fraunces'; + font-size: 23px; + color: var(--accent); + line-height: 1.35; +} +.description { + color: var(--muted); + font-size: 13px; +} +.recipe-stats { + display: flex; + gap: 24px; + padding: 23px 0; + margin-top: 20px; + border-top: 1px solid var(--line); +} +.recipe-stats > div { + display: flex; + flex-direction: column; + gap: 4px; + font-size: 10px; + color: var(--muted); +} +.recipe-stats svg { + color: var(--green); + margin-bottom: 5px; +} +.recipe-stats strong { + font-weight: 500; + color: var(--ink); + font-size: 12px; +} +.recipe-actions { + display: flex; + gap: 10px; + align-items: center; + margin-top: 12px; +} +.recipe-cover { + height: 440px; + border-radius: 20px; + overflow: hidden; + position: relative; +} +.recipe-cover > img { + height: 100%; + width: 100%; +} +.image-label { + position: absolute; + bottom: 18px; + left: 20px; + right: 20px; + background: #fcf9f0e8; + color: #3f5140; + backdrop-filter: blur(8px); + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + border-radius: 7px; + font-size: 10px; +} +.recipe-columns { + display: grid; + grid-template-columns: 350px minmax(0, 1fr); + gap: 65px; + align-items: start; +} +.ingredients-panel { + background: var(--surface); + border: 1px solid var(--line); + border-radius: 18px; + padding: 26px; + position: sticky; + top: 24px; +} +.panel-title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} +.panel-title h2 { + font-size: 28px; + margin: 0; +} +.panel-title .eyebrow { + font-size: 8px; + letter-spacing: 1px; +} +.calculator { + padding: 22px 0 20px; + border-bottom: 1px solid var(--line); +} +.segmented { + display: flex; + padding: 4px; + border-radius: 8px; + background: var(--surface-2); + gap: 4px; +} +.segmented button { + flex: 1; + border-radius: 5px; + padding: 7px 3px; + font-size: 10px; + color: var(--muted); +} +.segmented button.active { + background: var(--surface); + color: var(--ink); + box-shadow: 0 2px 5px #0001; +} +.portion-control { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 12px 10px; +} +.portion-control > button { + width: 34px; + height: 34px; + border: 1px solid var(--line); + border-radius: 50%; + display: grid; + place-items: center; +} +.portion-control label { + display: flex; + flex-direction: column; + align-items: center; + margin: 0; + font-size: 10px; + color: var(--muted); + gap: 0; +} +.portion-control input { + font: 28px 'Fraunces'; + text-align: center; + padding: 0; + width: 90px; + background: none; + border: 0; + color: var(--ink); + -moz-appearance: textfield; +} +.portion-control input::-webkit-inner-spin-button { + -webkit-appearance: none; +} +.reset-link { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + font-size: 9px; + color: var(--muted); + width: 100%; + padding: 7px; +} +.calculator-hint { + display: flex; + gap: 9px; + font-size: 11px; + margin: 15px 0 8px; + color: var(--muted); +} +.ingredient-list { + list-style: none; + margin: 12px 0 23px; + padding: 0; +} +.ingredient-list li { + padding: 12px 0; + border-bottom: 1px solid var(--line); +} +.ingredient-list li:last-child { + border: 0; +} +.ingredient-list h4 { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1px; + margin: 13px 0 14px; + color: var(--muted); +} +.ingredient-line { + display: flex; + align-items: center; + gap: 10px; + min-height: 29px; + flex-wrap: wrap; +} +.ingredient-name { + font-size: 12px; + flex: 1; +} +.ingredient-name small { + display: block; + color: var(--muted); + font-size: 9px; +} +.check-box { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + border: 1px solid var(--line); + border-radius: 5px; + background: var(--surface); + flex-shrink: 0; +} +.is-checked .check-box, +.checked .check-box { + background: var(--green); + color: var(--bg); + border-color: var(--green); +} +.is-checked .ingredient-name, +.is-checked .shopping-item > span:nth-child(2) { + text-decoration: line-through; + color: var(--muted); +} +.amount-button { + font-size: 13px; + border-bottom: 1px dashed var(--muted); + padding: 3px 0; + color: var(--green); + white-space: nowrap; +} +.amount-button span { + font-size: 10px; +} +.amount-unset { + font-size: 9px; + color: var(--muted); + max-width: 90px; + text-align: right; +} +.amount-edit { + display: flex; + flex-wrap: wrap; + gap: 4px; + width: 100%; +} +.amount-edit input { + width: 80px; + padding: 4px 8px; +} +.amount-edit select { + width: 70px; + padding: 4px; +} +.amount-edit small { + color: var(--accent); + width: 100%; +} +.ingredients-panel > .small { + text-align: center; + font-size: 8px; + margin: 12px 0 0; +} +.ingredients-panel > .button { + font-size: 10px; + padding: 12px 8px; +} +.preparation .section-heading h2 { + font-size: 34px; +} +.preparation .section-heading .eyebrow { + font-size: 8px; + letter-spacing: 1px; +} +.time-breakdown { + display: flex; + gap: 25px; + padding: 17px 0; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); + font-size: 10px; + color: var(--muted); + margin-bottom: 30px; +} +.time-breakdown strong { + display: block; + color: var(--ink); + font-size: 12px; + font-weight: 500; +} +.steps-list { + list-style: none; + padding: 0; + margin: 0; +} +.steps-list > li { + display: flex; + gap: 22px; + margin: 0 0 33px; +} +.step-number { + font-family: 'Fraunces'; + color: var(--accent); + font-size: 28px; + line-height: 1.2; + min-width: 36px; +} +.steps-list h3 { + font-size: 24px; + margin: 2px 0 12px; +} +.steps-list p { + font-size: 13px; + color: var(--muted); + white-space: pre-line; + margin-bottom: 10px; +} +.timer-tag { + display: inline-flex; + gap: 5px; + align-items: center; + font-size: 10px; + background: var(--surface-2); + padding: 5px 9px; + border-radius: 5px; + margin: 0 7px 5px 0; +} +.tip-card { + display: flex; + gap: 16px; + background: var(--soft-green); + border-radius: 14px; + padding: 25px; + margin-top: 40px; +} +.tip-card svg { + color: var(--green); +} +.tip-card h3 { + font-size: 23px; +} +.tip-card p, +.storage-card p { + font-size: 12px; + white-space: pre-line; + color: var(--muted); + margin: 0; +} +.storage-card { + padding: 30px 0; +} +.storage-card h3 { + font-size: 22px; +} +.nutrition { + padding: 20px 0; + border-top: 1px solid var(--line); +} +.nutrition h3 { + font-size: 21px; +} +.nutrition > div { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 15px; +} +.nutrition span { + display: flex; + flex-direction: column; + font-size: 10px; + color: var(--muted); +} +.nutrition strong { + color: var(--ink); + font-size: 15px; + font-weight: 500; +} +.tag-list { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-top: 25px; +} +.image-gallery { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; + margin: 22px 0; +} +.image-gallery img { + width: 100%; + height: 150px; + border-radius: 10px; +} +.related { + margin-top: 75px; +} +.related h2 { + margin: 12px 0 28px; +} +.mobile-cook-cta { + display: none; +} +/* Hands-free-ish, kitchen-friendly focus */ +.cooking-shell { + max-width: 1000px; + padding: 0 45px; + margin: auto; + min-height: 100dvh; + display: flex; + flex-direction: column; +} +.cooking-header { + display: flex; + align-items: center; + gap: 30px; + justify-content: space-between; + height: 90px; + border-bottom: 1px solid var(--line); +} +.cooking-recipe-title { + color: var(--muted); + font-size: 12px; + text-align: center; + flex: 1; +} +.cooking-header .brand { + font-size: 27px; +} +.cooking-tools { + display: flex; + align-items: center; + justify-content: space-between; + padding: 28px 0; +} +.cooking-tools > span { + font-size: 9px; + letter-spacing: 2px; + display: flex; + gap: 8px; + align-items: center; +} +.cooking-tools > div { + display: flex; + gap: 16px; +} +.cooking-tools button { + font-size: 11px; + display: flex; + align-items: center; + gap: 6px; + color: var(--muted); +} +.cooking-tools button.active { + color: var(--green); +} +.cooking-progress > div:first-child { + display: flex; + justify-content: space-between; + font-size: 12px; + color: var(--muted); + margin-bottom: 12px; +} +.cooking-progress strong { + color: var(--ink); +} +.progress-track { + height: 5px; + border-radius: 8px; + background: var(--line); + overflow: hidden; +} +.progress-track span { + display: block; + height: 100%; + background: var(--green); + transition: width 0.3s; +} +.cooking-step { + padding: 52px 0 25px; +} +.cooking-step h1 { + font-size: 53px; + margin: 15px 0 26px; +} +.cooking-instruction { + font-size: 25px; + line-height: 1.65; + white-space: pre-line; + max-width: 850px; + color: var(--ink); + letter-spacing: -0.3px; +} +.step-ingredients { + display: flex; + gap: 10px; + flex-wrap: wrap; + margin-top: 30px; +} +.step-ingredients button { + display: flex; + align-items: center; + gap: 10px; + border: 1px solid var(--line); + background: var(--surface); + border-radius: 8px; + padding: 12px 16px; + min-height: 48px; + font-size: 12px; +} +.step-ingredients button.checked { + text-decoration: line-through; + color: var(--muted); +} +.timer-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 15px; + padding: 5px 0 15px; +} +.timer-card { + display: flex; + align-items: center; + gap: 14px; + background: var(--soft-green); + border: 1px solid var(--line); + border-radius: 14px; + padding: 20px; +} +.timer-card > div { + flex: 1; + display: flex; + flex-direction: column; +} +.timer-card > div > span { + font-size: 11px; + color: var(--muted); +} +.timer-card strong { + font-family: 'Fraunces'; + font-size: 32px; + font-weight: 400; + font-variant-numeric: tabular-nums; + line-height: 1.3; +} +.timer-card .icon-button { + background: var(--surface); + width: 37px; + height: 37px; +} +.timer-done { + border-color: var(--accent); + background: var(--sand); +} +.cooking-bottom-actions { + display: flex; + justify-content: space-between; + align-items: center; + gap: 15px; + padding: 15px 0 30px; +} +.cooking-bottom-actions .small { + font-size: 10px; +} +.cooking-all-ingredients { + padding: 20px 30px; + background: var(--surface); + border-radius: 12px; + font-size: 14px; +} +.cooking-navigation { + display: flex; + justify-content: space-between; + align-items: center; + gap: 18px; + padding: 25px 0 30px; + margin-top: auto; + border-top: 1px solid var(--line); + position: sticky; + bottom: 0; + background: var(--bg); + z-index: 4; +} +.cooking-navigation > .button { + min-height: 58px; + min-width: 160px; + font-size: 14px; +} +.cooking-navigation > span { + font-size: 11px; + color: var(--muted); + letter-spacing: 2px; +} +.cooking-complete { + text-align: center; + padding: 60px 0; +} +.completion-icon { + background: var(--soft-green); + width: 110px; + height: 110px; + display: grid; + place-items: center; + border-radius: 50%; + margin: 0 auto 30px; + color: var(--green); +} +.cooking-complete .eyebrow { + justify-content: center; + margin-bottom: 20px; +} +.cooking-complete h1 { + font-size: 70px; +} +.cooking-complete .button-row { + justify-content: center; + margin-top: 30px; +} +.cooking-complete p { + font-size: 19px; +} +.cooking-complete p.muted { + font-size: 12px; +} +/* Lists & administration */ +label { + display: flex; + flex-direction: column; + gap: 7px; + font-size: 12px; + font-weight: 500; + margin-bottom: 20px; +} +input, +select, +textarea { + border: 1px solid var(--line); + background: var(--surface); + border-radius: 8px; + padding: 11px 12px; + color: var(--ink); + min-width: 0; + width: 100%; + font-size: 13px; +} +input::placeholder, +textarea::placeholder { + color: var(--muted); + opacity: 0.7; +} +textarea { + resize: vertical; + line-height: 1.65; +} +input[type='checkbox'] { + width: 17px; + height: 17px; + accent-color: var(--green); + flex-shrink: 0; +} +.checkbox-label { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; + font-size: 12px; +} +.checkbox-label small { + display: block; + color: var(--muted); + font-weight: 400; + margin-top: 2px; +} +.shopping-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24px; + font-size: 12px; + color: var(--muted); +} +.shopping-list { + padding: 0; + list-style: none; + border: 1px solid var(--line); + border-radius: 18px; + background: var(--surface); + overflow: hidden; + margin: 0 0 20px; +} +.shopping-list > li { + display: flex; + border-bottom: 1px solid var(--line); + padding-right: 14px; +} +.shopping-list > li:last-child { + border: 0; +} +.shopping-item { + display: flex; + flex: 1; + align-items: center; + gap: 18px; + min-height: 75px; + text-align: left; + padding: 18px 24px; +} +.shopping-item > span:nth-child(2) { + flex: 1; +} +.shopping-item small { + display: block; + font-size: 10px; + color: var(--muted); +} +.shopping-item strong { + font-size: 14px; + font-weight: 500; + white-space: nowrap; +} +.shopping-list .icon-button { + align-self: center; +} +.login-layout { + max-width: 1050px; + margin: 60px auto 0; + padding: 20px 40px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 90px; + align-items: center; +} +.login-story h1 { + font-size: 67px; + line-height: 1.04; + margin: 28px 0; +} +.login-story p { + color: var(--muted); + font-size: 13px; +} +.login-card { + background: var(--surface); + padding: 35px; + border: 1px solid var(--line); + border-radius: 20px; + box-shadow: var(--shadow); +} +.login-card h2 { + font-size: 32px; + margin: 22px 0 10px; +} +.login-card > p { + font-size: 12px; + margin-bottom: 25px; +} +.login-card > .small { + font-size: 10px; + line-height: 1.7; + margin: 22px 0 0; +} +.admin-page { + padding-top: 48px; +} +.admin-heading, +.editor-heading { + display: flex; + justify-content: space-between; + align-items: center; + gap: 20px; + margin-bottom: 35px; +} +.admin-heading h1 { + margin: 12px 0; + font-size: 48px; +} +.admin-heading p { + margin: 0; + font-size: 13px; +} +.admin-tabs { + display: flex; + gap: 30px; + border-bottom: 1px solid var(--line); + margin-bottom: 32px; + overflow-x: auto; + scrollbar-width: none; +} +.admin-tabs button { + display: flex; + align-items: center; + gap: 7px; + font-size: 12px; + white-space: nowrap; + padding: 15px 1px; + border-bottom: 2px solid transparent; + color: var(--muted); +} +.admin-tabs button.active { + border-color: var(--green); + color: var(--ink); +} +.admin-recipe-list { + border: 1px solid var(--line); + border-radius: 16px; + overflow: hidden; + background: var(--surface); +} +.admin-recipe-list article { + display: flex; + gap: 20px; + align-items: center; + padding: 20px; + border-bottom: 1px solid var(--line); +} +.admin-recipe-list article:last-child { + border: 0; +} +.admin-thumb { + width: 85px; + height: 85px; + background: var(--surface-2); + border-radius: 10px; + display: grid; + place-items: center; + flex-shrink: 0; + overflow: hidden; +} +.admin-thumb img { + width: 100%; + height: 100%; +} +.admin-recipe-name { + flex: 1; + min-width: 0; +} +.admin-recipe-name h3 { + font-size: 23px; + margin-bottom: 9px; +} +.admin-recipe-name small { + font-size: 10px; + color: var(--muted); + margin-left: 12px; +} +.status-pill { + display: inline-flex; + border-radius: 5px; + padding: 3px 8px; + font-size: 9px; + background: var(--sand); + color: var(--ink); +} +.status-pill.published { + background: var(--soft-green); + color: var(--green); +} +.dialog-backdrop { + position: fixed; + inset: 0; + background: #101b18aa; + backdrop-filter: blur(4px); + display: grid; + place-items: center; + padding: 24px; + z-index: 100; +} +.dialog { + padding: 32px; + max-width: 480px; + width: 100%; + background: var(--surface); + border-radius: 20px; + box-shadow: 0 20px 80px #0003; +} +.dialog h2 { + font-size: 30px; +} +.dialog p { + color: var(--muted); + font-size: 14px; +} +.dialog .button-row { + justify-content: flex-end; + margin-top: 25px; +} +.panel { + padding: 28px; + border: 1px solid var(--line); + border-radius: 16px; + background: var(--surface); +} +.settings-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 25px; + align-items: start; +} +.settings-grid h2 { + font-size: 29px; +} +.import-controls { + display: grid; + grid-template-columns: 220px 1fr; + align-items: center; + gap: 30px; + margin: 30px 0; +} +.import-controls label { + margin: 0; +} +.upload-area { + border: 1px dashed var(--muted); + padding: 25px; + border-radius: 14px; + align-items: center; + font-size: 12px; +} +.upload-area input { + border: 0; + background: none; + max-width: 300px; + padding: 5px; +} +.code-input { + font-family: ui-monospace, monospace; + font-size: 12px; + background: var(--surface-2); +} +.import-preview { + margin-top: 45px; +} +.import-preview .panel { + margin: 20px 0; +} +.preview-columns { + display: grid; + grid-template-columns: 1fr 1.3fr; + gap: 20px; + font-size: 12px; + color: var(--muted); +} +.preview-columns li { + margin-bottom: 8px; +} +.media-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; +} +.media-grid .panel { + padding: 14px; +} +.media-grid img { + width: 100%; + height: 200px; + border-radius: 10px; + margin-bottom: 10px; +} +.media-grid .icon-button { + float: right; +} +.editor-heading h1 { + font-size: 39px; + margin: 10px 0; +} +.save-status { + display: flex; + align-items: center; + gap: 7px; + font-size: 10px; + color: var(--muted); +} +.editor-layout.with-preview { + display: grid; + grid-template-columns: 1.6fr 1fr; + gap: 25px; + align-items: start; +} +.editor-panel { + margin-bottom: 30px; +} +.editor-panel h2 { + font-size: 29px; + margin-bottom: 25px; +} +.editor-panel > p { + font-size: 12px; +} +.form-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; +} +.form-grid.two { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} +.image-upload { + position: relative; + border: 1px dashed var(--line); + border-radius: 14px; + background: var(--surface-2); + padding: 25px; + text-align: center; + margin: 15px 0 22px; + display: flex; + flex-direction: column; + align-items: center; +} +.image-upload > img { + width: 100%; + height: 250px; + border-radius: 10px; + margin-bottom: 15px; +} +.image-upload > .icon-button { + position: absolute; + right: 35px; + top: 35px; + background: var(--surface); +} +.image-upload h3 { + margin: 12px 0 6px; +} +.image-upload p { + font-size: 11px; + color: var(--muted); +} +.image-upload label { + margin: 0; +} +.editor-ingredient { + display: flex; + gap: 12px; + border: 1px solid var(--line); + background: var(--bg); + padding: 17px; + border-radius: 12px; + margin-bottom: 14px; + align-items: flex-start; +} +.reorder { + display: flex; + flex-direction: column; + gap: 4px; + align-items: center; + flex-shrink: 0; + color: var(--muted); +} +.reorder button { + padding: 3px; + display: flex; +} +.drag-handle { + cursor: grab; + display: flex; +} +.ingredient-fields { + flex: 1; + min-width: 0; +} +.ingredient-main-fields { + display: grid; + grid-template-columns: 80px 90px minmax(100px, 1fr); + gap: 10px; +} +.ingredient-main-fields label { + font-size: 10px; + margin-bottom: 10px; +} +.ingredient-secondary-fields { + display: flex; + gap: 8px; + align-items: center; + margin-bottom: 8px; +} +.ingredient-secondary-fields > input { + font-size: 10px; + padding: 7px; + flex: 1; + min-width: 50px; +} +.ingredient-secondary-fields .checkbox-label { + font-size: 10px; + margin: 0; +} +.ingredient-fields > small { + font-size: 8px; + overflow-wrap: anywhere; +} +.editor-ingredient > .icon-button { + width: 25px; + height: 30px; + margin-top: 24px; +} +.editor-step { + border: 1px solid var(--line); + border-radius: 14px; + background: var(--bg); + padding: 22px; + margin: 20px 0; +} +.editor-step .reorder { + flex-direction: row; +} +.editor-step .section-heading { + margin-bottom: 18px; +} +.editor-step h3 { + font-size: 24px; +} +.reference-picker { + display: flex; + gap: 7px; + flex-wrap: wrap; + align-items: center; + margin: 15px 0; +} +.reference-picker .chip { + padding: 5px 10px; + font-size: 9px; + min-height: 30px; +} +.reference-picker .checkbox-label { + margin: 0 10px 8px 0; +} +.editor-step details { + font-size: 11px; + color: var(--muted); + margin: 15px 0; +} +.editor-step summary { + cursor: pointer; +} +.editor-timers { + border-top: 1px solid var(--line); + padding-top: 10px; +} +.timer-input { + display: flex; + gap: 12px; + align-items: center; +} +.timer-input label { + flex: 1; +} +.timer-input .icon-button { + margin-top: 4px; +} +.editor-preview { + position: sticky; + top: 20px; + font-size: 12px; + overflow-wrap: anywhere; +} +.editor-preview > img { + border-radius: 12px; + width: 100%; + height: 190px; + margin: 20px 0; +} +.editor-preview h2 { + font-size: 30px; + margin-top: 20px; +} +.editor-preview h3 { + font-size: 21px; +} +.editor-preview li { + margin: 10px 0; + color: var(--muted); +} +.editor-preview ul, +.editor-preview ol { + padding-left: 20px; +} +@media (max-width: 700px) { + .home-bottom > div:not(.note-icon) { + flex: 1 1 calc(100% - 65px); + } + .home-bottom > a { + flex-basis: calc(100% - 52px); + } +} +/* Tablet */ +@media (max-width: 1050px) { + .container { + padding: 0 28px; + } + .header-inner { + padding: 0 28px; + } + .home-hero { + min-height: 420px; + } + .hero-copy { + padding: 38px 28px; + } + .hero-copy h1 { + font-size: 46px; + } + .hero-copy .eyebrow { + font-size: 8px; + letter-spacing: 1.4px; + } + .hero-note { + font-size: 9px; + } + .card-image { + height: 190px; + } + .card-body { + padding: 17px; + } + .card-body h3 { + font-size: 21px; + } + .recipe-grid { + gap: 18px; + } + .recipe-columns { + grid-template-columns: 310px minmax(0, 1fr); + gap: 35px; + } + .ingredients-panel { + padding: 21px; + } + .recipe-hero { + gap: 30px; + } + .recipe-intro h1 { + font-size: 39px; + } + .recipe-cover { + height: 400px; + } + .recipe-stats { + gap: 18px; + } + .preparation .section-heading { + display: block; + } + .preparation .section-heading .eyebrow { + margin-top: 12px; + } + .login-layout { + gap: 40px; + } + .login-story h1 { + font-size: 56px; + } + .editor-layout.with-preview { + grid-template-columns: 1fr; + } + .editor-preview { + position: static; + } + .editor-heading { + align-items: flex-start; + } + .editor-heading h1 { + font-size: 32px; + } + .editor-heading > .button-row { + flex-direction: column; + } + .editor-heading .button { + width: 100%; + } + .admin-page { + padding-top: 35px; + } + .recipe-stats > div > span { + font-size: 8px; + } +} +/* Mobile is a different rhythm, not a squeezed desktop */ +@media (max-width: 700px) { + body { + font-size: 14px; + } + .container { + padding: 0 20px; + } + .site-header { + height: 72px; + } + .header-inner { + padding: 0 20px; + } + .brand { + font-size: 29px; + } + .desktop-nav { + display: none; + } + .header-actions { + gap: 3px; + } + .header-actions .icon-button { + width: 36px; + height: 36px; + } + .mobile-nav { + display: flex; + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: 9px 18px calc(10px + env(safe-area-inset-bottom)); + background: var(--surface); + border-top: 1px solid var(--line); + z-index: 40; + justify-content: space-around; + } + .mobile-nav a { + display: flex; + align-items: center; + flex-direction: column; + gap: 3px; + color: var(--muted); + font-size: 9px; + min-width: 65px; + } + .mobile-nav a.active { + color: var(--green); + } + .main { + padding-bottom: 15px; + } + .site-footer { + padding: 25px 20px 100px; + margin-top: 45px; + flex-direction: column; + gap: 10px; + text-align: center; + } + .site-footer .serif { + font-size: 21px; + } + .site-footer > span { + font-size: 10px; + } + .toast { + bottom: 90px; + width: max-content; + font-size: 12px; + } + .home-hero { + grid-template-columns: 1fr; + margin-top: 20px; + border-radius: 16px; + min-height: 0; + } + .hero-copy { + padding: 30px 25px; + } + .hero-copy h1 { + font-size: 44px; + letter-spacing: -2px; + margin: 20px 0 17px; + } + .hero-copy p { + font-size: 12px; + margin-bottom: 20px; + } + .hero-copy .eyebrow { + font-size: 8px; + letter-spacing: 1.5px; + } + .hero-copy .button { + font-size: 11px; + padding: 12px 17px; + } + .hero-note { + display: none; + } + .hero-visual { + height: 220px; + } + .hero-caption { + bottom: 23px; + left: 25px; + } + .hero-caption p { + font-size: 25px; + } + .hero-caption > span { + font-size: 7px; + } + .hero-stamp { + width: 80px; + height: 80px; + right: 20px; + top: 20px; + font-size: 7px; + gap: 1px; + } + .hero-stamp span { + font-size: 17px; + } + .hero-stamp svg { + width: 17px; + } + .library-section { + padding-top: 35px; + } + .section-heading { + margin-bottom: 20px; + gap: 12px; + } + .section-heading h2 { + font-size: 28px; + } + .section-heading .eyebrow { + font-size: 8px; + letter-spacing: 1.5px; + } + .handwritten { + display: none; + } + .count-badge { + height: 23px; + width: 23px; + font-size: 10px; + margin-left: 4px; + } + .search-row { + gap: 8px; + flex-wrap: wrap; + } + .search-box { + flex-basis: calc(100% - 100px); + padding: 0 12px; + } + .search-row .button { + padding: 11px 12px; + font-size: 11px; + } + .search-row > select { + width: 100%; + padding: 9px 12px; + background: none; + font-size: 11px; + } + .category-chips { + padding: 17px 0 22px; + margin-right: -20px; + } + .chip { + font-size: 10px; + padding: 7px 13px; + } + .recipe-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 15px; + } + .card-image { + height: 160px; + } + .card-body { + padding: 14px 13px 10px; + } + .card-body h3 { + font-size: 21px; + letter-spacing: -0.7px; + } + .card-body > p { + font-size: 10px; + line-height: 1.6; + height: 32px; + margin-bottom: 10px; + } + .card-meta { + font-size: 8px; + gap: 6px; + margin-bottom: 8px; + } + .card-meta svg { + width: 12px; + } + .card-bottom { + font-size: 8px; + padding-top: 9px; + } + .card-bottom svg { + width: 19px; + } + .card-category { + font-size: 8px; + padding: 4px 7px; + left: 10px; + bottom: 10px; + max-width: calc(100% - 20px); + } + .favorite-button { + width: 30px; + height: 30px; + right: 10px; + top: 10px; + } + .favorite-button svg { + width: 16px; + } + .recipe-card { + border-radius: 13px; + } + .home-bottom { + margin-top: 28px; + padding: 22px; + gap: 12px; + flex-wrap: wrap; + } + .home-bottom .note-icon { + width: 40px; + height: 40px; + } + .home-bottom > div { + flex: 1; + } + .home-bottom h3 { + font-size: 20px; + line-height: 1.2; + } + .home-bottom p { + font-size: 10px; + } + .home-bottom > a { + margin-left: 52px; + font-size: 10px; + } + .filter-panel { + flex-direction: column; + gap: 10px; + align-items: stretch; + } + .filter-panel label { + font-size: 10px; + } + .filter-panel .text-button { + margin: 0; + } + .page-heading { + padding: 35px 0 20px; + } + .page-heading h1 { + font-size: 40px; + } + .page-heading p { + font-size: 12px; + } + .empty-state { + padding: 45px 20px; + } + .empty-state h2, + .empty-state h3 { + font-size: 27px; + } + .empty-state p { + font-size: 12px; + } + .back-link { + margin: 20px 0; + font-size: 11px; + } + .recipe-hero { + display: flex; + flex-direction: column-reverse; + gap: 25px; + margin: 0 0 32px; + align-items: stretch; + } + .recipe-cover { + height: 280px; + border-radius: 15px; + } + .recipe-intro .eyebrow { + font-size: 8px; + margin-bottom: 14px; + } + .recipe-intro h1 { + font-size: 40px; + line-height: 1.12; + margin-bottom: 14px; + } + .recipe-subtitle { + font-size: 22px; + margin-bottom: 14px; + } + .recipe-stats { + justify-content: space-between; + gap: 15px; + padding: 18px 0 8px; + margin-top: 20px; + } + .recipe-stats > div > span { + font-size: 9px; + } + .recipe-actions { + margin-top: 18px; + } + .recipe-actions > .primary { + flex: 1; + } + .recipe-columns { + grid-template-columns: 1fr; + gap: 35px; + } + .ingredients-panel { + position: static; + padding: 24px; + } + .panel-title h2 { + font-size: 30px; + } + .panel-title .eyebrow { + font-size: 9px; + } + .calculator { + padding-top: 20px; + } + .segmented button { + font-size: 12px; + padding: 9px; + } + .portion-control { + padding: 16px 25px 10px; + } + .reset-link { + font-size: 10px; + } + .ingredient-name { + font-size: 14px; + } + .ingredient-name small { + font-size: 10px; + } + .amount-button { + font-size: 16px; + padding: 5px 0; + } + .amount-button span { + font-size: 12px; + } + .ingredient-list li { + padding: 12px 0; + } + .check-box { + width: 21px; + height: 21px; + } + .ingredients-panel > .button { + font-size: 12px; + } + .ingredients-panel > .small { + font-size: 9px; + } + .calculator-hint { + font-size: 12px; + } + .preparation .section-heading { + display: flex; + align-items: center; + } + .preparation .section-heading h2 { + font-size: 30px; + } + .preparation .section-heading .eyebrow { + display: none; + } + .time-breakdown { + gap: 35px; + } + .steps-list > li { + gap: 17px; + } + .steps-list h3 { + font-size: 25px; + } + .steps-list p { + font-size: 14px; + } + .step-number { + font-size: 27px; + min-width: 28px; + } + .tip-card { + padding: 22px; + } + .tip-card p, + .storage-card p { + font-size: 13px; + } + .related { + margin-top: 45px; + } + .related h2 { + font-size: 30px; + } + .related .recipe-grid .recipe-card:nth-child(3) { + display: none; + } + .mobile-cook-cta { + display: flex; + position: fixed; + bottom: calc(65px + env(safe-area-inset-bottom)); + left: 0; + right: 0; + z-index: 35; + background: var(--surface); + border-top: 1px solid var(--line); + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 10px 20px; + box-shadow: 0 -6px 30px #00000006; + } + .mobile-cook-cta > span { + font-size: 11px; + color: var(--muted); + } + .mobile-cook-cta .button { + min-height: 42px; + font-size: 11px; + } + .recipe-page { + padding-bottom: 40px; + } + .image-gallery { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .image-gallery img { + height: 130px; + } + .shopping-toolbar { + align-items: flex-start; + gap: 15px; + flex-direction: column; + } + .shopping-item { + padding: 15px; + gap: 12px; + font-size: 13px; + min-height: 70px; + } + .shopping-item strong { + font-size: 12px; + } + .shopping-list > li { + padding-right: 4px; + } + .shopping-list .icon-button { + width: 32px; + } + .login-layout { + margin: 15px 0 0; + padding: 20px; + display: block; + } + .login-story { + padding: 0 10px 20px; + } + .login-story h1 { + font-size: 45px; + line-height: 1.05; + margin: 18px 0; + } + .login-story h1 br { + display: none; + } + .login-story p { + display: none; + } + .login-story .eyebrow { + font-size: 8px; + } + .login-card { + padding: 27px; + } + .login-card h2 { + font-size: 30px; + } + .admin-page { + padding-top: 30px; + } + .admin-heading { + align-items: flex-start; + } + .admin-heading h1 { + font-size: 34px; + } + .admin-heading .eyebrow { + font-size: 7px; + letter-spacing: 1px; + } + .admin-heading > .button { + font-size: 10px; + padding: 9px 12px; + min-height: 38px; + } + .admin-heading p { + font-size: 11px; + } + .admin-tabs { + gap: 20px; + margin-bottom: 25px; + } + .admin-tabs button { + font-size: 11px; + gap: 5px; + } + .admin-tabs svg { + width: 15px; + } + .admin-page > .section-heading { + align-items: flex-start; + flex-direction: column; + } + .admin-recipe-list article { + padding: 14px; + gap: 12px; + flex-wrap: wrap; + } + .admin-thumb { + width: 65px; + height: 65px; + } + .admin-recipe-name h3 { + font-size: 20px; + } + .admin-recipe-name small { + display: block; + margin: 5px 0 0; + font-size: 9px; + } + .admin-recipe-list article > .button-row { + margin-left: auto; + gap: 3px; + } + .admin-recipe-list .icon-button { + width: 35px; + height: 35px; + } + .settings-grid { + grid-template-columns: 1fr; + } + .panel { + padding: 22px; + } + .import-panel > .section-heading { + display: block; + } + .import-panel .section-heading h2 { + font-size: 27px; + } + .import-panel .section-heading p { + font-size: 12px; + } + .import-controls { + grid-template-columns: 1fr; + gap: 20px; + } + .upload-area { + padding: 20px; + } + .preview-columns { + grid-template-columns: 1fr; + gap: 0; + } + .media-grid { + grid-template-columns: repeat(2, 1fr); + gap: 12px; + } + .media-grid img { + height: 130px; + } + .media-grid .text-button { + font-size: 9px; + } + .media-grid .icon-button { + width: 28px; + height: 28px; + } + .dialog { + padding: 26px; + } + .dialog h2 { + font-size: 28px; + } + .dialog .button { + font-size: 11px; + padding: 10px 12px; + } + .editor-heading { + display: block; + } + .editor-heading h1 { + font-size: 33px; + } + .editor-heading .eyebrow { + font-size: 8px; + } + .editor-heading > .button-row { + flex-direction: row; + margin-top: 22px; + } + .editor .admin-tabs { + gap: 18px; + } + .editor-panel { + padding: 19px; + } + .editor-panel h2 { + font-size: 26px; + } + .editor-panel label { + font-size: 11px; + } + .form-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; + } + .form-grid.two { + grid-template-columns: 1fr; + gap: 0; + } + .image-upload { + padding: 20px; + } + .image-upload > img { + height: 180px; + } + .editor-ingredient { + padding: 12px; + gap: 6px; + position: relative; + } + .ingredient-main-fields { + grid-template-columns: 1fr 1fr; + gap: 8px; + padding-right: 15px; + } + .ingredient-main-fields > label:nth-child(3) { + grid-column: 1/-1; + } + .ingredient-secondary-fields { + flex-wrap: wrap; + } + .ingredient-secondary-fields > input { + flex-basis: 100%; + } + .editor-ingredient > .icon-button { + position: absolute; + top: 6px; + right: 3px; + margin: 0; + width: 24px; + } + .reorder { + padding-top: 20px; + } + .ingredient-fields small { + font-size: 7px; + } + .editor-step { + padding: 15px; + } + .editor-step .reorder { + padding: 0; + } + .timer-input { + flex-wrap: wrap; + gap: 6px; + } + .timer-input label { + min-width: 80px; + } + .timer-input .icon-button { + width: 30px; + } + .cooking-shell { + padding: 0 22px; + } + .cooking-header { + height: 76px; + gap: 10px; + } + .cooking-header .brand { + font-size: 25px; + } + .cooking-header .brand > svg { + width: 21px; + } + .cooking-header .button { + font-size: 10px; + padding: 10px 12px; + min-height: 39px; + } + .cooking-recipe-title { + display: none; + } + .cooking-tools { + padding: 24px 0; + gap: 12px; + } + .cooking-tools > span { + font-size: 8px; + letter-spacing: 1px; + } + .cooking-tools button { + font-size: 10px; + } + .cooking-tools > div { + gap: 12px; + } + .cooking-progress > div:first-child { + font-size: 11px; + } + .cooking-step { + padding: 37px 0 20px; + } + .cooking-step .eyebrow { + font-size: 8px; + letter-spacing: 1.5px; + } + .cooking-step h1 { + font-size: 39px; + line-height: 1.12; + margin: 15px 0 22px; + } + .cooking-instruction { + font-size: 22px; + line-height: 1.6; + } + .step-ingredients { + gap: 8px; + margin-top: 25px; + } + .step-ingredients button { + font-size: 11px; + min-height: 45px; + padding: 10px 12px; + } + .timer-grid { + grid-template-columns: 1fr; + gap: 12px; + } + .timer-card { + padding: 17px; + } + .timer-card strong { + font-size: 33px; + } + .cooking-bottom-actions { + align-items: flex-start; + flex-direction: column; + gap: 3px; + padding: 10px 0 25px; + } + .cooking-bottom-actions .small { + font-size: 9px; + } + .cooking-navigation { + padding: 17px 0 calc(17px + env(safe-area-inset-bottom)); + gap: 12px; + } + .cooking-navigation > span { + display: none; + } + .cooking-navigation > .button { + min-height: 58px; + min-width: 0; + font-size: 12px; + padding: 13px 15px; + } + .cooking-navigation > .primary { + flex: 1; + } + .cooking-complete { + padding: 45px 0; + } + .cooking-complete h1 { + font-size: 51px; + } + .cooking-complete .eyebrow { + font-size: 8px; + } + .cooking-complete p { + font-size: 17px; + } + .cooking-complete .button-row { + flex-direction: column; + } + .cooking-complete .button { + width: 100%; + } + .offline-banner { + font-size: 10px; + align-items: flex-start; + } +} +@media (max-width: 380px) { + .container { + padding: 0 15px; + } + .hero-copy { + padding: 27px 22px; + } + .hero-copy h1 { + font-size: 39px; + } + .recipe-grid { + grid-template-columns: 1fr; + } + .card-image { + height: 225px; + } + .card-body h3 { + font-size: 26px; + } + .card-body > p { + font-size: 12px; + height: auto; + } + .card-meta { + font-size: 10px; + } + .card-bottom { + font-size: 10px; + } + .search-row .button { + font-size: 10px; + } + .recipe-stats { + gap: 12px; + } + .recipe-stats strong { + font-size: 11px; + } + .cooking-navigation .button { + font-size: 11px; + padding: 12px; + } + .cooking-instruction { + font-size: 21px; + } +} +@media (prefers-reduced-motion: reduce) { + *, + *:before, + *:after { + animation-duration: 0.01ms !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} +@media print { + * { + box-shadow: none !important; + text-shadow: none !important; + } + body { + background: white !important; + color: black !important; + font-size: 11pt; + } + .no-print, + .site-header, + .site-footer, + .mobile-nav, + .toast, + .offline-banner, + .recipe-actions, + .recipe-cover, + .related, + .tag-list, + .back-link { + display: none !important; + } + .container { + max-width: none; + padding: 0; + } + .recipe-hero { + display: block; + margin: 0 0 20px; + } + .recipe-intro h1 { + font-size: 30pt; + } + .recipe-subtitle { + font-size: 16pt; + } + .recipe-columns { + display: block; + } + .ingredients-panel { + position: static; + border: 0; + padding: 0; + border-radius: 0; + background: white; + } + .ingredient-list { + columns: 2; + gap: 30px; + } + .ingredient-list li { + break-inside: avoid; + padding: 5px 0; + } + .ingredient-line { + display: flex; + } + .ingredient-name { + font-size: 11pt; + } + .amount-button { + border: 0; + color: black; + font-size: 11pt; + } + .amount-button span { + font-size: 10pt; + } + .amount-unset { + font-size: 9pt; + } + .panel-title .eyebrow { + font-size: 10pt; + } + .preparation { + margin-top: 25px; + } + .preparation h2 { + font-size: 22pt; + } + .steps-list li { + break-inside: avoid; + margin-bottom: 20px; + } + .steps-list p { + font-size: 11pt; + color: black; + } + .steps-list h3 { + font-size: 15pt; + } + .step-number { + font-size: 18pt; + } + .tip-card { + break-inside: avoid; + background: #f5f5f5; + } + .shopping-list { + border: 0; + } + .shopping-item { + padding: 10px 0; + } + .shopping-list .check-box { + border: 1px solid black; + } + .page-heading { + padding: 0; + } + .time-breakdown { + font-size: 9pt; + } + .image-gallery { + display: none; + } + .recipe-stats { + justify-content: flex-start; + } + .recipe-stats svg { + display: none; + } + .recipe-stats strong { + font-size: 10pt; + } + .recipe-stats span { + display: none; + } + .nutrition { + break-inside: avoid; + } + .recipe-page { + padding: 0; + } + .main { + padding: 0; + } + .notice { + display: none; + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..20c3568 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,38 @@ +import type { Metadata, Viewport } from 'next'; +import '@fontsource/dm-sans/400.css'; +import '@fontsource/dm-sans/500.css'; +import '@fontsource/dm-sans/600.css'; +import '@fontsource/dm-sans/700.css'; +import '@fontsource/fraunces/400.css'; +import '@fontsource/fraunces/500.css'; +import './globals.css'; +import { Shell } from '@/components/shell'; +export const metadata: Metadata = { + title: { default: 'HamHam · Zuhause schmeckt’s am besten', template: '%s · HamHam' }, + description: + 'Dein persönliches Kochbuch. Lieblingsrezepte sammeln, clever planen und entspannt kochen.', + manifest: '/manifest.webmanifest', + appleWebApp: { capable: true, title: 'HamHam', statusBarStyle: 'default' }, +}; +export const viewport: Viewport = { + width: 'device-width', + initialScale: 1, + viewportFit: 'cover', + themeColor: '#365747', +}; +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + +