From 104ffc4f1df0b616e6e4df85db16b3f3502cefd9 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 10 Jun 2026 09:56:41 +0000 Subject: [PATCH] feat: compact task rows + bottom-sheet list picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tasks as dense divided rows: title clamps to 2 lines, note to 1; tap a row to expand the full text (was: full-height cards → heavy scroll) - List switcher moved from top chip row into the thumb zone: a "List" pill in the dock opens a bottom sheet with all lists (52px rows, active check) - Masthead title now shows the selected list; compacted header spacing Co-Authored-By: Claude Opus 4.8 --- app/pages/index.vue | 413 ++++++++++++++++++++++++++++++++------------ 1 file changed, 298 insertions(+), 115 deletions(-) diff --git a/app/pages/index.vue b/app/pages/index.vue index 18543a4..88138c6 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -28,6 +28,9 @@ const showNote = ref(false); const adding = ref(false); const titleInput = ref(null); +const sheetOpen = ref(false); +const expandedId = ref(null); + const selectedList = computed(() => lists.value.find((l) => l.id === selectedId.value) ?? null); const today = new Intl.DateTimeFormat(undefined, { @@ -53,6 +56,8 @@ async function refreshLists() { async function selectList(id: string) { selectedId.value = id; + sheetOpen.value = false; + expandedId.value = null; await refreshTasks(); } @@ -101,6 +106,10 @@ function toggleNote() { if (!showNote.value) description.value = ""; } +function toggleTask(t: Task) { + expandedId.value = expandedId.value === t.id ? null : t.id; +} + onMounted(() => { // The auth plugin gates before mount, so this is normally authenticated. // Safety net: if not, drive login instead of calling the API (no 401 banner). @@ -119,7 +128,7 @@ onMounted(() => { -

Inbox

+

{{ selectedList?.name ?? "Inbox" }}

{{ today }}

@@ -139,18 +148,6 @@ onMounted(() => { -
- -
- +
+ + + - + + +
+
+ + + +
+ + +
- + +
@@ -230,9 +268,9 @@ onMounted(() => { -webkit-tap-highlight-color: transparent; } -/* ——— Masthead: editorial, mobile-first ——— */ +/* ——— Masthead: compact, title = current list ——— */ .masthead { - padding: max(1rem, env(safe-area-inset-top)) 1.25rem 0.75rem; + padding: max(0.85rem, env(safe-area-inset-top)) 1.25rem 0.6rem; } .masthead-row { display: flex; @@ -249,16 +287,19 @@ onMounted(() => { color: var(--accent); } .masthead h1 { - margin: 0.4rem 0 0; + margin: 0.35rem 0 0; font-family: "Fraunces", Georgia, serif; font-weight: 600; - font-size: clamp(2.1rem, 9vw, 2.75rem); + font-size: clamp(1.6rem, 7vw, 2.1rem); line-height: 1.05; letter-spacing: -0.02em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .date { - margin: 0.3rem 0 0; - font-size: 0.85rem; + margin: 0.25rem 0 0; + font-size: 0.8rem; color: var(--muted); } .who { @@ -292,7 +333,7 @@ onMounted(() => { .content { flex: 1; - padding: 0.5rem 1.25rem 1rem; + padding: 0.25rem 1.25rem 1rem; display: flex; flex-direction: column; gap: 0.9rem; @@ -300,88 +341,82 @@ onMounted(() => { overscroll-behavior-y: contain; } -/* ——— List chips: edge-to-edge swipe row ——— */ -.lists { - display: flex; - gap: 0.5rem; - overflow-x: auto; - margin: 0 -1.25rem; - padding: 0.25rem 1.25rem; - scrollbar-width: none; - scroll-snap-type: x proximity; -} -.lists::-webkit-scrollbar { - display: none; -} -.chip { - flex: 0 0 auto; - scroll-snap-align: start; - min-height: 44px; - padding: 0.55rem 1.1rem; - border-radius: 999px; - border: 1px solid var(--border); - background: var(--card); - color: var(--text); - font-family: inherit; - font-size: 0.9rem; - font-weight: 600; - cursor: pointer; - touch-action: manipulation; - transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.1s; -} -.chip:active { - transform: scale(0.96); -} -.chip.active { - background: var(--accent); - border-color: var(--accent); - color: var(--accent-contrast); -} - -/* ——— Tasks: paper cards with staggered entrance ——— */ +/* ——— Tasks: dense divided rows, tap to expand ——— */ .task-list { list-style: none; margin: 0; padding: 0; - display: flex; - flex-direction: column; - gap: 0.6rem; -} -.task { background: var(--card); border: 1px solid var(--border); border-radius: 14px; - padding: 0.9rem 1rem; box-shadow: var(--shadow); - animation: rise 0.35s cubic-bezier(0.2, 0.7, 0.3, 1) both; - animation-delay: calc(var(--i, 0) * 35ms); + overflow: hidden; +} +.task-list li { + animation: rise 0.3s cubic-bezier(0.2, 0.7, 0.3, 1) both; + animation-delay: calc(var(--i, 0) * 25ms); +} +.task-list li + li { + border-top: 1px solid var(--border); } @keyframes rise { from { opacity: 0; - transform: translateY(10px); + transform: translateY(8px); } to { opacity: 1; transform: none; } } +.task { + display: block; + width: 100%; + min-height: 48px; + padding: 0.65rem 0.95rem; + border: 0; + background: none; + color: inherit; + font: inherit; + text-align: left; + cursor: pointer; + touch-action: manipulation; +} +.task:active { + background: color-mix(in srgb, var(--accent-soft) 45%, transparent); +} .task-title { margin: 0; - font-size: 1rem; + font-size: 0.95rem; font-weight: 500; line-height: 1.35; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; } .task-desc { - margin: 0.35rem 0 0; - font-size: 0.875rem; + margin: 0.2rem 0 0; + font-size: 0.8rem; color: var(--muted); line-height: 1.45; white-space: pre-wrap; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow: hidden; +} +.task.open .task-title, +.task.open .task-desc { + -webkit-line-clamp: unset; + display: block; +} +.task.open { + background: color-mix(in srgb, var(--accent-soft) 30%, transparent); } .empty { - margin-top: 14vh; + margin-top: 12vh; text-align: center; padding: 0 1.5rem; } @@ -412,19 +447,64 @@ onMounted(() => { margin: 0; } -/* ——— Composer: thumb-zone capture bar ——— */ -.composer { +/* ——— Dock: list switcher + capture, all in the thumb zone ——— */ +.dock { position: sticky; bottom: 0; + z-index: 10; display: flex; flex-direction: column; gap: 0.5rem; - padding: 0.65rem 1rem calc(0.65rem + env(safe-area-inset-bottom)); + padding: 0.6rem 1rem calc(0.6rem + env(safe-area-inset-bottom)); background: color-mix(in srgb, var(--bg) 88%, transparent); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-top: 1px solid var(--border); } +.list-pill { + display: flex; + align-items: center; + gap: 0.5rem; + align-self: flex-start; + max-width: 100%; + min-height: 40px; + padding: 0.4rem 0.9rem; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--card); + color: var(--text); + font-family: inherit; + font-size: 0.85rem; + font-weight: 600; + cursor: pointer; + touch-action: manipulation; + box-shadow: var(--shadow); +} +.pill-label { + font-size: 0.65rem; + font-weight: 600; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--muted); +} +.pill-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.pill-chevron { + color: var(--accent); + transition: transform 0.2s; +} +.pill-chevron.up { + transform: rotate(180deg); +} + +.composer { + display: flex; + flex-direction: column; + gap: 0.5rem; +} .capture { display: flex; align-items: center; @@ -510,28 +590,128 @@ onMounted(() => { } } +/* ——— Bottom sheet: list picker in the thumb zone ——— */ +.backdrop { + position: fixed; + inset: 0; + z-index: 20; + background: rgb(20 14 8 / 0.35); +} +.sheet { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 21; + max-width: 28rem; + margin: 0 auto; + max-height: 70dvh; + overflow-y: auto; + overscroll-behavior: contain; + background: var(--card); + border: 1px solid var(--border); + border-bottom: 0; + border-radius: 20px 20px 0 0; + padding: 0.5rem 0.75rem calc(0.75rem + env(safe-area-inset-bottom)); + box-shadow: 0 -8px 32px rgb(20 14 8 / 0.15); +} +.sheet-handle { + width: 40px; + height: 4px; + border-radius: 2px; + background: var(--border); + margin: 0.25rem auto 0.5rem; +} +.sheet-title { + margin: 0 0 0.25rem; + padding: 0 0.5rem; + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 0.15em; + text-transform: uppercase; + color: var(--muted); +} +.sheet-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + width: 100%; + min-height: 52px; + padding: 0.7rem 0.85rem; + border: 0; + border-radius: 12px; + background: none; + color: var(--text); + font-family: inherit; + font-size: 1rem; + font-weight: 500; + text-align: left; + cursor: pointer; + touch-action: manipulation; +} +.sheet-item:active { + background: var(--accent-soft); +} +.sheet-item.active { + color: var(--accent); + font-weight: 600; +} +.sheet-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.sheet-check { + color: var(--accent); +} + +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.2s; +} +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} +.slide-enter-active, +.slide-leave-active { + transition: transform 0.25s cubic-bezier(0.2, 0.7, 0.3, 1); +} +.slide-enter-from, +.slide-leave-to { + transform: translateY(100%); +} + /* ——— Larger screens: the enhancement, not the default ——— */ @media (min-width: 640px) { .masthead, .content, - .composer { + .dock { padding-left: max(1.25rem, calc(50vw - 19rem)); padding-right: max(1.25rem, calc(50vw - 19rem)); } .email { display: inline; } - .lists { - margin: 0; - padding: 0.25rem 0; + .sheet { + border-radius: 20px; + bottom: 1rem; + border-bottom: 1px solid var(--border); } } @media (prefers-reduced-motion: reduce) { - .task, + .task-list li, .input.note { animation: none; } + .slide-enter-active, + .slide-leave-active, + .fade-enter-active, + .fade-leave-active { + transition: none; + } } @media (prefers-color-scheme: dark) { @@ -551,5 +731,8 @@ onMounted(() => { border-color: #5a2a22; color: #ff9a8d; } + .backdrop { + background: rgb(0 0 0 / 0.5); + } }