mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:01:09 +02:00
refactor(theming): replace transition-all with specific transitions
Sweep 98 `transition-all` occurrences across 62 files and replace with targeted Tailwind transition utilities. Motivation: 1. `transition-all` animates every property, including CSS custom- property-backed colours. On first paint the vars may not have resolved yet, producing the P5 "white-on-white until first interaction" rendering bug. The same bug hit food/moodlit ListViews in the earlier theme migration. 2. Specific transitions also perform better — no layout-property interpolation overhead. Codemod scripts/migrate-transition-all.mjs classifies each class attribute by its sibling classes and picks one of: - `transition-opacity` — icon fade on group-hover - `transition-[width]` — progress-bar width anim - `transition-[transform,colors,box-shadow]` — scaled buttons/cards - `transition-[border-color,box-shadow]` — card hover:border+shadow - `transition-colors` — default (card/row hover) 91 / 98 auto-classified, 7 hand-migrated: - EntryItem → transition-[box-shadow] (ring fade) - NutritionProgressWidget → transition-[stroke-dashoffset,stroke] - OnboardingModal → transition-[width,background-color] - times/reports (3×) → transition-[width] / -[height] (bar anims) - presi/present → transition-[width,background-color] (dots) svelte-check clean with 0 errors; validate:all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
46c03e6a5b
commit
ea71d3c215
63 changed files with 285 additions and 102 deletions
|
|
@ -64,7 +64,7 @@
|
|||
{#each buttons as row}
|
||||
{#each row as btn}
|
||||
<button
|
||||
class="h-14 rounded-xl border border-border transition-all active:scale-95 text-lg {getButtonClass(
|
||||
class="h-14 rounded-xl border border-border transition-[transform,colors,box-shadow] active:scale-95 text-lg {getButtonClass(
|
||||
btn
|
||||
)}"
|
||||
onclick={() => handleButton(btn)}
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
</div>
|
||||
|
||||
<button
|
||||
class="mt-2 w-full h-10 rounded-lg bg-muted/50 text-muted-foreground hover:bg-muted transition-all text-sm"
|
||||
class="mt-2 w-full h-10 rounded-lg bg-muted/50 text-muted-foreground hover:bg-muted transition-colors text-sm"
|
||||
onclick={onBackspace}
|
||||
>
|
||||
← Löschen
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@
|
|||
stroke-linecap="round"
|
||||
stroke-dasharray={CIRCUMFERENCE}
|
||||
stroke-dashoffset={strokeOffset}
|
||||
class="transition-all duration-500"
|
||||
class="transition-[stroke-dashoffset,stroke] duration-500"
|
||||
/>
|
||||
</svg>
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center">
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
|
||||
<button
|
||||
type="button"
|
||||
class="mood-card group relative w-full overflow-hidden rounded-2xl transition-all duration-200 hover:scale-[1.02] hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
class="mood-card group relative w-full overflow-hidden rounded-2xl transition-[transform,colors,box-shadow] duration-200 hover:scale-[1.02] hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
class:ring-2={isActive}
|
||||
class:ring-primary={isActive}
|
||||
onclick={handleClick}
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@
|
|||
<div class="flex-1 flex items-center justify-center pointer-events-auto">
|
||||
<button
|
||||
type="button"
|
||||
class="p-6 rounded-full bg-white/20 hover:bg-white/30 backdrop-blur-sm transition-all hover:scale-110"
|
||||
class="p-6 rounded-full bg-white/20 hover:bg-white/30 backdrop-blur-sm transition-[transform,colors,box-shadow] hover:scale-110"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
togglePlay();
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
</script>
|
||||
|
||||
<div
|
||||
class="flex cursor-pointer flex-col items-center rounded-xl border-2 border-dashed p-12 text-center transition-all {dragActive
|
||||
class="flex cursor-pointer flex-col items-center rounded-xl border-2 border-dashed p-12 text-center transition-colors {dragActive
|
||||
? 'border-primary bg-primary/5 border-solid'
|
||||
: 'border-border bg-background-card hover:border-primary'}"
|
||||
ondragover={handleDragOver}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
</script>
|
||||
|
||||
<div
|
||||
class="relative rounded-xl border p-4 transition-all duration-200 {achievement.unlocked
|
||||
class="relative rounded-xl border p-4 transition-colors duration-200 {achievement.unlocked
|
||||
? `${rarity.bgColor} ${rarity.borderColor}`
|
||||
: 'border-border/50 bg-card/30'} {achievement.unlocked
|
||||
? 'hover:-translate-y-0.5 hover:shadow-lg'
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
</div>
|
||||
<div class="mt-1 h-1.5 overflow-hidden rounded-full bg-muted">
|
||||
<div
|
||||
class="h-full rounded-full bg-gradient-to-r from-gray-500 to-gray-400 transition-all duration-300"
|
||||
class="h-full rounded-full bg-gradient-to-r from-gray-500 to-gray-400 transition-[width] duration-300"
|
||||
style="width: {progressPercent}%"
|
||||
></div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -100,14 +100,14 @@
|
|||
</button>
|
||||
<button
|
||||
onclick={onEdit}
|
||||
class="rounded-lg bg-muted/20 p-2 text-muted-foreground opacity-0 transition-all hover:bg-muted/30 hover:text-white group-hover:opacity-100"
|
||||
class="rounded-lg bg-muted/20 p-2 text-muted-foreground opacity-0 transition-colors hover:bg-muted/30 hover:text-white group-hover:opacity-100"
|
||||
title={$_('common.edit')}
|
||||
>
|
||||
<PencilSimple class="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onclick={onDelete}
|
||||
class="rounded-lg bg-red-600/20 p-2 text-red-400 opacity-0 transition-all hover:bg-red-600/30 group-hover:opacity-100"
|
||||
class="rounded-lg bg-red-600/20 p-2 text-red-400 opacity-0 transition-colors hover:bg-red-600/30 group-hover:opacity-100"
|
||||
title={$_('common.delete')}
|
||||
>
|
||||
<Trash class="h-4 w-4" />
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@
|
|||
</script>
|
||||
|
||||
<div
|
||||
class="entry-item rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] transition-all {isExpanded
|
||||
class="entry-item rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] transition-[box-shadow] {isExpanded
|
||||
? 'ring-1 ring-[hsl(var(--color-primary)/0.3)]'
|
||||
: ''}"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@
|
|||
<!-- Start/Stop Button -->
|
||||
<button
|
||||
onclick={handleStartStop}
|
||||
class="w-full rounded-xl py-3 text-lg font-medium transition-all {timerStore.isRunning
|
||||
class="w-full rounded-xl py-3 text-lg font-medium transition-colors {timerStore.isRunning
|
||||
? 'bg-red-500 text-white hover:bg-red-600'
|
||||
: 'bg-[hsl(var(--color-primary))] text-[hsl(var(--color-primary-foreground))] hover:opacity-90'}"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
cy={y}
|
||||
r={isSelected ? 8 : 5}
|
||||
fill={isSelected ? 'hsl(var(--color-primary))' : 'hsl(var(--color-muted-foreground))'}
|
||||
class="cursor-pointer hover:opacity-80 transition-all"
|
||||
class="cursor-pointer hover:opacity-80 transition-opacity"
|
||||
/>
|
||||
{#if isSelected}
|
||||
<text
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
<div class="mb-6 flex gap-1.5">
|
||||
{#each steps as _, i}
|
||||
<div
|
||||
class="h-1.5 rounded-full transition-all {i === step
|
||||
class="h-1.5 rounded-full transition-[width,background-color] {i === step
|
||||
? 'w-6 bg-primary'
|
||||
: i < step
|
||||
? 'w-1.5 bg-primary/40'
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
onkeydown={(e) => e.key === 'Enter' && onOpenTask(task)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class="group flex cursor-pointer items-center gap-4 rounded-xl border border-border bg-card p-4 transition-all hover:shadow-md"
|
||||
class="group flex cursor-pointer items-center gap-4 rounded-xl border border-border bg-card p-4 transition-colors hover:shadow-md"
|
||||
style="border-left: 4px solid {getPriorityColor(task.priority)}"
|
||||
>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@
|
|||
{#each quickLinks as link}
|
||||
<a
|
||||
href={link.href}
|
||||
class="rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-lg group"
|
||||
class="rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-lg group"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-3 text-center">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -157,8 +157,7 @@
|
|||
<!-- Skin picker toggle -->
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<button
|
||||
class="text-xs rounded-full border px-3 py-1.5 transition-all
|
||||
{showSkinPicker
|
||||
class="text-xs rounded-full border px-3 py-1.5 transition-colors {showSkinPicker
|
||||
? 'border-pink-500 bg-pink-500 text-white'
|
||||
: 'border-border bg-card text-muted-foreground hover:bg-muted'}"
|
||||
onclick={() => (showSkinPicker = !showSkinPicker)}
|
||||
|
|
@ -177,8 +176,9 @@
|
|||
<div class="grid grid-cols-5 gap-2">
|
||||
{#each CALCULATOR_SKINS as skin}
|
||||
<button
|
||||
class="rounded-lg border p-2 text-center transition-all
|
||||
{activeSkin === skin.id ? 'border-pink-500 bg-pink-500/10' : 'border-transparent hover:bg-muted'}"
|
||||
class="rounded-lg border p-2 text-center transition-colors {activeSkin === skin.id
|
||||
? 'border-pink-500 bg-pink-500/10'
|
||||
: 'border-transparent hover:bg-muted'}"
|
||||
onclick={() => setSkin(skin.id)}
|
||||
>
|
||||
<div class="text-sm font-medium text-foreground">{skin.label}</div>
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
{#each quickLinks as link}
|
||||
<a
|
||||
href={link.href}
|
||||
class="rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-lg group"
|
||||
class="rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-lg group"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-3 text-center">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@
|
|||
tabindex="0"
|
||||
onclick={() => handleConversationClick(conv.id)}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleConversationClick(conv.id)}
|
||||
class="group flex items-center gap-3 rounded-lg border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-3 transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="group flex items-center gap-3 rounded-lg border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-3 transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<ChatCircle size={20} class="shrink-0 text-[hsl(var(--color-primary))]" />
|
||||
<div class="min-w-0 flex-1">
|
||||
|
|
@ -185,7 +185,7 @@
|
|||
tabindex="0"
|
||||
onclick={() => handleConversationClick(conv.id)}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleConversationClick(conv.id)}
|
||||
class="group flex items-center gap-3 rounded-lg border border-transparent p-3 transition-all hover:border-[hsl(var(--color-border))] hover:bg-[hsl(var(--color-card))]"
|
||||
class="group flex items-center gap-3 rounded-lg border border-transparent p-3 transition-colors hover:border-[hsl(var(--color-border))] hover:bg-[hsl(var(--color-card))]"
|
||||
>
|
||||
<ChatCircle size={20} class="shrink-0 text-[hsl(var(--color-muted-foreground))]" />
|
||||
<div class="min-w-0 flex-1">
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
tabindex="0"
|
||||
onclick={() => handleClick(conv.id)}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleClick(conv.id)}
|
||||
class="group flex items-center gap-3 rounded-lg border border-transparent p-3 transition-all hover:border-[hsl(var(--color-border))] hover:bg-[hsl(var(--color-card))]"
|
||||
class="group flex items-center gap-3 rounded-lg border border-transparent p-3 transition-colors hover:border-[hsl(var(--color-border))] hover:bg-[hsl(var(--color-card))]"
|
||||
>
|
||||
<ChatCircle size={20} class="shrink-0 text-[hsl(var(--color-muted-foreground))]" />
|
||||
<div class="min-w-0 flex-1">
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@
|
|||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
{#each templates as template (template.id)}
|
||||
<div
|
||||
class="group rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-5 transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="group rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-5 transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
{#if authStore.isAuthenticated}
|
||||
<a
|
||||
href="/citycorners/add-city"
|
||||
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary text-white shadow-md transition-all hover:bg-primary/90 hover:shadow-lg"
|
||||
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary text-white shadow-md transition-colors hover:bg-primary/90 hover:shadow-lg"
|
||||
title={$_('cities.add')}
|
||||
>
|
||||
<Plus size={20} weight="bold" />
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
</div>
|
||||
<a
|
||||
href="/citycorners/cities/{citySlug}/add"
|
||||
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary text-white shadow-md transition-all hover:bg-primary/90 hover:shadow-lg"
|
||||
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary text-white shadow-md transition-colors hover:bg-primary/90 hover:shadow-lg"
|
||||
title={$_('add.title')}
|
||||
>
|
||||
<Plus size={20} weight="bold" />
|
||||
|
|
@ -217,7 +217,7 @@
|
|||
|
||||
{#if authStore.isAuthenticated}
|
||||
<button
|
||||
class="absolute right-3 top-3 flex h-9 w-9 items-center justify-center rounded-full bg-black/30 backdrop-blur-sm transition-all hover:bg-black/50"
|
||||
class="absolute right-3 top-3 flex h-9 w-9 items-center justify-center rounded-full bg-black/30 backdrop-blur-sm transition-colors hover:bg-black/50"
|
||||
onclick={(e) => handleFavoriteToggle(e, location.id)}
|
||||
title={favoriteIds.has(location.id) ? $_('favorites.remove') : $_('favorites.add')}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@
|
|||
<!-- Share + Favorite buttons overlay -->
|
||||
<div class="absolute right-4 top-4 flex gap-2">
|
||||
<button
|
||||
class="flex h-10 w-10 items-center justify-center rounded-full bg-black/30 text-white backdrop-blur-sm transition-all hover:bg-black/50"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-full bg-black/30 text-white backdrop-blur-sm transition-colors hover:bg-black/50"
|
||||
onclick={handleShare}
|
||||
title={$_('detail.share')}
|
||||
>
|
||||
|
|
@ -239,7 +239,7 @@
|
|||
|
||||
{#if authStore.isAuthenticated}
|
||||
<button
|
||||
class="flex h-10 w-10 items-center justify-center rounded-full bg-black/30 backdrop-blur-sm transition-all hover:bg-black/50"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-full bg-black/30 backdrop-blur-sm transition-colors hover:bg-black/50"
|
||||
onclick={() => favoritesStore.toggle(location!.id)}
|
||||
title={favoriteIds.has(location.id) ? $_('favorites.remove') : $_('favorites.add')}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
href="https://www.openstreetmap.org/#map=14/{lat}/{lng}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-background-card border border-border text-foreground-secondary shadow-sm transition-all hover:text-primary hover:border-primary"
|
||||
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-background-card border border-border text-foreground-secondary shadow-sm transition-colors hover:text-primary hover:border-primary"
|
||||
title={$_('map.openInMaps')}
|
||||
>
|
||||
<GlobeSimple size={20} />
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@
|
|||
{#each pinnedSpaces as space}
|
||||
<a
|
||||
href="/context/spaces/{space.id}"
|
||||
class="rounded-xl border border-border-strong bg-white p-4 transition-all hover:shadow-md dark:border-border dark:bg-card"
|
||||
class="rounded-xl border border-border-strong bg-white p-4 transition-colors hover:shadow-md dark:border-border dark:bg-card"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<span
|
||||
|
|
@ -128,7 +128,7 @@
|
|||
{#each recentDocs as doc}
|
||||
<a
|
||||
href="/context/documents/{doc.id}"
|
||||
class="group rounded-xl border border-border-strong bg-white p-4 transition-all hover:shadow-md dark:border-border dark:bg-card"
|
||||
class="group rounded-xl border border-border-strong bg-white p-4 transition-colors hover:shadow-md dark:border-border dark:bg-card"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="min-w-0 flex-1">
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@
|
|||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
{#each filteredDocuments as doc (doc.id)}
|
||||
<div
|
||||
class="group rounded-xl border border-border-strong bg-white p-4 transition-all hover:shadow-md dark:border-border dark:bg-card"
|
||||
class="group rounded-xl border border-border-strong bg-white p-4 transition-colors hover:shadow-md dark:border-border dark:bg-card"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<a href="/context/documents/{doc.id}" class="min-w-0 flex-1">
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@
|
|||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
{#each filteredSpaces as space (space.id)}
|
||||
<div
|
||||
class="group rounded-xl border border-border-strong bg-white p-4 transition-all hover:shadow-md dark:border-border dark:bg-card"
|
||||
class="group rounded-xl border border-border-strong bg-white p-4 transition-colors hover:shadow-md dark:border-border dark:bg-card"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<a href="/context/spaces/{space.id}" class="min-w-0 flex-1">
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@
|
|||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
{#each filteredDocuments as doc (doc.id)}
|
||||
<div
|
||||
class="group rounded-xl border border-border-strong bg-white p-4 transition-all hover:shadow-md dark:border-border dark:bg-card"
|
||||
class="group rounded-xl border border-border-strong bg-white p-4 transition-colors hover:shadow-md dark:border-border dark:bg-card"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<a href="/context/documents/{doc.id}" class="min-w-0 flex-1">
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@
|
|||
</p>
|
||||
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-[hsl(var(--color-muted))]">
|
||||
<div
|
||||
class="h-full rounded-full transition-all {getProgressColor(
|
||||
class="h-full rounded-full transition-[width] {getProgressColor(
|
||||
progress.calories.percentage
|
||||
)}"
|
||||
style="width: {Math.min(progress.calories.percentage, 100)}%"
|
||||
|
|
@ -116,7 +116,9 @@
|
|||
</p>
|
||||
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-[hsl(var(--color-muted))]">
|
||||
<div
|
||||
class="h-full rounded-full transition-all {getProgressColor(progress.protein.percentage)}"
|
||||
class="h-full rounded-full transition-[width] {getProgressColor(
|
||||
progress.protein.percentage
|
||||
)}"
|
||||
style="width: {Math.min(progress.protein.percentage, 100)}%"
|
||||
></div>
|
||||
</div>
|
||||
|
|
@ -143,7 +145,9 @@
|
|||
</p>
|
||||
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-[hsl(var(--color-muted))]">
|
||||
<div
|
||||
class="h-full rounded-full transition-all {getProgressColor(progress.carbs.percentage)}"
|
||||
class="h-full rounded-full transition-[width] {getProgressColor(
|
||||
progress.carbs.percentage
|
||||
)}"
|
||||
style="width: {Math.min(progress.carbs.percentage, 100)}%"
|
||||
></div>
|
||||
</div>
|
||||
|
|
@ -165,7 +169,7 @@
|
|||
</p>
|
||||
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-[hsl(var(--color-muted))]">
|
||||
<div
|
||||
class="h-full rounded-full transition-all {getProgressColor(progress.fat.percentage)}"
|
||||
class="h-full rounded-full transition-[width] {getProgressColor(progress.fat.percentage)}"
|
||||
style="width: {Math.min(progress.fat.percentage, 100)}%"
|
||||
></div>
|
||||
</div>
|
||||
|
|
@ -204,7 +208,7 @@
|
|||
{#each todaysMeals as meal (meal.id)}
|
||||
<a
|
||||
href="/food/{meal.id}"
|
||||
class="block rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="block rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
{#if meal.photoThumbnailUrl || meal.photoUrl}
|
||||
|
|
@ -264,14 +268,14 @@
|
|||
<div class="flex gap-3">
|
||||
<a
|
||||
href="/food/goals"
|
||||
class="flex-1 rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 text-center transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="flex-1 rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 text-center transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<span class="text-2xl">🎯</span>
|
||||
<p class="mt-1 text-sm font-medium text-[hsl(var(--color-foreground))]">Ziele</p>
|
||||
</a>
|
||||
<a
|
||||
href="/food/history"
|
||||
class="flex-1 rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 text-center transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="flex-1 rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 text-center transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<span class="text-2xl">📊</span>
|
||||
<p class="mt-1 text-sm font-medium text-[hsl(var(--color-foreground))]">Verlauf</p>
|
||||
|
|
|
|||
|
|
@ -337,8 +337,8 @@
|
|||
<button
|
||||
type="button"
|
||||
onclick={() => (editMealType = type)}
|
||||
class="rounded-lg border-2 px-3 py-2 text-sm transition-all
|
||||
{editMealType === type
|
||||
class="rounded-lg border-2 px-3 py-2 text-sm transition-colors {editMealType ===
|
||||
type
|
||||
? 'border-[hsl(var(--color-primary))] bg-[hsl(var(--color-primary)/0.05)] font-medium'
|
||||
: 'border-[hsl(var(--color-border))] hover:border-[hsl(var(--color-primary)/0.3)]'}"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -424,8 +424,7 @@
|
|||
<button
|
||||
type="button"
|
||||
onclick={() => (mealType = type)}
|
||||
class="rounded-lg border-2 px-3 py-2 text-sm transition-all
|
||||
{mealType === type
|
||||
class="rounded-lg border-2 px-3 py-2 text-sm transition-colors {mealType === type
|
||||
? 'border-[hsl(var(--color-primary))] bg-[hsl(var(--color-primary)/0.05)] font-medium'
|
||||
: 'border-[hsl(var(--color-border))] hover:border-[hsl(var(--color-primary)/0.3)]'}"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@
|
|||
{#each filteredGuides as guide}
|
||||
{@const cat = GUIDE_CATEGORIES[guide.category]}
|
||||
<div
|
||||
class="group flex items-center gap-4 rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-md"
|
||||
class="group flex items-center gap-4 rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-md"
|
||||
>
|
||||
<div
|
||||
class="{cat.color} flex h-10 w-10 shrink-0 items-center justify-center rounded-full text-white"
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@
|
|||
tabindex="0"
|
||||
onclick={() => handleCollectionClick(collection)}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleCollectionClick(collection)}
|
||||
class="group rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-5 text-left transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="group rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-5 text-left transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
{#each DEFAULT_TEMPLATES as template}
|
||||
<button
|
||||
onclick={() => selectTemplate(template)}
|
||||
class="rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 text-left transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 text-left transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-2xl">{template.icon}</span>
|
||||
|
|
|
|||
|
|
@ -620,7 +620,7 @@
|
|||
{#if progress !== null}
|
||||
<div class="mt-3 h-2 overflow-hidden rounded-full bg-muted">
|
||||
<div
|
||||
class="h-full rounded-full bg-primary transition-all duration-300"
|
||||
class="h-full rounded-full bg-primary transition-[width] duration-300"
|
||||
style="width: {Math.round(progress * 100)}%"
|
||||
></div>
|
||||
</div>
|
||||
|
|
@ -1110,7 +1110,7 @@
|
|||
</div>
|
||||
<div class="h-2 overflow-hidden rounded-full bg-muted">
|
||||
<div
|
||||
class="h-full rounded-full bg-primary transition-all duration-300"
|
||||
class="h-full rounded-full bg-primary transition-[width] duration-300"
|
||||
style="width: {Math.round((benchmarkCurrentRun / benchmarkIterations) * 100)}%"
|
||||
></div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@
|
|||
tabindex="0"
|
||||
onclick={() => handleMemoClick(memo.id)}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleMemoClick(memo.id)}
|
||||
class="group rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="group rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="min-w-0 flex-1">
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
tabindex="0"
|
||||
onclick={() => handleClick(memo.id)}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleClick(memo.id)}
|
||||
class="group rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="group rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="min-w-0 flex-1">
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
{#each quickLinks as link}
|
||||
<a
|
||||
href={link.href}
|
||||
class="rounded-xl border border-border bg-card p-6 transition-all hover:border-primary/50 hover:shadow-lg group"
|
||||
class="rounded-xl border border-border bg-card p-6 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-lg group"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-3 text-center">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@
|
|||
: `linear-gradient(135deg, ${mood.colors.join(', ')})`}
|
||||
<button
|
||||
onclick={() => (fullscreenMood = mood)}
|
||||
class="mood-card group relative aspect-[16/10] w-full overflow-hidden rounded-2xl border-[3px] border-transparent transition-all duration-200 hover:border-border/40 focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
class="mood-card group relative aspect-[16/10] w-full overflow-hidden rounded-2xl border-[3px] border-transparent transition-colors duration-200 hover:border-border/40 focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
style="--mood-color: {mood.colors[0]}"
|
||||
>
|
||||
<div
|
||||
|
|
@ -197,7 +197,7 @@
|
|||
deleteMood(mood);
|
||||
}
|
||||
}}
|
||||
class="absolute right-2 top-2 rounded-full bg-black/20 p-1.5 text-foreground opacity-0 backdrop-blur-sm transition-all hover:bg-black/40 hover:text-white group-hover:opacity-100 cursor-pointer"
|
||||
class="absolute right-2 top-2 rounded-full bg-black/20 p-1.5 text-foreground opacity-0 backdrop-blur-sm transition-colors hover:bg-black/40 hover:text-white group-hover:opacity-100 cursor-pointer"
|
||||
>
|
||||
<X size={14} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@
|
|||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{#each projectsCtx.value.slice(0, 6) as project (project.id)}
|
||||
<div
|
||||
class="rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<h3 class="font-medium text-[hsl(var(--color-foreground))]">{project.title}</h3>
|
||||
{#if project.description}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@
|
|||
{#each playlistsCtx.value as playlist (playlist.id)}
|
||||
<a
|
||||
href="/music/playlists/{playlist.id}"
|
||||
class="group relative rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="group relative rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<div
|
||||
class="mb-3 flex aspect-square items-center justify-center overflow-hidden rounded-lg bg-[hsl(var(--color-muted))]"
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@
|
|||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{#each projectsCtx.value as project (project.id)}
|
||||
<div
|
||||
class="group rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="group rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<h3 class="font-medium text-[hsl(var(--color-foreground))]">{project.title}</h3>
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@
|
|||
{#each filteredImages as img (img.id)}
|
||||
<button
|
||||
onclick={() => (selectedImage = img)}
|
||||
class="group relative overflow-hidden rounded-lg border border-border bg-card transition-all hover:shadow-lg hover:border-primary/50"
|
||||
class="group relative overflow-hidden rounded-lg border border-border bg-card transition-[border-color,box-shadow] hover:shadow-lg hover:border-primary/50"
|
||||
>
|
||||
{#if img.publicUrl}
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@
|
|||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{#each allBoards.value as board (board.id)}
|
||||
<div
|
||||
class="group relative overflow-hidden rounded-lg border border-border bg-card transition-all hover:shadow-lg hover:border-primary/50"
|
||||
class="group relative overflow-hidden rounded-lg border border-border bg-card transition-[border-color,box-shadow] hover:shadow-lg hover:border-primary/50"
|
||||
>
|
||||
<!-- Thumbnail -->
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@
|
|||
<!-- svelte-ignore a11y_consider_explicit_label -->
|
||||
<button
|
||||
onclick={() => goToSlide(index)}
|
||||
class="w-2 h-2 rounded-full transition-all"
|
||||
class="w-2 h-2 rounded-full transition-[width,background-color]"
|
||||
class:bg-primary-500={index === currentSlideIndex}
|
||||
class:w-4={index === currentSlideIndex}
|
||||
class:bg-muted={index !== currentSlideIndex}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@
|
|||
|
||||
<a
|
||||
href="/questions/{question.id}"
|
||||
class="block rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="block rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="mt-1">
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@
|
|||
<div class="space-y-3">
|
||||
{#each collections as collection (collection.id)}
|
||||
<div
|
||||
class="flex items-center gap-4 rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-all hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
class="flex items-center gap-4 rounded-xl border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-4 transition-colors hover:border-[hsl(var(--color-primary)/0.3)]"
|
||||
>
|
||||
<!-- Icon & Color -->
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -215,7 +215,8 @@
|
|||
<button
|
||||
type="button"
|
||||
onclick={() => (researchDepth = option.value)}
|
||||
class="rounded-lg border-2 p-4 text-left transition-all {researchDepth === option.value
|
||||
class="rounded-lg border-2 p-4 text-left transition-colors {researchDepth ===
|
||||
option.value
|
||||
? 'border-[hsl(var(--color-primary))] bg-[hsl(var(--color-primary)/0.05)]'
|
||||
: 'border-[hsl(var(--color-border))] hover:border-[hsl(var(--color-primary)/0.3)]'}"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,11 @@
|
|||
|
||||
<!-- Daily Quote Card -->
|
||||
{#if quotesStore.currentQuote}
|
||||
<div class="mb-8 transition-all duration-300 {isRefreshing ? 'opacity-50 scale-95' : ''}">
|
||||
<div
|
||||
class="mb-8 transition-[transform,colors,box-shadow] duration-300 {isRefreshing
|
||||
? 'opacity-50 scale-95'
|
||||
: ''}"
|
||||
>
|
||||
<QuoteCard
|
||||
quote={quotesStore.currentQuote}
|
||||
size="large"
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@
|
|||
{#each allLists.value as list (list.id)}
|
||||
<a
|
||||
href="/quotes/lists/{list.id}"
|
||||
class="block p-6 bg-surface-elevated rounded-2xl hover:shadow-lg transition-all group"
|
||||
class="block p-6 bg-surface-elevated rounded-2xl hover:shadow-lg transition-colors group"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
</div>
|
||||
<div class="h-3 overflow-hidden rounded-full bg-muted">
|
||||
<div
|
||||
class="h-full rounded-full bg-gradient-to-r from-yellow-500 to-yellow-400 transition-all duration-500"
|
||||
class="h-full rounded-full bg-gradient-to-r from-yellow-500 to-yellow-400 transition-[width] duration-500"
|
||||
style="width: {completion}%"
|
||||
></div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
{#each quickLinks as link}
|
||||
<a
|
||||
href={link.href}
|
||||
class="rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-lg group"
|
||||
class="rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-lg group"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-3 text-center">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@
|
|||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
|
||||
{#each favoriteFolders as folder (folder.id)}
|
||||
<button
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-md"
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-md"
|
||||
onclick={() => handleFolderClick(folder)}
|
||||
>
|
||||
<div
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
{/each}
|
||||
{#each favoriteFiles as file (file.id)}
|
||||
<div
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-md"
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-md"
|
||||
>
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-muted text-2xl">
|
||||
{#if file.mimeType.startsWith('image/')}📷
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@
|
|||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
|
||||
{#each folders as folder (folder.id)}
|
||||
<button
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-md"
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-md"
|
||||
onclick={() => handleFolderClick(folder)}
|
||||
>
|
||||
<div
|
||||
|
|
@ -178,7 +178,7 @@
|
|||
{/each}
|
||||
{#each files as file (file.id)}
|
||||
<div
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-md"
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-md"
|
||||
>
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-muted text-2xl">
|
||||
{#if file.mimeType.startsWith('image/')}📷
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@
|
|||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
|
||||
{#each folders as folder (folder.id)}
|
||||
<button
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-md"
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-md"
|
||||
onclick={() => handleFolderClick(folder)}
|
||||
>
|
||||
<div
|
||||
|
|
@ -186,7 +186,7 @@
|
|||
{/each}
|
||||
{#each files as file (file.id)}
|
||||
<div
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-md"
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-md"
|
||||
>
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-muted text-2xl">
|
||||
{#if file.mimeType.startsWith('image/')}📷
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@
|
|||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
|
||||
{#each results.folders as folder (folder.id)}
|
||||
<button
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-md"
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-md"
|
||||
onclick={() => handleFolderClick(folder)}
|
||||
>
|
||||
<div
|
||||
|
|
@ -137,7 +137,7 @@
|
|||
{/each}
|
||||
{#each results.files as file (file.id)}
|
||||
<div
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-md"
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-md"
|
||||
>
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-muted text-2xl">
|
||||
{#if file.mimeType.startsWith('image/')}📷
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@
|
|||
{#each quickLinks as link}
|
||||
<a
|
||||
href={link.href}
|
||||
class="rounded-xl border border-border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-lg group"
|
||||
class="rounded-xl border border-border bg-card p-4 transition-[border-color,box-shadow] hover:border-primary/50 hover:shadow-lg group"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-3 text-center">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -311,7 +311,7 @@
|
|||
</div>
|
||||
<div class="mt-1 h-1.5 rounded-full bg-[hsl(var(--color-muted))]">
|
||||
<div
|
||||
class="h-full rounded-full transition-all {budgetPct > 90
|
||||
class="h-full rounded-full transition-[width] {budgetPct > 90
|
||||
? 'bg-red-500'
|
||||
: budgetPct > 75
|
||||
? 'bg-amber-500'
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@
|
|||
</div>
|
||||
<div class="mt-2 h-2.5 rounded-full bg-[hsl(var(--color-muted))]">
|
||||
<div
|
||||
class="h-full rounded-full transition-all {budgetPercent()! > 90
|
||||
class="h-full rounded-full transition-[width] {budgetPercent()! > 90
|
||||
? 'bg-red-500'
|
||||
: budgetPercent()! > 75
|
||||
? 'bg-amber-500'
|
||||
|
|
|
|||
|
|
@ -152,11 +152,11 @@
|
|||
</h3>
|
||||
<div class="flex h-4 overflow-hidden rounded-full">
|
||||
<div
|
||||
class="bg-[hsl(var(--color-primary))] transition-all"
|
||||
class="bg-[hsl(var(--color-primary))] transition-[width]"
|
||||
style="width: {(billableDuration / totalDuration) * 100}%"
|
||||
></div>
|
||||
<div
|
||||
class="bg-[hsl(var(--color-muted))] transition-all"
|
||||
class="bg-[hsl(var(--color-muted))] transition-[width]"
|
||||
style="width: {(nonBillableDuration / totalDuration) * 100}%"
|
||||
></div>
|
||||
</div>
|
||||
|
|
@ -189,7 +189,7 @@
|
|||
</div>
|
||||
<div class="mt-1 h-2 rounded-full bg-[hsl(var(--color-muted))]">
|
||||
<div
|
||||
class="h-full rounded-full transition-all"
|
||||
class="h-full rounded-full transition-[width]"
|
||||
style="width: {(item.duration / maxProjectDuration) *
|
||||
100}%; background-color: {item.color}"
|
||||
></div>
|
||||
|
|
@ -213,7 +213,7 @@
|
|||
<div class="flex flex-1 flex-col items-center gap-1">
|
||||
<div class="w-full flex flex-col justify-end" style="height: 100px;">
|
||||
<div
|
||||
class="w-full rounded-t bg-[hsl(var(--color-primary))] transition-all"
|
||||
class="w-full rounded-t bg-[hsl(var(--color-primary))] transition-[height]"
|
||||
style="height: {maxDailyDuration > 0
|
||||
? (day.duration / maxDailyDuration) * 100
|
||||
: 0}%"
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@
|
|||
</a>
|
||||
<button
|
||||
onclick={() => (showCreateForm = !showCreateForm)}
|
||||
class="rounded-lg bg-indigo-600 px-4 py-2 font-medium text-white shadow-lg transition-all hover:scale-105 hover:bg-indigo-700"
|
||||
class="rounded-lg bg-indigo-600 px-4 py-2 font-medium text-white shadow-lg transition-[transform,colors,box-shadow] hover:scale-105 hover:bg-indigo-700"
|
||||
>
|
||||
{showCreateForm ? '- Ausblenden' : '+ Neuer Link'}
|
||||
</button>
|
||||
|
|
@ -490,7 +490,7 @@
|
|||
<div class="space-y-3">
|
||||
{#each filteredLinks as link (link.id)}
|
||||
<div
|
||||
class="group rounded-xl border border-border-strong bg-white p-4 shadow-sm transition-all hover:shadow-md dark:border-border dark:bg-card"
|
||||
class="group rounded-xl border border-border-strong bg-white p-4 shadow-sm transition-colors hover:shadow-md dark:border-border dark:bg-card"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="min-w-0 flex-1">
|
||||
|
|
@ -545,7 +545,7 @@
|
|||
<div class="ml-4 flex items-center gap-1">
|
||||
<a
|
||||
href="/uload/analytics/{link.id}"
|
||||
class="flex items-center gap-1 rounded-lg px-2 py-1.5 text-sm font-medium opacity-60 transition-all hover:bg-muted hover:opacity-100 dark:hover:bg-muted"
|
||||
class="flex items-center gap-1 rounded-lg px-2 py-1.5 text-sm font-medium opacity-60 transition-colors hover:bg-muted hover:opacity-100 dark:hover:bg-muted"
|
||||
title="Analytics"
|
||||
>
|
||||
<ChartBar size={16} />
|
||||
|
|
@ -553,28 +553,28 @@
|
|||
</a>
|
||||
<button
|
||||
onclick={() => copyShortUrl(link.shortCode)}
|
||||
class="rounded-lg p-2 opacity-0 transition-all hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
|
||||
class="rounded-lg p-2 opacity-0 transition-colors hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
|
||||
title="Link kopieren"
|
||||
>
|
||||
<Copy size={16} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => (qrLink = link)}
|
||||
class="rounded-lg p-2 opacity-0 transition-all hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
|
||||
class="rounded-lg p-2 opacity-0 transition-colors hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
|
||||
title="QR-Code"
|
||||
>
|
||||
<QrCode size={16} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => openEdit(link)}
|
||||
class="rounded-lg p-2 opacity-0 transition-all hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
|
||||
class="rounded-lg p-2 opacity-0 transition-colors hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
|
||||
title={$_('common.edit')}
|
||||
>
|
||||
<PencilSimple size={16} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => toggleActive(link)}
|
||||
class="rounded-lg p-2 opacity-0 transition-all hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
|
||||
class="rounded-lg p-2 opacity-0 transition-colors hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
|
||||
title={link.isActive ? 'Deaktivieren' : 'Aktivieren'}
|
||||
>
|
||||
<Lightning
|
||||
|
|
@ -584,7 +584,7 @@
|
|||
</button>
|
||||
<button
|
||||
onclick={() => deleteLink(link)}
|
||||
class="rounded-lg p-2 opacity-0 transition-all hover:bg-red-50 hover:text-red-600 group-hover:opacity-100 dark:hover:bg-red-900/20"
|
||||
class="rounded-lg p-2 opacity-0 transition-colors hover:bg-red-50 hover:text-red-600 group-hover:opacity-100 dark:hover:bg-red-900/20"
|
||||
title="Loeschen"
|
||||
>
|
||||
<Trash size={16} />
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@
|
|||
<div class="space-y-3">
|
||||
{#each filteredLinks as link (link.id)}
|
||||
<div
|
||||
class="group rounded-xl border border-border-strong bg-white p-4 shadow-sm transition-all hover:shadow-md dark:border-border dark:bg-card"
|
||||
class="group rounded-xl border border-border-strong bg-white p-4 shadow-sm transition-colors hover:shadow-md dark:border-border dark:bg-card"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
{#if selectMode}
|
||||
|
|
@ -291,7 +291,7 @@
|
|||
<div class="ml-4 flex items-center gap-1">
|
||||
<a
|
||||
href="/uload/analytics/{link.id}"
|
||||
class="flex items-center gap-1 rounded-lg px-2 py-1.5 text-sm font-medium opacity-60 transition-all hover:bg-muted hover:opacity-100 dark:hover:bg-muted"
|
||||
class="flex items-center gap-1 rounded-lg px-2 py-1.5 text-sm font-medium opacity-60 transition-colors hover:bg-muted hover:opacity-100 dark:hover:bg-muted"
|
||||
title="Analytics"
|
||||
>
|
||||
<ChartBar size={16} />
|
||||
|
|
@ -299,14 +299,14 @@
|
|||
</a>
|
||||
<button
|
||||
onclick={() => copyShortUrl(link.shortCode)}
|
||||
class="rounded-lg p-2 opacity-0 transition-all hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
|
||||
class="rounded-lg p-2 opacity-0 transition-colors hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
|
||||
title="Link kopieren"
|
||||
>
|
||||
<Copy size={16} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => toggleActive(link)}
|
||||
class="rounded-lg p-2 opacity-0 transition-all hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
|
||||
class="rounded-lg p-2 opacity-0 transition-colors hover:bg-muted group-hover:opacity-100 dark:hover:bg-muted"
|
||||
title={link.isActive ? 'Deaktivieren' : 'Aktivieren'}
|
||||
>
|
||||
<Lightning
|
||||
|
|
@ -316,7 +316,7 @@
|
|||
</button>
|
||||
<button
|
||||
onclick={() => deleteLink(link)}
|
||||
class="rounded-lg p-2 opacity-0 transition-all hover:bg-red-50 hover:text-red-600 group-hover:opacity-100 dark:hover:bg-red-900/20"
|
||||
class="rounded-lg p-2 opacity-0 transition-colors hover:bg-red-50 hover:text-red-600 group-hover:opacity-100 dark:hover:bg-red-900/20"
|
||||
title="Loeschen"
|
||||
>
|
||||
<Trash size={16} />
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@
|
|||
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{#each tags.value as tag (tag.id)}
|
||||
<div
|
||||
class="group rounded-xl border border-border/10 bg-muted/5 p-4 transition-all hover:bg-muted/8"
|
||||
class="group rounded-xl border border-border/10 bg-muted/5 p-4 transition-colors hover:bg-muted/8"
|
||||
>
|
||||
{#if editingTag?.id === tag.id}
|
||||
<div class="space-y-3">
|
||||
|
|
@ -152,13 +152,13 @@
|
|||
<span class="text-sm text-muted-foreground">{getUsageCount(tag.id)} Links</span>
|
||||
<button
|
||||
onclick={() => (editingTag = { id: tag.id, name: tag.name, color: tag.color })}
|
||||
class="rounded p-1 text-muted-foreground opacity-0 transition-all hover:bg-muted/10 hover:text-white group-hover:opacity-100"
|
||||
class="rounded p-1 text-muted-foreground opacity-0 transition-colors hover:bg-muted/10 hover:text-white group-hover:opacity-100"
|
||||
>
|
||||
<PencilSimple size={16} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => deleteTag(tag)}
|
||||
class="rounded p-1 text-muted-foreground opacity-0 transition-all hover:bg-red-900/20 hover:text-red-400 group-hover:opacity-100"
|
||||
class="rounded p-1 text-muted-foreground opacity-0 transition-colors hover:bg-red-900/20 hover:text-red-400 group-hover:opacity-100"
|
||||
>
|
||||
<Trash size={16} />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@
|
|||
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{#each appConfig.features as feature}
|
||||
<div
|
||||
class="group cursor-default rounded-2xl border-2 border-gray-200 bg-white p-6 shadow-md transition-all duration-300 hover:scale-105 hover:shadow-xl dark:border-gray-700 dark:bg-gray-800"
|
||||
class="group cursor-default rounded-2xl border-2 border-gray-200 bg-white p-6 shadow-md transition-[transform,colors,box-shadow] duration-300 hover:scale-105 hover:shadow-xl dark:border-gray-700 dark:bg-gray-800"
|
||||
style="border-left: 4px solid {feature.color}"
|
||||
>
|
||||
<div class="mb-4 flex items-center gap-3">
|
||||
|
|
|
|||
175
scripts/migrate-transition-all.mjs
Normal file
175
scripts/migrate-transition-all.mjs
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* One-shot codemod: replace `transition-all` with specific transitions
|
||||
* based on what the element actually animates (derived from sibling
|
||||
* hover:/focus:/group-hover:/active: classes and the element's layout
|
||||
* role).
|
||||
*
|
||||
* Why: `transition-all` animates *every* property, including custom-
|
||||
* property-backed colours. On first paint, some CSS custom properties
|
||||
* haven't resolved yet, producing the P5 "white-on-white until first
|
||||
* interaction" rendering bug. Specific transitions also perf better
|
||||
* (no layout-property interpolation).
|
||||
*
|
||||
* Strategy: this script parses each `class="..."` attribute that
|
||||
* contains `transition-all` and picks one of:
|
||||
*
|
||||
* - `transition-opacity`
|
||||
* When the element only changes opacity (icon fade on group-hover).
|
||||
*
|
||||
* - `transition-[width]`
|
||||
* Progress bars — the element has `h-full rounded-full` pattern.
|
||||
*
|
||||
* - `transition-[transform,colors,box-shadow]`
|
||||
* Scaled buttons / cards (`hover:scale-*` or `active:scale-*`).
|
||||
*
|
||||
* - `transition-[border-color,box-shadow]`
|
||||
* Cards with hover:border + hover:shadow (no colour/bg change).
|
||||
*
|
||||
* - `transition-colors`
|
||||
* Default for everything else (most card/row hover states).
|
||||
*
|
||||
* Ambiguous cases stay as `transition-all` — review the remaining list
|
||||
* with `rg transition-all` and convert by hand.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/migrate-transition-all.mjs [--dry-run]
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const REPO_ROOT = join(__dirname, '..');
|
||||
const DRY_RUN = process.argv.includes('--dry-run');
|
||||
|
||||
const SCAN_GLOBS = ['apps/mana/apps/web/src/**/*.svelte'];
|
||||
|
||||
function listFiles() {
|
||||
const args = SCAN_GLOBS.map((g) => `"${g}"`).join(' ');
|
||||
const out = execSync(`git ls-files ${args}`, {
|
||||
cwd: REPO_ROOT,
|
||||
encoding: 'utf8',
|
||||
});
|
||||
return out
|
||||
.split('\n')
|
||||
.map((p) => p.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/** Decide the replacement for a transition-all occurrence based on sibling classes. */
|
||||
function pickReplacement(classes) {
|
||||
const has = (p) => classes.some((c) => p.test(c));
|
||||
|
||||
const hasScale = has(/:scale-/) || has(/^scale-/);
|
||||
const hasOpacity = has(/opacity-\d/) || has(/:opacity-\d/);
|
||||
const hasHoverBg = has(/(?:hover|focus|active|group-hover):bg-/);
|
||||
const hasHoverBorder = has(/(?:hover|focus|active|group-hover):border-/);
|
||||
const hasHoverShadow = has(/(?:hover|focus|active|group-hover):shadow-/);
|
||||
const hasHoverText = has(/(?:hover|focus|active|group-hover):text-/);
|
||||
// Progress bars: `h-full rounded-full` without any interactive variant.
|
||||
const isProgressBar =
|
||||
classes.includes('h-full') &&
|
||||
classes.includes('rounded-full') &&
|
||||
!hasScale &&
|
||||
!hasHoverBg &&
|
||||
!hasHoverBorder;
|
||||
|
||||
if (isProgressBar) return 'transition-[width]';
|
||||
if (hasScale) return 'transition-[transform,colors,box-shadow]';
|
||||
// Pure opacity fade (icon reveal on hover).
|
||||
if (hasOpacity && !hasHoverBg && !hasHoverBorder && !hasHoverText && !hasHoverShadow) {
|
||||
return 'transition-opacity';
|
||||
}
|
||||
// Card with border + shadow dance, no colour change.
|
||||
if (hasHoverBorder && hasHoverShadow && !hasHoverBg && !hasHoverText) {
|
||||
return 'transition-[border-color,box-shadow]';
|
||||
}
|
||||
// Any colour-ish interactive change.
|
||||
if (hasHoverBg || hasHoverBorder || hasHoverText || hasHoverShadow) {
|
||||
return 'transition-colors';
|
||||
}
|
||||
// No signal — leave as-is so the human can decide.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk each class="..." attribute (including class={...} template strings)
|
||||
* containing `transition-all` and rewrite it in place. Skips cases where
|
||||
* no deterministic replacement is found.
|
||||
*/
|
||||
function migrateSource(src) {
|
||||
let changes = 0;
|
||||
let unresolved = 0;
|
||||
|
||||
// Match `class="..."` and `class={"..."}` constructs. Keep simple —
|
||||
// we'll bail out if the value looks too complex to tokenise.
|
||||
const classAttrRe = /class\s*=\s*(["'`])([\s\S]*?)\1/g;
|
||||
|
||||
const out = src.replace(classAttrRe, (full, quote, value) => {
|
||||
if (!value.includes('transition-all')) return full;
|
||||
|
||||
// Tokenise on whitespace — good enough for Svelte class attributes
|
||||
// that embed `{expr}` fragments; those stay opaque and we just
|
||||
// skip them as a single token, which is fine because we only read
|
||||
// known static classes.
|
||||
const classes = value
|
||||
.split(/\s+/)
|
||||
.map((t) => t.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (!classes.some((c) => c === 'transition-all')) {
|
||||
// `transition-all duration-300` etc. — remove the duration
|
||||
// handling and just match the token itself.
|
||||
return full;
|
||||
}
|
||||
|
||||
const replacement = pickReplacement(classes);
|
||||
if (!replacement) {
|
||||
unresolved++;
|
||||
return full;
|
||||
}
|
||||
|
||||
const newClasses = classes.map((c) => (c === 'transition-all' ? replacement : c));
|
||||
changes++;
|
||||
return `class=${quote}${newClasses.join(' ')}${quote}`;
|
||||
});
|
||||
|
||||
return { out, changes, unresolved };
|
||||
}
|
||||
|
||||
function migrate() {
|
||||
const paths = listFiles();
|
||||
let totalChanges = 0;
|
||||
let totalUnresolved = 0;
|
||||
let changedFiles = 0;
|
||||
|
||||
for (const rel of paths) {
|
||||
const abs = join(REPO_ROOT, rel);
|
||||
const src = readFileSync(abs, 'utf8');
|
||||
if (!src.includes('transition-all')) continue;
|
||||
|
||||
const { out, changes, unresolved } = migrateSource(src);
|
||||
totalChanges += changes;
|
||||
totalUnresolved += unresolved;
|
||||
|
||||
if (changes > 0) {
|
||||
changedFiles++;
|
||||
if (!DRY_RUN) writeFileSync(abs, out, 'utf8');
|
||||
console.log(` ${String(changes).padStart(3)} → (${unresolved} left) ${rel}`);
|
||||
} else if (unresolved > 0) {
|
||||
console.log(` ${'·'.padStart(3)} (${unresolved} left) ${rel}`);
|
||||
}
|
||||
}
|
||||
|
||||
const verb = DRY_RUN ? 'Would migrate' : 'Migrated';
|
||||
console.log(
|
||||
`\n${verb} ${totalChanges} transition-all → specific, ` +
|
||||
`${totalUnresolved} left ambiguous across ${changedFiles} file(s).`
|
||||
);
|
||||
if (DRY_RUN) console.log('Run without --dry-run to apply.');
|
||||
}
|
||||
|
||||
migrate();
|
||||
Loading…
Add table
Add a link
Reference in a new issue