feat(todo-web): redesign task list as physical notepad

Adds notepad container with cream paper background, spiral binding holes,
red margin line, and lined rows instead of card-style task items.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-23 20:16:40 +01:00
parent 43131986a9
commit 97d6787d40
3 changed files with 221 additions and 122 deletions

View file

@ -618,12 +618,13 @@
align-items: center;
gap: 0.625rem;
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(0, 0, 0, 0.08);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 0;
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
border: none;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
box-shadow: none;
transition: all 0.2s;
}
@ -641,19 +642,18 @@
}
:global(.dark) .task-item {
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.15);
background: transparent;
border: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.task-item:hover {
background: rgba(255, 255, 255, 0.95);
border-color: rgba(0, 0, 0, 0.12);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
background: rgba(0, 0, 0, 0.02);
box-shadow: none;
}
:global(.dark) .task-item:hover {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.25);
background: rgba(255, 255, 255, 0.04);
}
.task-item-wrapper.expanded .task-item:hover {
@ -667,13 +667,13 @@
/* Completing animation */
.task-item.completing {
background: rgba(34, 197, 94, 0.15);
border-color: rgba(34, 197, 94, 0.3);
background: rgba(34, 197, 94, 0.08);
border-bottom-color: rgba(34, 197, 94, 0.2);
}
:global(.dark) .task-item.completing {
background: rgba(34, 197, 94, 0.2);
border-color: rgba(34, 197, 94, 0.4);
background: rgba(34, 197, 94, 0.12);
border-bottom-color: rgba(34, 197, 94, 0.3);
}
/* Drag handle */

View file

@ -193,12 +193,16 @@
<style>
.task-list {
min-height: 60px;
min-height: 40px;
padding: 0;
border-radius: 0.5rem;
border-radius: 0;
transition: background-color 0.15s ease;
}
.task-list :global(.task-item-wrapper:last-child .task-item) {
border-bottom: none;
}
.task-list.empty {
border: 2px dashed rgba(0, 0, 0, 0.15);
display: flex;

View file

@ -185,116 +185,128 @@
</div>
</div>
{:else}
<div class="space-y-2">
<!-- Overdue Section - only show if there are overdue tasks -->
{#if overdueTasks.length > 0}
<CollapsibleSection
title="Überfällig"
count={overdueTasks.length}
icon="warning"
variant="warning"
defaultOpen={true}
>
<TaskList
tasks={overdueTasks}
enableDragDrop
dropTargetDate="overdue"
onTaskDrop={handleTaskDrop}
/>
</CollapsibleSection>
{/if}
<div class="notepad">
<div class="notepad-holes" aria-hidden="true">
<span class="hole"></span>
<span class="hole"></span>
<span class="hole"></span>
<span class="hole"></span>
<span class="hole"></span>
<span class="hole"></span>
</div>
<div class="notepad-content">
<div class="space-y-2">
<!-- Overdue Section - only show if there are overdue tasks -->
{#if overdueTasks.length > 0}
<CollapsibleSection
title="Überfällig"
count={overdueTasks.length}
icon="warning"
variant="warning"
defaultOpen={true}
>
<TaskList
tasks={overdueTasks}
enableDragDrop
dropTargetDate="overdue"
onTaskDrop={handleTaskDrop}
/>
</CollapsibleSection>
{/if}
<!-- Today Section - always visible when there are any tasks -->
{#if showTodaySection}
<CollapsibleSection
title="Heute"
count={todayTasks.length}
icon="today"
variant="default"
defaultOpen={true}
>
<TaskList
tasks={todayTasks}
enableDragDrop
dropTargetDate={startOfDay(new Date())}
onTaskDrop={handleTaskDrop}
/>
</CollapsibleSection>
{/if}
<!-- Today Section - always visible when there are any tasks -->
{#if showTodaySection}
<CollapsibleSection
title="Heute"
count={todayTasks.length}
icon="today"
variant="default"
defaultOpen={true}
>
<TaskList
tasks={todayTasks}
enableDragDrop
dropTargetDate={startOfDay(new Date())}
onTaskDrop={handleTaskDrop}
/>
</CollapsibleSection>
{/if}
<!-- Tomorrow Section - only show if there are tasks -->
{#if showTomorrowSection}
<CollapsibleSection
title="Morgen"
count={tomorrowTasks.length}
icon="upcoming"
variant="default"
defaultOpen={true}
>
<TaskList
tasks={tomorrowTasks}
enableDragDrop
dropTargetDate={tomorrowDate}
onTaskDrop={handleTaskDrop}
/>
</CollapsibleSection>
{/if}
<!-- Tomorrow Section - only show if there are tasks -->
{#if showTomorrowSection}
<CollapsibleSection
title="Morgen"
count={tomorrowTasks.length}
icon="upcoming"
variant="default"
defaultOpen={true}
>
<TaskList
tasks={tomorrowTasks}
enableDragDrop
dropTargetDate={tomorrowDate}
onTaskDrop={handleTaskDrop}
/>
</CollapsibleSection>
{/if}
<!-- Upcoming Section - only show if there are tasks -->
{#if showUpcomingSection}
<CollapsibleSection
title="Demnächst"
count={upcomingCount}
icon="upcoming"
variant="default"
defaultOpen={true}
>
<div class="space-y-4">
{#each groupedUpcomingTasks() as group}
<div>
<h3 class="text-sm font-medium text-muted-foreground mb-2 pl-2">
{group.label} ({group.tasks.length})
</h3>
<TaskList
tasks={group.tasks}
enableDragDrop
dropTargetDate={group.date}
onTaskDrop={handleTaskDrop}
/>
<!-- Upcoming Section - only show if there are tasks -->
{#if showUpcomingSection}
<CollapsibleSection
title="Demnächst"
count={upcomingCount}
icon="upcoming"
variant="default"
defaultOpen={true}
>
<div class="space-y-4">
{#each groupedUpcomingTasks() as group}
<div>
<h3 class="text-sm font-medium text-muted-foreground mb-2 pl-2">
{group.label} ({group.tasks.length})
</h3>
<TaskList
tasks={group.tasks}
enableDragDrop
dropTargetDate={group.date}
onTaskDrop={handleTaskDrop}
/>
</div>
{/each}
</div>
{/each}
</div>
</CollapsibleSection>
{/if}
</CollapsibleSection>
{/if}
<!-- Completed Section - only show if there are completed tasks -->
{#if showCompletedSection}
<CollapsibleSection
title="Erledigt"
count={completedTasks.length}
icon="completed"
variant="success"
defaultOpen={true}
>
<TaskList
tasks={completedTasks}
enableDragDrop
dropTargetDate="completed"
onTaskDrop={handleTaskDrop}
showCompleted
/>
</CollapsibleSection>
{/if}
<!-- Completed Section - only show if there are completed tasks -->
{#if showCompletedSection}
<CollapsibleSection
title="Erledigt"
count={completedTasks.length}
icon="completed"
variant="success"
defaultOpen={true}
>
<TaskList
tasks={completedTasks}
enableDragDrop
dropTargetDate="completed"
onTaskDrop={handleTaskDrop}
showCompleted
/>
</CollapsibleSection>
{/if}
<!-- Onboarding tip for users with 1-3 tasks -->
{#if showOnboardingTip}
<div class="onboarding-tip">
<span class="onboarding-tip-icon">💡</span>
<span class="onboarding-tip-text">
Tipp: Nutze <code>#tags</code> und <code>!priorität</code> für bessere Organisation
</span>
<!-- Onboarding tip for users with 1-3 tasks -->
{#if showOnboardingTip}
<div class="onboarding-tip">
<span class="onboarding-tip-icon">💡</span>
<span class="onboarding-tip-text">
Tipp: Nutze <code>#tags</code> und <code>!priorität</code> für bessere Organisation
</span>
</div>
{/if}
</div>
{/if}
</div>
</div>
{/if}
</div>
@ -447,4 +459,87 @@
border-radius: 0.25rem;
color: hsl(var(--color-primary));
}
/* Notepad container */
.notepad {
max-width: 560px;
margin: 0 auto;
background: #fffef5;
border-radius: 0.5rem 0.5rem 0.75rem 0.75rem;
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.08),
0 1px 2px rgba(0, 0, 0, 0.04);
position: relative;
padding-top: 1.25rem;
}
:global(.dark) .notepad {
background: #2a2520;
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.3),
0 1px 2px rgba(0, 0, 0, 0.2);
}
/* Spiral binding holes */
.notepad-holes {
position: absolute;
top: -6px;
left: 10%;
right: 10%;
display: flex;
justify-content: space-evenly;
pointer-events: none;
z-index: 2;
}
.hole {
width: 14px;
height: 14px;
border-radius: 50%;
background: hsl(var(--color-background, 0 0% 100%));
border: 2px solid #c4c4c4;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
}
:global(.dark) .hole {
background: hsl(var(--color-background, 0 0% 7%));
border-color: #555;
}
/* Notepad content with red margin line */
.notepad-content {
position: relative;
padding: 0.75rem 1rem 1.5rem 3.25rem;
min-height: 200px;
}
.notepad-content::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 2.5rem;
width: 2px;
background: #e8b4b8;
z-index: 1;
}
:global(.dark) .notepad-content::before {
background: rgba(232, 180, 184, 0.4);
}
@media (max-width: 640px) {
.notepad {
max-width: 100%;
border-radius: 0;
}
.notepad-content {
padding-left: 2.75rem;
}
.notepad-content::before {
left: 2rem;
}
}
</style>