v0.1.33 - 모바일 플래너 본문 레이아웃 개선

This commit is contained in:
2026-04-22 17:29:07 +09:00
parent c372f325ab
commit fa09a13e93
4 changed files with 51 additions and 47 deletions

View File

@@ -4,7 +4,7 @@
- 프로젝트명: 10 Minute Planner 웹 UI - 프로젝트명: 10 Minute Planner 웹 UI
- 기술 스택: Vue 3 + Vite + TailwindCSS + JavaScript - 기술 스택: Vue 3 + Vite + TailwindCSS + JavaScript
- 현재 기준 버전: `v0.1.32` 준비 중 - 현재 기준 버전: `v0.1.33` 준비 중
- Git 원격 저장소: `https://git.sori.studio/zenn/planner.sori.studio.git` - Git 원격 저장소: `https://git.sori.studio/zenn/planner.sori.studio.git`
## 기준 디자인 ## 기준 디자인
@@ -178,6 +178,8 @@
- 통계 화면과 우측 `FOCUSED TIME` 요약처럼 사용자에게 보여주는 집중 시간 표기는 `00H 00M` 대신 `00시간 00분` 한글 형식으로 바꿨다. - 통계 화면과 우측 `FOCUSED TIME` 요약처럼 사용자에게 보여주는 집중 시간 표기는 `00H 00M` 대신 `00시간 00분` 한글 형식으로 바꿨다.
- 좌측 메뉴 드로어와 우측 정보 패널 오버레이는 이제 열고 닫힐 때 페이드 + 슬라이드 애니메이션이 적용된다. - 좌측 메뉴 드로어와 우측 정보 패널 오버레이는 이제 열고 닫힐 때 페이드 + 슬라이드 애니메이션이 적용된다.
- 모바일처럼 좁은 화면에서는 본문 래퍼 패딩을 조금 줄이고, 우측 패널 열기 버튼 문구를 `INFO`로 축약해 밀도를 낮췄다. - 모바일처럼 좁은 화면에서는 본문 래퍼 패딩을 조금 줄이고, 우측 패널 열기 버튼 문구를 `INFO`로 축약해 밀도를 낮췄다.
- 플래너 본문은 작은 화면에서 상단 정보 영역이 세로로 쌓이고, `TIME TABLE`이 아래로 내려가도록 조정했다.
- 모바일 구간에서는 TASKS / MEMO 행 높이와 좌우 패딩을 조금 줄여 입력 밀도를 낮췄고, 타임테이블은 필요할 때만 최소 가로 스크롤이 생기도록 바뀌었다.
- 비로그인 랜딩 카드는 상단 고정이 아니라 화면 중앙에 오도록 정렬을 수정했다. - 비로그인 랜딩 카드는 상단 고정이 아니라 화면 중앙에 오도록 정렬을 수정했다.
- 현재 환경에서는 Docker 데몬이 꺼져 있어서 `docker compose build` 실검증은 하지 못했고, 데몬 시작 후 다시 확인이 필요하다. - 현재 환경에서는 Docker 데몬이 꺼져 있어서 `docker compose build` 실검증은 하지 못했고, 데몬 시작 후 다시 확인이 필요하다.
- 이미지 저장 기능은 추후 `print-only` 또는 별도 export 전용 레이아웃을 기준으로 구현하면 화면/인쇄/공유 결과를 맞추기 쉽다. - 이미지 저장 기능은 추후 `print-only` 또는 별도 export 전용 레이아웃을 기준으로 구현하면 화면/인쇄/공유 결과를 맞추기 쉽다.

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "ten-minute-planner", "name": "ten-minute-planner",
"version": "0.1.32", "version": "0.1.33",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ten-minute-planner", "name": "ten-minute-planner",
"version": "0.1.32", "version": "0.1.33",
"dependencies": { "dependencies": {
"vue": "^3.5.13" "vue": "^3.5.13"
}, },

View File

@@ -1,7 +1,7 @@
{ {
"name": "ten-minute-planner", "name": "ten-minute-planner",
"private": true, "private": true,
"version": "0.1.32", "version": "0.1.33",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -119,52 +119,52 @@ onBeforeUnmount(() => {
<template> <template>
<article <article
class="planner-sheet flex w-full max-w-[762px] flex-col gap-3 bg-paper px-6 py-6 text-[10px] font-bold tracking-[0.16em] text-ink shadow-paper sm:px-12 sm:py-12" class="planner-sheet flex w-full max-w-[762px] flex-col gap-3 bg-paper px-4 py-4 text-[10px] font-bold tracking-[0.16em] text-ink shadow-paper sm:px-8 sm:py-8 lg:px-12 lg:py-12"
> >
<div class="flex flex-col gap-4 py-[18px]"> <div class="flex flex-col gap-4 py-3 sm:py-[18px]">
<div class="flex gap-4"> <div class="flex flex-col gap-3 sm:gap-4" :class="props.showDday ? 'sm:flex-row' : ''">
<div class="relative h-[90px] border-t border-ink px-[10px] pt-[10px]" :class="props.showDday ? 'w-[394px] flex-1' : 'w-full flex-1'"> <div class="relative min-h-[82px] border-t border-ink px-[10px] pt-[10px]" :class="props.showDday ? 'w-full sm:w-[394px] sm:flex-1' : 'w-full flex-1'">
<span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">YEAR / MONTH / DAY</span> <span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">YEAR / MONTH / DAY</span>
<p class="pt-6 text-xs tracking-[0.24em] text-ink sm:text-sm"> <p class="pt-5 text-[11px] tracking-[0.2em] text-ink sm:pt-6 sm:text-sm">
<span>{{ dateMain }}</span> <span>{{ dateMain }}</span>
<span class="ml-1" :class="dateWeekdayTone">{{ dateWeekday }}</span> <span class="ml-1" :class="dateWeekdayTone">{{ dateWeekday }}</span>
</p> </p>
</div> </div>
<div v-if="props.showDday" class="relative h-[90px] w-[210px] border-t border-ink px-[10px] pt-[10px]"> <div v-if="props.showDday" class="relative min-h-[82px] w-full border-t border-ink px-[10px] pt-[10px] sm:w-[210px]">
<span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">D-DAY</span> <span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">D-DAY</span>
<p class="pt-6 text-xs tracking-[0.24em] text-ink sm:text-sm">{{ dday }}</p> <p class="pt-5 text-[11px] tracking-[0.2em] text-ink sm:pt-6 sm:text-sm">{{ dday }}</p>
</div> </div>
</div> </div>
<div class="flex gap-4 border-b border-ink pb-[18px]"> <div class="flex flex-col gap-3 border-b border-ink pb-3 sm:gap-4 sm:pb-[18px] lg:flex-row">
<div class="relative h-[90px] w-[394px] flex-1 border-t border-ink px-[10px] pt-[10px]"> <div class="relative min-h-[82px] w-full flex-1 border-t border-ink px-[10px] pt-[10px] lg:w-[394px]">
<span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">COMMENT</span> <span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">COMMENT</span>
<textarea <textarea
:value="comment" :value="comment"
rows="3" rows="3"
class="mt-4 h-[56px] w-full resize-none bg-transparent pt-2 text-[11px] font-semibold normal-case tracking-[0.08em] text-stone-700 outline-none placeholder:text-stone-400 sm:text-xs" class="mt-3 h-[54px] w-full resize-none bg-transparent pt-2 text-[11px] font-semibold normal-case tracking-[0.06em] text-stone-700 outline-none placeholder:text-stone-400 sm:mt-4 sm:text-xs"
placeholder="오늘의 코멘트를 적어 주세요." placeholder="오늘의 코멘트를 적어 주세요."
@input="emit('update:comment', $event.target.value)" @input="emit('update:comment', $event.target.value)"
/> />
</div> </div>
<div class="relative h-[90px] w-[210px] border-t border-ink px-[10px] pt-[10px]"> <div class="relative min-h-[82px] w-full border-t border-ink px-[10px] pt-[10px] lg:w-[210px]">
<span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">TOTAL TIME</span> <span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">TOTAL TIME</span>
<p class="pt-6 text-xs tracking-[0.24em] text-ink sm:text-sm">{{ totalTime }}</p> <p class="pt-5 text-[11px] tracking-[0.2em] text-ink sm:pt-6 sm:text-sm">{{ totalTime }}</p>
</div> </div>
</div> </div>
</div> </div>
<div class="flex gap-4 py-[10px]"> <div class="flex flex-col gap-5 py-[10px] lg:flex-row lg:gap-4">
<div class="flex w-[394px] flex-1 flex-col gap-9"> <div class="flex w-full flex-1 flex-col gap-7 sm:gap-9 lg:w-[394px]">
<section class="relative"> <section class="relative">
<div class="absolute -top-[9px] left-0 bg-paper px-[2px] text-muted">TASKS</div> <div class="absolute -top-[9px] left-0 bg-paper px-[2px] text-muted">TASKS</div>
<div class="border-t border-ink"> <div class="border-t border-ink">
<div <div
v-for="(task, index) in tasks" v-for="(task, index) in tasks"
:key="task.id" :key="task.id"
class="flex h-[38px] items-center border-b" class="flex min-h-[38px] items-center border-b"
:class="index % 5 === 4 || index === tasks.length - 1 ? 'border-ink' : 'border-line'" :class="index % 5 === 4 || index === tasks.length - 1 ? 'border-ink' : 'border-line'"
> >
<div class="h-full w-[62px] border-r border-dashed border-ink px-2 py-[7px]"> <div class="h-full w-[52px] shrink-0 border-r border-dashed border-ink px-1.5 py-[7px] sm:w-[62px] sm:px-2">
<input <input
:value="task.label" :value="task.label"
type="text" type="text"
@@ -172,16 +172,16 @@ onBeforeUnmount(() => {
@input="emit('update:task-label', { index, value: $event.target.value })" @input="emit('update:task-label', { index, value: $event.target.value })"
/> />
</div> </div>
<div class="flex min-w-0 flex-1 items-center px-3"> <div class="flex min-w-0 flex-1 items-center px-2 sm:px-3">
<input <input
:value="task.title" :value="task.title"
type="text" type="text"
class="w-full truncate bg-transparent text-[11px] font-semibold normal-case tracking-[0.06em] text-stone-800 outline-none placeholder:text-stone-400" class="w-full truncate bg-transparent text-[10px] font-semibold normal-case tracking-[0.04em] text-stone-800 outline-none placeholder:text-stone-400 sm:text-[11px] sm:tracking-[0.06em]"
:placeholder="shouldShowTaskPlaceholder(index) ? '할 일을 입력해 주세요.' : ''" :placeholder="shouldShowTaskPlaceholder(index) ? '할 일을 입력해 주세요.' : ''"
@input="emit('update:task-title', { index, value: $event.target.value })" @input="emit('update:task-title', { index, value: $event.target.value })"
/> />
</div> </div>
<div class="flex h-full w-[42px] items-center justify-center p-[10px]"> <div class="flex h-full w-[36px] shrink-0 items-center justify-center p-[8px] sm:w-[42px] sm:p-[10px]">
<button <button
type="button" type="button"
class="flex h-full w-full items-center justify-center border border-dashed transition" class="flex h-full w-full items-center justify-center border border-dashed transition"
@@ -201,10 +201,10 @@ onBeforeUnmount(() => {
<div <div
v-for="(memoItem, index) in memo" v-for="(memoItem, index) in memo"
:key="`memo-${index}`" :key="`memo-${index}`"
class="flex h-[38px] items-center border-b" class="flex min-h-[38px] items-center border-b"
:class="index === memo.length - 1 ? 'border-ink' : 'border-line'" :class="index === memo.length - 1 ? 'border-ink' : 'border-line'"
> >
<div class="h-full w-[62px] border-r border-dashed border-ink px-2 py-[7px]"> <div class="h-full w-[52px] shrink-0 border-r border-dashed border-ink px-1.5 py-[7px] sm:w-[62px] sm:px-2">
<input <input
:value="memoItem.label" :value="memoItem.label"
type="text" type="text"
@@ -212,11 +212,11 @@ onBeforeUnmount(() => {
@input="emit('update:memo-label', { index, value: $event.target.value })" @input="emit('update:memo-label', { index, value: $event.target.value })"
/> />
</div> </div>
<div class="flex flex-1 items-center px-3"> <div class="flex flex-1 items-center px-2 sm:px-3">
<input <input
:value="memoItem.text" :value="memoItem.text"
type="text" type="text"
class="w-full bg-transparent text-[11px] font-semibold normal-case tracking-[0.06em] text-stone-700 outline-none placeholder:text-stone-400" class="w-full bg-transparent text-[10px] font-semibold normal-case tracking-[0.04em] text-stone-700 outline-none placeholder:text-stone-400 sm:text-[11px] sm:tracking-[0.06em]"
@input="emit('update:memo', { index, value: $event.target.value })" @input="emit('update:memo', { index, value: $event.target.value })"
/> />
</div> </div>
@@ -225,30 +225,32 @@ onBeforeUnmount(() => {
</section> </section>
</div> </div>
<section class="relative w-[210px] shrink-0"> <section class="relative w-full shrink-0 lg:w-[210px]">
<div class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">TIME TABLE</div> <div class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">TIME TABLE</div>
<div class="border-t border-ink"> <div class="overflow-x-auto pb-1">
<div <div class="min-w-[210px] border-t border-ink">
v-for="(hour, index) in hours"
:key="`${hour}-${index}`"
class="flex h-[30px] border-b"
:class="index === hours.length - 1 ? 'border-ink' : 'border-line'"
>
<div <div
class="flex h-full w-[30px] touch-none select-none items-center justify-center border-r border-ink text-[9px] text-ink" v-for="(hour, index) in hours"
@pointerdown.prevent :key="`${hour}-${index}`"
class="flex h-[26px] border-b sm:h-[30px]"
:class="index === hours.length - 1 ? 'border-ink' : 'border-line'"
> >
{{ hour }} <div
class="flex h-full w-[30px] touch-none select-none items-center justify-center border-r border-ink text-[9px] text-ink"
@pointerdown.prevent
>
{{ hour }}
</div>
<div
v-for="quarter in 6"
:key="quarter"
:class="props.timetable[index * 6 + quarter - 1] ? 'bg-stone-800/90' : 'bg-transparent'"
class="h-full w-[30px] cursor-crosshair border-r border-dashed border-line transition-colors last:border-r-0 touch-none select-none"
@contextmenu.prevent
@pointerdown.prevent="startTimetableDrag(index * 6 + quarter - 1, $event)"
@pointerenter="moveTimetableDrag(index * 6 + quarter - 1)"
/>
</div> </div>
<div
v-for="quarter in 6"
:key="quarter"
:class="props.timetable[index * 6 + quarter - 1] ? 'bg-stone-800/90' : 'bg-transparent'"
class="h-full w-[30px] cursor-crosshair border-r border-dashed border-line transition-colors last:border-r-0 touch-none select-none"
@contextmenu.prevent
@pointerdown.prevent="startTimetableDrag(index * 6 + quarter - 1, $event)"
@pointerenter="moveTimetableDrag(index * 6 + quarter - 1)"
/>
</div> </div>
</div> </div>
</section> </section>