feat: mobile-friendly UI overhaul

- Bottom navigation bar on mobile instead of sidebar
- Sheet-style modals (slide up from bottom) on small screens
- Larger touch targets for shopping list checkboxes
- Stacked ingredient form inputs for mobile usability
- Responsive header buttons with short labels on small screens
- Safe-area support for notched phones
- Reduced padding on mobile for better space usage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 07:41:04 +00:00
parent a5471276e8
commit 416c6ed7c7
7 changed files with 109 additions and 103 deletions

View File

@@ -3,7 +3,9 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="theme-color" content="#09090b" />
<title>Mealplanner</title> <title>Mealplanner</title>
</head> </head>
<body> <body>

View File

@@ -45,9 +45,9 @@
<!-- Authenticated app --> <!-- Authenticated app -->
<div v-else class="flex h-screen bg-zinc-950 overflow-hidden"> <div v-else class="flex h-screen bg-zinc-950 overflow-hidden">
<!-- Sidebar --> <!-- Desktop Sidebar (hidden on mobile) -->
<aside <aside
class="flex flex-col bg-zinc-900 border-r border-zinc-800 transition-all duration-300 z-20" class="hidden md:flex flex-col bg-zinc-900 border-r border-zinc-800 transition-all duration-300 z-20"
:class="sidebarOpen ? 'w-56' : 'w-14'" :class="sidebarOpen ? 'w-56' : 'w-14'"
> >
<!-- Logo / toggle --> <!-- Logo / toggle -->
@@ -79,7 +79,7 @@
: 'text-zinc-400 hover:text-zinc-100 hover:bg-zinc-800'" : 'text-zinc-400 hover:text-zinc-100 hover:bg-zinc-800'"
:title="!sidebarOpen ? item.label : undefined" :title="!sidebarOpen ? item.label : undefined"
> >
<span class="flex-shrink-0 w-5 h-5" v-html="item.icon" /> <span class="flex-shrink-0 w-5 h-5 [&>svg]:w-5 [&>svg]:h-5" v-html="item.icon" />
<Transition name="fade-text"> <Transition name="fade-text">
<span v-if="sidebarOpen" class="text-sm font-medium whitespace-nowrap overflow-hidden">{{ item.label }}</span> <span v-if="sidebarOpen" class="text-sm font-medium whitespace-nowrap overflow-hidden">{{ item.label }}</span>
</Transition> </Transition>
@@ -122,47 +122,39 @@
</div> </div>
</aside> </aside>
<!-- Mobile overlay -->
<div
v-if="sidebarOpen && isMobile"
class="fixed inset-0 z-10 bg-black/50"
@click="sidebarOpen = false"
/>
<!-- Main content --> <!-- Main content -->
<main class="flex-1 overflow-y-auto"> <main class="flex-1 overflow-y-auto pb-20 md:pb-0">
<div class="p-6 max-w-7xl mx-auto"> <div class="p-4 md:p-6 max-w-7xl mx-auto">
<RouterView /> <RouterView />
</div> </div>
</main> </main>
<!-- Mobile bottom navigation -->
<nav class="md:hidden fixed bottom-0 inset-x-0 z-30 bg-zinc-900 border-t border-zinc-800 safe-bottom">
<div class="flex items-stretch">
<RouterLink
v-for="item in navItems"
:key="item.to"
:to="item.to"
class="flex-1 flex flex-col items-center gap-1 py-3 transition-colors"
:class="isActiveRoute(item.to)
? 'text-accent'
: 'text-zinc-500 active:text-zinc-300'"
>
<span class="w-6 h-6" v-html="item.icon" />
<span class="text-[10px] font-medium">{{ item.shortLabel }}</span>
</RouterLink>
</div>
</nav>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { isAuthenticated, isLoading, authError, currentUser, login, logout } from './auth' import { isAuthenticated, isLoading, authError, currentUser, login, logout } from './auth'
const sidebarOpen = ref(true) const sidebarOpen = ref(window.innerWidth >= 768)
const windowWidth = ref(window.innerWidth)
function onResize(): void {
windowWidth.value = window.innerWidth
if (window.innerWidth < 768) {
sidebarOpen.value = false
}
}
onMounted(() => {
window.addEventListener('resize', onResize)
if (window.innerWidth < 768) sidebarOpen.value = false
})
onUnmounted(() => {
window.removeEventListener('resize', onResize)
})
const isMobile = computed(() => windowWidth.value < 768)
const route = useRoute() const route = useRoute()
@@ -175,7 +167,8 @@ const navItems = [
{ {
to: '/', to: '/',
label: 'Wochenplan', label: 'Wochenplan',
icon: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-5 h-5"> shortLabel: 'Plan',
icon: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-full h-full">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /> d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>`, </svg>`,
@@ -183,7 +176,8 @@ const navItems = [
{ {
to: '/shopping', to: '/shopping',
label: 'Einkaufsliste', label: 'Einkaufsliste',
icon: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-5 h-5"> shortLabel: 'Einkauf',
icon: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-full h-full">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" /> d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>`, </svg>`,
@@ -191,7 +185,8 @@ const navItems = [
{ {
to: '/recipes', to: '/recipes',
label: 'Rezepte', label: 'Rezepte',
icon: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-5 h-5"> shortLabel: 'Rezepte',
icon: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-full h-full">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0118 18a8.966 8.966 0 00-6 2.292m0-14.25v14.25" /> d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0118 18a8.966 8.966 0 00-6 2.292m0-14.25v14.25" />
</svg>`, </svg>`,
@@ -199,7 +194,8 @@ const navItems = [
{ {
to: '/settings', to: '/settings',
label: 'Einstellungen', label: 'Einstellungen',
icon: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-5 h-5"> shortLabel: 'Mehr',
icon: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-full h-full">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /> d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
@@ -212,6 +208,9 @@ const navItems = [
html, body { html, body {
background-color: #09090b; background-color: #09090b;
} }
.safe-bottom {
padding-bottom: env(safe-area-inset-bottom, 0px);
}
</style> </style>
<style scoped> <style scoped>

View File

@@ -2,10 +2,10 @@
<!-- Backdrop --> <!-- Backdrop -->
<Teleport to="body"> <Teleport to="body">
<div <div
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm" class="fixed inset-0 z-50 flex items-end md:items-center justify-center md:p-4 bg-black/70 backdrop-blur-sm"
@click.self="$emit('close')" @click.self="$emit('close')"
> >
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-zinc-900 border border-zinc-800 rounded-2xl shadow-2xl"> <div class="relative w-full max-w-2xl h-full md:h-auto md:max-h-[90vh] overflow-y-auto bg-zinc-900 md:border md:border-zinc-800 md:rounded-2xl shadow-2xl">
<!-- Close button --> <!-- Close button -->
<button <button
@click="$emit('close')" @click="$emit('close')"
@@ -32,7 +32,7 @@
</svg> </svg>
</div> </div>
<div class="p-6"> <div class="p-4 md:p-6">
<h2 class="text-2xl font-bold text-zinc-100 mb-4">{{ recipe.title }}</h2> <h2 class="text-2xl font-bold text-zinc-100 mb-4">{{ recipe.title }}</h2>
<!-- Ingredients --> <!-- Ingredients -->

View File

@@ -1,16 +1,16 @@
<template> <template>
<div <div
class="flex items-center gap-3 py-2 px-3 rounded-lg hover:bg-zinc-800/50 transition-colors group cursor-pointer" class="flex items-center gap-3 py-3 px-3 rounded-lg hover:bg-zinc-800/50 active:bg-zinc-800/70 transition-colors group cursor-pointer select-none"
@click="$emit('toggle', item.name)" @click="$emit('toggle', item.name)"
> >
<!-- Checkbox --> <!-- Checkbox -->
<div <div
class="flex-shrink-0 w-5 h-5 rounded border-2 flex items-center justify-center transition-all" class="flex-shrink-0 w-6 h-6 rounded border-2 flex items-center justify-center transition-all"
:class="item.isChecked :class="item.isChecked
? 'bg-accent border-accent' ? 'bg-accent border-accent'
: 'border-zinc-600 group-hover:border-zinc-400'" : 'border-zinc-600 group-hover:border-zinc-400'"
> >
<svg v-if="item.isChecked" class="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg v-if="item.isChecked" class="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
</svg> </svg>
</div> </div>
@@ -23,7 +23,7 @@
<!-- Amount + unit --> <!-- Amount + unit -->
<span <span
class="text-sm tabular-nums transition-colors" class="text-sm tabular-nums transition-colors whitespace-nowrap"
:class="item.isChecked ? 'text-zinc-700' : 'text-zinc-400'" :class="item.isChecked ? 'text-zinc-700' : 'text-zinc-400'"
>{{ item.totalAmount != null ? item.totalAmount : '' }} {{ item.unit ?? '' }}</span> >{{ item.totalAmount != null ? item.totalAmount : '' }} {{ item.unit ?? '' }}</span>
</div> </div>

View File

@@ -1,10 +1,10 @@
<template> <template>
<Teleport to="body"> <Teleport to="body">
<div <div
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm" class="fixed inset-0 z-50 flex items-end md:items-center justify-center md:p-4 bg-black/70 backdrop-blur-sm"
@click.self="$emit('close')" @click.self="$emit('close')"
> >
<div class="w-full max-w-lg bg-zinc-900 border border-zinc-800 rounded-2xl shadow-2xl"> <div class="w-full max-w-lg max-h-[85vh] md:max-h-none bg-zinc-900 md:border md:border-zinc-800 rounded-t-2xl md:rounded-2xl shadow-2xl flex flex-col">
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between px-6 py-4 border-b border-zinc-800"> <div class="flex items-center justify-between px-6 py-4 border-b border-zinc-800">
<h2 class="text-lg font-semibold text-zinc-100">Rezept austauschen</h2> <h2 class="text-lg font-semibold text-zinc-100">Rezept austauschen</h2>
@@ -30,7 +30,7 @@
</div> </div>
<!-- Recipe list --> <!-- Recipe list -->
<div class="px-6 pb-4 max-h-80 overflow-y-auto space-y-1"> <div class="px-4 md:px-6 pb-4 flex-1 overflow-y-auto space-y-1">
<div v-if="!filtered.length" class="text-center py-8 text-zinc-500 text-sm"> <div v-if="!filtered.length" class="text-center py-8 text-zinc-500 text-sm">
Keine Rezepte gefunden. Keine Rezepte gefunden.
</div> </div>

View File

@@ -1,17 +1,17 @@
<template> <template>
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-6">
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between"> <div class="flex items-center justify-between gap-3">
<h1 class="text-2xl font-bold text-zinc-100">Meine Rezepte</h1> <h1 class="text-xl md:text-2xl font-bold text-zinc-100">Meine Rezepte</h1>
<button <button
@click="openCreate" @click="openCreate"
class="flex items-center gap-2 px-4 py-2 rounded-lg bg-accent hover:bg-accent-hover text-white text-sm font-medium transition-colors" class="flex-shrink-0 flex items-center gap-2 px-3 md:px-4 py-2 rounded-lg bg-accent hover:bg-accent-hover text-white text-sm font-medium transition-colors"
> >
<!-- Plus icon -->
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg> </svg>
Neues Rezept <span class="hidden sm:inline">Neues Rezept</span>
<span class="sm:hidden">Neu</span>
</button> </button>
</div> </div>
@@ -115,10 +115,10 @@
<Teleport to="body"> <Teleport to="body">
<div <div
v-if="showForm" v-if="showForm"
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm" class="fixed inset-0 z-50 flex items-end md:items-center justify-center md:p-4 bg-black/70 backdrop-blur-sm"
@click.self="closeForm" @click.self="closeForm"
> >
<div class="w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-zinc-900 border border-zinc-800 rounded-2xl shadow-2xl"> <div class="w-full max-w-2xl h-full md:h-auto md:max-h-[90vh] overflow-y-auto bg-zinc-900 md:border md:border-zinc-800 md:rounded-2xl shadow-2xl">
<!-- Form header --> <!-- Form header -->
<div class="flex items-center justify-between px-6 py-4 border-b border-zinc-800 sticky top-0 bg-zinc-900 z-10"> <div class="flex items-center justify-between px-6 py-4 border-b border-zinc-800 sticky top-0 bg-zinc-900 z-10">
<h2 class="text-lg font-semibold text-zinc-100"> <h2 class="text-lg font-semibold text-zinc-100">
@@ -134,7 +134,7 @@
</button> </button>
</div> </div>
<form @submit.prevent="handleSubmit" class="p-6 space-y-5"> <form @submit.prevent="handleSubmit" class="p-4 md:p-6 space-y-5">
<!-- Title --> <!-- Title -->
<div> <div>
<label class="block text-sm font-medium text-zinc-300 mb-1.5">Titel *</label> <label class="block text-sm font-medium text-zinc-300 mb-1.5">Titel *</label>
@@ -185,52 +185,56 @@
</button> </button>
</div> </div>
<div class="space-y-2"> <div class="space-y-3">
<div <div
v-for="(ing, idx) in form.ingredients" v-for="(ing, idx) in form.ingredients"
:key="idx" :key="idx"
class="flex gap-2 items-start" class="bg-zinc-800/30 rounded-lg p-3 space-y-2"
> >
<input <div class="flex items-center gap-2">
v-model="ing.name" <input
type="text" v-model="ing.name"
placeholder="Name" type="text"
class="flex-1 bg-zinc-800 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-accent text-sm" placeholder="Name"
/> class="flex-1 bg-zinc-800 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-accent text-sm"
<input />
v-model.number="ing.amount" <button
type="number" type="button"
placeholder="Menge" @click="removeIngredient(idx)"
min="0" class="p-2 text-zinc-500 hover:text-error transition-colors flex-shrink-0"
step="any" >
class="w-20 bg-zinc-800 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-accent text-sm" <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
<input d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
v-model="ing.unit" </svg>
type="text" </button>
placeholder="Einheit" </div>
class="w-20 bg-zinc-800 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-accent text-sm" <div class="flex gap-2">
/> <input
<select v-model.number="ing.amount"
v-model="ing.category" type="number"
class="w-32 bg-zinc-800 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 focus:outline-none focus:border-accent text-sm" placeholder="Menge"
> min="0"
<option value="Gemüse">Gemüse</option> step="any"
<option value="Fleisch">Fleisch</option> class="w-24 bg-zinc-800 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-accent text-sm"
<option value="Milchprodukte">Milchprodukte</option> />
<option value="Gewürze">Gewürze</option> <input
<option value="Sonstiges">Sonstiges</option> v-model="ing.unit"
</select> type="text"
<button placeholder="Einheit"
type="button" class="w-24 bg-zinc-800 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-accent text-sm"
@click="removeIngredient(idx)" />
class="p-2 text-zinc-500 hover:text-error transition-colors" <select
> v-model="ing.category"
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> class="flex-1 bg-zinc-800 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 focus:outline-none focus:border-accent text-sm"
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" >
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> <option value="Gemüse">Gemüse</option>
</svg> <option value="Fleisch">Fleisch</option>
</button> <option value="Milchprodukte">Milchprodukte</option>
<option value="Gewürze">Gewürze</option>
<option value="Sonstiges">Sonstiges</option>
</select>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,9 @@
<template> <template>
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-6">
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between"> <div class="flex items-center justify-between gap-3">
<div> <div class="min-w-0">
<h1 class="text-2xl font-bold text-zinc-100">Wochenplan</h1> <h1 class="text-xl md:text-2xl font-bold text-zinc-100">Wochenplan</h1>
<p v-if="store.currentPlan" class="text-zinc-500 text-sm mt-0.5"> <p v-if="store.currentPlan" class="text-zinc-500 text-sm mt-0.5">
{{ formatWeek(store.currentPlan.weekStart) }} {{ formatWeek(store.currentPlan.weekStart) }}
</p> </p>
@@ -11,7 +11,7 @@
<button <button
@click="handleGenerate" @click="handleGenerate"
:disabled="store.generating" :disabled="store.generating"
class="flex items-center gap-2 px-4 py-2 rounded-lg bg-accent hover:bg-accent-hover disabled:opacity-60 disabled:cursor-not-allowed text-white text-sm font-medium transition-colors" class="flex-shrink-0 flex items-center gap-2 px-3 md:px-4 py-2 rounded-lg bg-accent hover:bg-accent-hover disabled:opacity-60 disabled:cursor-not-allowed text-white text-sm font-medium transition-colors"
> >
<svg v-if="store.generating" class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg v-if="store.generating" class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
@@ -24,7 +24,8 @@
<circle cx="15.5" cy="15.5" r="1.5" fill="currentColor" /> <circle cx="15.5" cy="15.5" r="1.5" fill="currentColor" />
<circle cx="12" cy="12" r="1.5" fill="currentColor" /> <circle cx="12" cy="12" r="1.5" fill="currentColor" />
</svg> </svg>
{{ store.generating ? 'Generiere...' : 'Neue Woche generieren' }} <span class="hidden sm:inline">{{ store.generating ? 'Generiere...' : 'Neue Woche generieren' }}</span>
<span class="sm:hidden">{{ store.generating ? '...' : 'Neu' }}</span>
</button> </button>
</div> </div>