feat: redesign upload experience and visual branding

* feat: add animated flower step indicator

* feat: add custom pixel cursor

* feat: update branding, cursor, and artwork cards

* feat: add paper texture background

* style: update description card background

* feat: redesign upload moodboard collage layout

* style: update upload colors and add sliding mode toggle

* fix: add static favicon and apple touch icons
This commit is contained in:
Chaewon Lee
2026-06-13 01:20:54 +09:00
committed by GitHub
parent e7c690ac13
commit dda6ca972d
18 changed files with 549 additions and 109 deletions

106
src/lib/assets/cursor.svg Normal file
View File

@@ -0,0 +1,106 @@
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges">
<!-- grid: 4px per pixel, 16x16 grid -->
<path fill="#FFFFFF" d="
M4 4 h4 v4 h-4 z
M8 4 h4 v4 h-4 z
M4 8 h4 v4 h-4 z
M8 8 h4 v4 h-4 z
M12 8 h4 v4 h-4 z
M4 12 h4 v4 h-4 z
M8 12 h4 v4 h-4 z
M12 12 h4 v4 h-4 z
M16 12 h4 v4 h-4 z
M4 16 h4 v4 h-4 z
M8 16 h4 v4 h-4 z
M12 16 h4 v4 h-4 z
M16 16 h4 v4 h-4 z
M20 16 h4 v4 h-4 z
M4 20 h4 v4 h-4 z
M8 20 h4 v4 h-4 z
M12 20 h4 v4 h-4 z
M16 20 h4 v4 h-4 z
M20 20 h4 v4 h-4 z
M24 20 h4 v4 h-4 z
M4 24 h4 v4 h-4 z
M8 24 h4 v4 h-4 z
M12 24 h4 v4 h-4 z
M16 24 h4 v4 h-4 z
M20 24 h4 v4 h-4 z
M24 24 h4 v4 h-4 z
M28 24 h4 v4 h-4 z
M4 28 h4 v4 h-4 z
M8 28 h4 v4 h-4 z
M12 28 h4 v4 h-4 z
M16 28 h4 v4 h-4 z
M20 28 h4 v4 h-4 z
M24 28 h4 v4 h-4 z
M28 28 h4 v4 h-4 z
M32 28 h4 v4 h-4 z
M4 32 h4 v4 h-4 z
M8 32 h4 v4 h-4 z
M12 32 h4 v4 h-4 z
M16 32 h4 v4 h-4 z
M20 32 h4 v4 h-4 z
M4 36 h4 v4 h-4 z
M8 36 h4 v4 h-4 z
M12 36 h4 v4 h-4 z
M16 36 h4 v4 h-4 z
M20 36 h4 v4 h-4 z
M24 36 h4 v4 h-4 z
M28 36 h4 v4 h-4 z
M4 40 h4 v4 h-4 z
M8 40 h4 v4 h-4 z
M16 40 h4 v4 h-4 z
M20 40 h4 v4 h-4 z
M24 40 h4 v4 h-4 z
M28 40 h4 v4 h-4 z
M16 44 h4 v4 h-4 z
M20 44 h4 v4 h-4 z
M24 44 h4 v4 h-4 z
M28 44 h4 v4 h-4 z
M32 44 h4 v4 h-4 z
M20 48 h4 v4 h-4 z
M24 48 h4 v4 h-4 z
M28 48 h4 v4 h-4 z
M32 48 h4 v4 h-4 z
M20 52 h4 v4 h-4 z
M24 52 h4 v4 h-4 z
M28 52 h4 v4 h-4 z
M32 52 h4 v4 h-4 z
"/>
<path fill="#38322F" d="
M8 8 h4 v4 h-4 z
M8 12 h4 v4 h-4 z
M12 12 h4 v4 h-4 z
M8 16 h4 v4 h-4 z
M12 16 h4 v4 h-4 z
M16 16 h4 v4 h-4 z
M8 20 h4 v4 h-4 z
M12 20 h4 v4 h-4 z
M16 20 h4 v4 h-4 z
M20 20 h4 v4 h-4 z
M8 24 h4 v4 h-4 z
M12 24 h4 v4 h-4 z
M16 24 h4 v4 h-4 z
M20 24 h4 v4 h-4 z
M24 24 h4 v4 h-4 z
M8 28 h4 v4 h-4 z
M12 28 h4 v4 h-4 z
M16 28 h4 v4 h-4 z
M20 28 h4 v4 h-4 z
M24 28 h4 v4 h-4 z
M28 28 h4 v4 h-4 z
M8 32 h4 v4 h-4 z
M12 32 h4 v4 h-4 z
M16 32 h4 v4 h-4 z
M8 36 h4 v4 h-4 z
M20 36 h4 v4 h-4 z
M24 36 h4 v4 h-4 z
M20 40 h4 v4 h-4 z
M24 40 h4 v4 h-4 z
M24 44 h4 v4 h-4 z
M28 44 h4 v4 h-4 z
M24 48 h4 v4 h-4 z
M28 48 h4 v4 h-4 z
"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
<svg width="240" height="240" viewBox="0 0 240 240" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges">
<g fill="#38322F">
<!-- r1 --><rect x="96" y="12" width="12" height="12"/><rect x="108" y="12" width="12" height="12"/><rect x="120" y="12" width="12" height="12"/><rect x="132" y="12" width="12" height="12"/>
<!-- r2 --><rect x="84" y="24" width="12" height="12"/><rect x="96" y="24" width="12" height="12"/><rect x="108" y="24" width="12" height="12"/><rect x="120" y="24" width="12" height="12"/><rect x="132" y="24" width="12" height="12"/><rect x="144" y="24" width="12" height="12"/>
<!-- r3 --><rect x="84" y="36" width="12" height="12"/><rect x="96" y="36" width="12" height="12"/><rect x="108" y="36" width="12" height="12"/><rect x="120" y="36" width="12" height="12"/><rect x="132" y="36" width="12" height="12"/><rect x="144" y="36" width="12" height="12"/>
<!-- r4 --><rect x="84" y="48" width="12" height="12"/><rect x="96" y="48" width="12" height="12"/><rect x="108" y="48" width="12" height="12"/><rect x="120" y="48" width="12" height="12"/><rect x="132" y="48" width="12" height="12"/><rect x="144" y="48" width="12" height="12"/>
<!-- r5 --><rect x="48" y="60" width="12" height="12"/><rect x="60" y="60" width="12" height="12"/><rect x="72" y="60" width="12" height="12"/><rect x="84" y="60" width="12" height="12"/><rect x="96" y="60" width="12" height="12"/><rect x="108" y="60" width="12" height="12"/><rect x="120" y="60" width="12" height="12"/><rect x="132" y="60" width="12" height="12"/><rect x="144" y="60" width="12" height="12"/><rect x="156" y="60" width="12" height="12"/><rect x="168" y="60" width="12" height="12"/><rect x="180" y="60" width="12" height="12"/>
<!-- r6 --><rect x="36" y="72" width="12" height="12"/><rect x="48" y="72" width="12" height="12"/><rect x="60" y="72" width="12" height="12"/><rect x="72" y="72" width="12" height="12"/><rect x="84" y="72" width="12" height="12"/><rect x="96" y="72" width="12" height="12"/><rect x="108" y="72" width="12" height="12"/><rect x="120" y="72" width="12" height="12"/><rect x="132" y="72" width="12" height="12"/><rect x="144" y="72" width="12" height="12"/><rect x="156" y="72" width="12" height="12"/><rect x="168" y="72" width="12" height="12"/><rect x="180" y="72" width="12" height="12"/><rect x="192" y="72" width="12" height="12"/>
<!-- r7 (center hole) --><rect x="24" y="84" width="12" height="12"/><rect x="36" y="84" width="12" height="12"/><rect x="48" y="84" width="12" height="12"/><rect x="60" y="84" width="12" height="12"/><rect x="72" y="84" width="12" height="12"/><rect x="84" y="84" width="12" height="12"/><rect x="96" y="84" width="12" height="12"/><rect x="132" y="84" width="12" height="12"/><rect x="144" y="84" width="12" height="12"/><rect x="156" y="84" width="12" height="12"/><rect x="168" y="84" width="12" height="12"/><rect x="180" y="84" width="12" height="12"/><rect x="192" y="84" width="12" height="12"/><rect x="204" y="84" width="12" height="12"/>
<!-- r8 (center hole) --><rect x="24" y="96" width="12" height="12"/><rect x="36" y="96" width="12" height="12"/><rect x="48" y="96" width="12" height="12"/><rect x="60" y="96" width="12" height="12"/><rect x="72" y="96" width="12" height="12"/><rect x="84" y="96" width="12" height="12"/><rect x="96" y="96" width="12" height="12"/><rect x="132" y="96" width="12" height="12"/><rect x="144" y="96" width="12" height="12"/><rect x="156" y="96" width="12" height="12"/><rect x="168" y="96" width="12" height="12"/><rect x="180" y="96" width="12" height="12"/><rect x="192" y="96" width="12" height="12"/><rect x="204" y="96" width="12" height="12"/>
<!-- r9 --><rect x="36" y="108" width="12" height="12"/><rect x="48" y="108" width="12" height="12"/><rect x="60" y="108" width="12" height="12"/><rect x="72" y="108" width="12" height="12"/><rect x="84" y="108" width="12" height="12"/><rect x="96" y="108" width="12" height="12"/><rect x="108" y="108" width="12" height="12"/><rect x="120" y="108" width="12" height="12"/><rect x="132" y="108" width="12" height="12"/><rect x="144" y="108" width="12" height="12"/><rect x="156" y="108" width="12" height="12"/><rect x="168" y="108" width="12" height="12"/><rect x="180" y="108" width="12" height="12"/>
<!-- r10 --><rect x="48" y="120" width="12" height="12"/><rect x="60" y="120" width="12" height="12"/><rect x="72" y="120" width="12" height="12"/><rect x="84" y="120" width="12" height="12"/><rect x="96" y="120" width="12" height="12"/><rect x="108" y="120" width="12" height="12"/><rect x="120" y="120" width="12" height="12"/><rect x="132" y="120" width="12" height="12"/><rect x="144" y="120" width="12" height="12"/><rect x="156" y="120" width="12" height="12"/><rect x="168" y="120" width="12" height="12"/><rect x="180" y="120" width="12" height="12"/>
<!-- r11 --><rect x="84" y="132" width="12" height="12"/><rect x="96" y="132" width="12" height="12"/><rect x="108" y="132" width="12" height="12"/><rect x="120" y="132" width="12" height="12"/><rect x="132" y="132" width="12" height="12"/><rect x="144" y="132" width="12" height="12"/>
<!-- r12 --><rect x="84" y="144" width="12" height="12"/><rect x="96" y="144" width="12" height="12"/><rect x="108" y="144" width="12" height="12"/><rect x="120" y="144" width="12" height="12"/><rect x="132" y="144" width="12" height="12"/><rect x="144" y="144" width="12" height="12"/>
<!-- r13 --><rect x="84" y="156" width="12" height="12"/><rect x="96" y="156" width="12" height="12"/><rect x="108" y="156" width="12" height="12"/><rect x="120" y="156" width="12" height="12"/><rect x="132" y="156" width="12" height="12"/><rect x="144" y="156" width="12" height="12"/>
<!-- r14 --><rect x="96" y="168" width="12" height="12"/><rect x="108" y="168" width="12" height="12"/><rect x="120" y="168" width="12" height="12"/><rect x="132" y="168" width="12" height="12"/>
<!-- stem -->
<!-- r15 --><rect x="108" y="180" width="12" height="12"/><rect x="120" y="180" width="12" height="12"/>
<!-- r16 --><rect x="108" y="192" width="12" height="12"/><rect x="120" y="192" width="12" height="12"/>
<!-- r17 leaf --><rect x="72" y="204" width="12" height="12"/><rect x="84" y="204" width="12" height="12"/><rect x="96" y="204" width="12" height="12"/><rect x="108" y="204" width="12" height="12"/><rect x="120" y="204" width="12" height="12"/>
<!-- r18 --><rect x="108" y="216" width="12" height="12"/><rect x="120" y="216" width="12" height="12"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -1,5 +1,5 @@
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<path fill="#000000" d="
<path fill="#7d7d7d" d="
M32 6
C36 6 38.5 10.5 38 15.5
C42.5 12 48 12.5 50.5 16

Before

Width:  |  Height:  |  Size: 540 B

After

Width:  |  Height:  |  Size: 540 B

23
src/lib/assets/logo.svg Normal file
View File

@@ -0,0 +1,23 @@
<svg width="240" height="240" viewBox="0 0 240 240" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges">
<g fill="#38322F">
<!-- r1 --><rect x="96" y="12" width="12" height="12"/><rect x="108" y="12" width="12" height="12"/><rect x="120" y="12" width="12" height="12"/><rect x="132" y="12" width="12" height="12"/>
<!-- r2 --><rect x="84" y="24" width="12" height="12"/><rect x="96" y="24" width="12" height="12"/><rect x="108" y="24" width="12" height="12"/><rect x="120" y="24" width="12" height="12"/><rect x="132" y="24" width="12" height="12"/><rect x="144" y="24" width="12" height="12"/>
<!-- r3 --><rect x="84" y="36" width="12" height="12"/><rect x="96" y="36" width="12" height="12"/><rect x="108" y="36" width="12" height="12"/><rect x="120" y="36" width="12" height="12"/><rect x="132" y="36" width="12" height="12"/><rect x="144" y="36" width="12" height="12"/>
<!-- r4 --><rect x="84" y="48" width="12" height="12"/><rect x="96" y="48" width="12" height="12"/><rect x="108" y="48" width="12" height="12"/><rect x="120" y="48" width="12" height="12"/><rect x="132" y="48" width="12" height="12"/><rect x="144" y="48" width="12" height="12"/>
<!-- r5 --><rect x="48" y="60" width="12" height="12"/><rect x="60" y="60" width="12" height="12"/><rect x="72" y="60" width="12" height="12"/><rect x="84" y="60" width="12" height="12"/><rect x="96" y="60" width="12" height="12"/><rect x="108" y="60" width="12" height="12"/><rect x="120" y="60" width="12" height="12"/><rect x="132" y="60" width="12" height="12"/><rect x="144" y="60" width="12" height="12"/><rect x="156" y="60" width="12" height="12"/><rect x="168" y="60" width="12" height="12"/><rect x="180" y="60" width="12" height="12"/>
<!-- r6 --><rect x="36" y="72" width="12" height="12"/><rect x="48" y="72" width="12" height="12"/><rect x="60" y="72" width="12" height="12"/><rect x="72" y="72" width="12" height="12"/><rect x="84" y="72" width="12" height="12"/><rect x="96" y="72" width="12" height="12"/><rect x="108" y="72" width="12" height="12"/><rect x="120" y="72" width="12" height="12"/><rect x="132" y="72" width="12" height="12"/><rect x="144" y="72" width="12" height="12"/><rect x="156" y="72" width="12" height="12"/><rect x="168" y="72" width="12" height="12"/><rect x="180" y="72" width="12" height="12"/><rect x="192" y="72" width="12" height="12"/>
<!-- r7 (center hole) --><rect x="24" y="84" width="12" height="12"/><rect x="36" y="84" width="12" height="12"/><rect x="48" y="84" width="12" height="12"/><rect x="60" y="84" width="12" height="12"/><rect x="72" y="84" width="12" height="12"/><rect x="84" y="84" width="12" height="12"/><rect x="96" y="84" width="12" height="12"/><rect x="132" y="84" width="12" height="12"/><rect x="144" y="84" width="12" height="12"/><rect x="156" y="84" width="12" height="12"/><rect x="168" y="84" width="12" height="12"/><rect x="180" y="84" width="12" height="12"/><rect x="192" y="84" width="12" height="12"/><rect x="204" y="84" width="12" height="12"/>
<!-- r8 (center hole) --><rect x="24" y="96" width="12" height="12"/><rect x="36" y="96" width="12" height="12"/><rect x="48" y="96" width="12" height="12"/><rect x="60" y="96" width="12" height="12"/><rect x="72" y="96" width="12" height="12"/><rect x="84" y="96" width="12" height="12"/><rect x="96" y="96" width="12" height="12"/><rect x="132" y="96" width="12" height="12"/><rect x="144" y="96" width="12" height="12"/><rect x="156" y="96" width="12" height="12"/><rect x="168" y="96" width="12" height="12"/><rect x="180" y="96" width="12" height="12"/><rect x="192" y="96" width="12" height="12"/><rect x="204" y="96" width="12" height="12"/>
<!-- r9 --><rect x="36" y="108" width="12" height="12"/><rect x="48" y="108" width="12" height="12"/><rect x="60" y="108" width="12" height="12"/><rect x="72" y="108" width="12" height="12"/><rect x="84" y="108" width="12" height="12"/><rect x="96" y="108" width="12" height="12"/><rect x="108" y="108" width="12" height="12"/><rect x="120" y="108" width="12" height="12"/><rect x="132" y="108" width="12" height="12"/><rect x="144" y="108" width="12" height="12"/><rect x="156" y="108" width="12" height="12"/><rect x="168" y="108" width="12" height="12"/><rect x="180" y="108" width="12" height="12"/>
<!-- r10 --><rect x="48" y="120" width="12" height="12"/><rect x="60" y="120" width="12" height="12"/><rect x="72" y="120" width="12" height="12"/><rect x="84" y="120" width="12" height="12"/><rect x="96" y="120" width="12" height="12"/><rect x="108" y="120" width="12" height="12"/><rect x="120" y="120" width="12" height="12"/><rect x="132" y="120" width="12" height="12"/><rect x="144" y="120" width="12" height="12"/><rect x="156" y="120" width="12" height="12"/><rect x="168" y="120" width="12" height="12"/><rect x="180" y="120" width="12" height="12"/>
<!-- r11 --><rect x="84" y="132" width="12" height="12"/><rect x="96" y="132" width="12" height="12"/><rect x="108" y="132" width="12" height="12"/><rect x="120" y="132" width="12" height="12"/><rect x="132" y="132" width="12" height="12"/><rect x="144" y="132" width="12" height="12"/>
<!-- r12 --><rect x="84" y="144" width="12" height="12"/><rect x="96" y="144" width="12" height="12"/><rect x="108" y="144" width="12" height="12"/><rect x="120" y="144" width="12" height="12"/><rect x="132" y="144" width="12" height="12"/><rect x="144" y="144" width="12" height="12"/>
<!-- r13 --><rect x="84" y="156" width="12" height="12"/><rect x="96" y="156" width="12" height="12"/><rect x="108" y="156" width="12" height="12"/><rect x="120" y="156" width="12" height="12"/><rect x="132" y="156" width="12" height="12"/><rect x="144" y="156" width="12" height="12"/>
<!-- r14 --><rect x="96" y="168" width="12" height="12"/><rect x="108" y="168" width="12" height="12"/><rect x="120" y="168" width="12" height="12"/><rect x="132" y="168" width="12" height="12"/>
<!-- stem -->
<!-- r15 --><rect x="108" y="180" width="12" height="12"/><rect x="120" y="180" width="12" height="12"/>
<!-- r16 --><rect x="108" y="192" width="12" height="12"/><rect x="120" y="192" width="12" height="12"/>
<!-- r17 leaf --><rect x="72" y="204" width="12" height="12"/><rect x="84" y="204" width="12" height="12"/><rect x="96" y="204" width="12" height="12"/><rect x="108" y="204" width="12" height="12"/><rect x="120" y="204" width="12" height="12"/>
<!-- r18 --><rect x="108" y="216" width="12" height="12"/><rect x="120" y="216" width="12" height="12"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -2,7 +2,7 @@
let { title = 'Title', description = 'Description Description Description' } = $props();
</script>
<div class="min-w-0 flex-1 border border-line-strong px-4 py-3 lg:w-54 lg:flex-none lg:px-6 lg:py-5">
<h3 class="text-sm">{title}</h3>
<div class="w-64 max-w-full flex-none border border-line-strong bg-white px-4 py-3 shadow-sm lg:px-6 lg:py-5">
<h3 class="text-sm font-semibold">{title}</h3>
<p class="mt-2 text-xs leading-snug">{description}</p>
</div>

View File

@@ -0,0 +1,68 @@
<script>
import { onMount } from 'svelte';
import cursorUrl from '$lib/assets/cursor.svg';
let visible = $state(false);
let x = $state(0);
let y = $state(0);
onMount(() => {
const canUseCustomCursor = window.matchMedia('(hover: hover) and (pointer: fine)').matches;
if (!canUseCustomCursor) return;
document.documentElement.classList.add('flower-cursor');
function handlePointerMove(event) {
x = event.clientX;
y = event.clientY;
visible = true;
}
function handlePointerLeave() {
visible = false;
}
window.addEventListener('pointermove', handlePointerMove);
document.addEventListener('mouseleave', handlePointerLeave);
return () => {
document.documentElement.classList.remove('flower-cursor');
window.removeEventListener('pointermove', handlePointerMove);
document.removeEventListener('mouseleave', handlePointerLeave);
};
});
</script>
{#if visible}
<div class="flower-cursor-layer" style={`transform: translate3d(${x}px, ${y}px, 0)`}>
<img class="flower-cursor-icon" src={cursorUrl} alt="" aria-hidden="true" />
</div>
{/if}
<style>
:global(html.flower-cursor),
:global(html.flower-cursor *) {
cursor: none !important;
}
.flower-cursor-layer {
position: fixed;
top: 0;
left: 0;
z-index: 2147483647;
pointer-events: none;
translate: -0.3rem -0.3rem;
}
.flower-cursor-icon {
display: block;
width: 2.35rem;
height: 2.35rem;
}
@media (prefers-reduced-motion: reduce) {
.flower-cursor-layer {
transition: none;
}
}
</style>

View File

@@ -1,4 +1,6 @@
<script>
import logoUrl from '$lib/assets/logo.svg';
// `step` is 1-based; the matching dot is highlighted as the current step.
let { step = 1, total = 7 } = $props();
@@ -7,15 +9,45 @@
<header class="flex items-center justify-between border-b border-line px-6 py-5 md:px-10">
<div class="flex items-center gap-3">
<span class="size-4 shrink-0 bg-placeholder"></span>
<span class="text-lg tracking-wide">AI Florist</span>
<img src={logoUrl} alt="" class="size-7 shrink-0 translate-y-px" aria-hidden="true" />
<span class="text-lg leading-none tracking-wide">AI Florist</span>
</div>
<div class="flex items-center gap-3 sm:gap-4">
{#each dots as dot (dot)}
<span
class={['rounded-[1px]', dot === step - 1 ? 'size-2.5 bg-subtle' : 'size-2 bg-placeholder']}
></span>
<svg
class={[
'shrink-0',
dot === step - 1
? 'flower-icon--current size-4.5 text-subtle'
: 'size-3.5 text-placeholder'
]}
viewBox="0 0 64 64"
aria-hidden="true"
>
<path
fill="currentColor"
d="M32 6 C36 6 38.5 10.5 38 15.5 C42.5 12 48 12.5 50.5 16 C53 19.5 50.5 24.5 46 26.5 C51 27.5 54.5 31.5 53.5 36 C52.5 40.5 47 42 42.5 40 C45.5 44 45 49.5 41 52 C37 54.5 32.5 51.5 31 47 C29.5 51.5 25 54.5 21 52 C17 49.5 16.5 44 19.5 40 C15 42 9.5 40.5 8.5 36 C7.5 31.5 11 27.5 16 26.5 C11.5 24.5 9 19.5 11.5 16 C14 12.5 19.5 12 24 15.5 C23.5 10.5 28 6 32 6 Z"
/>
</svg>
{/each}
</div>
</header>
<style>
.flower-icon--current {
animation: flower-spin 2.4s linear infinite;
}
@keyframes flower-spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.flower-icon--current {
animation: none;
}
}
</style>

View File

@@ -4,7 +4,7 @@
import { hydrateDevUpload } from '$lib/dev/hydrateUpload.js';
import { getFlowObject, isDevSeeded } from '$lib/flowerFlow/session.js';
let { primaryFile = $bindable(null) } = $props();
let { primaryFile = $bindable(null), caption = 'build their moodboard!' } = $props();
let colorFile = $state(null);
let seasonFile = $state(null);
@@ -12,7 +12,8 @@
let locationFile = $state(null);
$effect(() => {
primaryFile = colorFile ?? seasonFile ?? characterFile ?? locationFile ?? null;
const next = colorFile ?? seasonFile ?? characterFile ?? locationFile ?? null;
if (primaryFile !== next) primaryFile = next;
});
onMount(async () => {
@@ -35,65 +36,170 @@
</script>
<div class="moodboard min-h-0 w-full flex-1">
<UploadTile
label="Color"
bind:file={colorFile}
class="tile tile-color aspect-4/5 h-full min-h-0 w-full max-lg:aspect-auto lg:aspect-auto"
/>
<UploadTile
label="Season"
bind:file={seasonFile}
class="tile tile-season aspect-4/3 h-full min-h-0 w-full max-lg:aspect-auto lg:aspect-auto"
/>
<UploadTile
label="Character"
bind:file={characterFile}
class="tile tile-character aspect-4/3 h-full min-h-0 w-full max-lg:aspect-auto lg:aspect-auto"
/>
<UploadTile
label="Location"
bind:file={locationFile}
class="tile tile-location aspect-4/5 h-full min-h-0 w-full max-lg:aspect-auto lg:aspect-auto"
/>
<div class="collage">
<span class="mood-number number-color">(01)</span>
<span class="mood-number number-season">(02)</span>
<span class="mood-number number-character">(03)</span>
<span class="mood-number number-location">(04)</span>
<span class="mood-caption">{caption}</span>
<UploadTile
label="Color"
bind:file={colorFile}
class="moodboard-tile tile-color"
/>
<UploadTile
label="Season"
bind:file={seasonFile}
class="moodboard-tile tile-season"
/>
<UploadTile
label="Character"
bind:file={characterFile}
class="moodboard-tile tile-character"
/>
<UploadTile
label="Location"
bind:file={locationFile}
class="moodboard-tile tile-location"
/>
</div>
</div>
<style>
.moodboard {
display: grid;
gap: 0;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
width: 100%;
flex: 1;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 0.5rem 1.5rem 1rem;
}
@media (min-width: 1024px) {
.collage {
position: relative;
width: min(100%, 34rem);
height: 100%;
aspect-ratio: 4 / 5.2;
max-height: 44rem;
}
.moodboard :global(.moodboard-tile) {
position: absolute;
background: #fff;
box-shadow: 0 10px 24px rgb(56 50 47 / 0.08);
}
/* 01 — top-left portrait */
:global(.tile-color) {
top: 8%;
left: 4%;
width: 30%;
aspect-ratio: 3 / 4;
}
/* 02 — right portrait, dips below the top of 01 */
:global(.tile-season) {
top: 13%;
right: 3%;
width: 29%;
aspect-ratio: 3 / 4;
}
/* 03 — landscape, lower-left */
:global(.tile-character) {
top: 49%;
left: 10%;
width: 36%;
aspect-ratio: 4 / 3;
}
/* 04 — bottom-right portrait */
:global(.tile-location) {
top: 62%;
right: 4%;
width: 29%;
aspect-ratio: 3 / 4;
}
.mood-number,
.mood-caption {
position: absolute;
z-index: 2;
pointer-events: none;
color: var(--color-ink);
}
.mood-number {
font-size: clamp(1rem, 2.2vw, 1.5rem);
line-height: 1;
}
.number-color {
top: 13%;
left: 35%;
}
.number-season {
top: 38%;
left: 60%;
}
.number-character {
top: 73%;
left: 9%;
}
.number-location {
top: 58%;
right: 11%;
}
.mood-caption {
/* horizontally centered over the toggle capsule (the left flex child of
the bottom bar), not the section. Its center sits left of the collage
midpoint because the "Continue" button occupies the bar's right side. */
left: 29%;
top: 84%;
font-size: clamp(0.85rem, 1.7vw, 1.1rem);
transform: translateX(-50%);
white-space: nowrap;
}
@media (max-width: 767px) {
.moodboard {
grid-template-rows: repeat(5, 1fr);
grid-template-areas:
'color season'
'color season'
'color location'
'character location'
'character location';
min-height: 34rem;
align-items: flex-start;
padding: 1.5rem 1rem 7rem;
}
:global(.tile-color) {
grid-area: color;
.collage {
display: grid;
width: 100%;
aspect-ratio: auto;
min-height: 0;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1.25rem;
}
:global(.tile-season) {
grid-area: season;
.moodboard :global(.moodboard-tile) {
position: relative;
inset: auto;
width: 100%;
height: auto;
}
:global(.tile-color),
:global(.tile-season),
:global(.tile-location) {
aspect-ratio: 3 / 4;
}
:global(.tile-character) {
grid-area: character;
aspect-ratio: 4 / 3;
}
:global(.tile-location) {
grid-area: location;
.mood-number,
.mood-caption {
display: none;
}
}
</style>

View File

@@ -4,13 +4,13 @@
import { hydrateDevUpload } from '$lib/dev/hydrateUpload.js';
import { getFlowObject, isDevSeeded } from '$lib/flowerFlow/session.js';
let { primaryFile = $bindable(null) } = $props();
let { primaryFile = $bindable(null), caption = 'upload their feed!' } = $props();
let firstFile = $state(null);
let secondFile = $state(null);
$effect(() => {
primaryFile = firstFile ?? secondFile ?? null;
const next = firstFile ?? null;
if (primaryFile !== next) primaryFile = next;
});
onMount(async () => {
@@ -23,7 +23,6 @@
try {
const files = await hydrateDevUpload(/** @type {Record<string, string>} */ (tiles));
if (files.first) firstFile = files.first;
if (files.second) secondFile = files.second;
} catch {
// dev seed 실패 시 빈 타일 유지
}
@@ -31,45 +30,89 @@
</script>
<div class="feed min-h-0 w-full flex-1">
<UploadTile
bind:file={firstFile}
class="tile-one aspect-4/5 h-full min-h-0 w-full max-lg:aspect-auto lg:aspect-auto"
/>
<UploadTile
bind:file={secondFile}
class="tile-two aspect-4/5 h-full min-h-0 w-full max-lg:aspect-auto lg:aspect-auto"
/>
<div class="sns-collage">
<span class="sns-number">(01)</span>
<span class="sns-caption">{caption}</span>
<UploadTile bind:file={firstFile} class="sns-tile" />
</div>
</div>
<style>
.feed {
display: grid;
gap: 0;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr;
width: 100%;
flex: 1;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 0.5rem 1.5rem 1rem;
}
@media (min-width: 1024px) {
.sns-collage {
position: relative;
width: min(100%, 42rem);
height: 100%;
aspect-ratio: 4 / 5;
max-height: 34rem;
}
.feed :global(.sns-tile) {
position: absolute;
top: 12%;
left: 50%;
width: 58%;
height: 46%;
background: #fff;
box-shadow: 0 10px 24px rgb(56 50 47 / 0.08);
transform: translateX(-50%);
}
.sns-number,
.sns-caption {
position: absolute;
z-index: 2;
pointer-events: none;
color: var(--color-ink);
}
.sns-number {
top: 6%;
left: 23%;
font-size: clamp(1rem, 2.2vw, 1.5rem);
line-height: 1;
}
.sns-caption {
left: 50%;
bottom: 13%;
font-size: clamp(0.9rem, 1.9vw, 1.25rem);
transform: translateX(-50%);
white-space: nowrap;
}
@media (max-width: 767px) {
.feed {
grid-template-rows: repeat(5, 1fr);
grid-template-areas:
'. two'
'one two'
'one two'
'one two'
'one .';
min-height: 34rem;
align-items: flex-start;
padding: 1.5rem 1rem 7rem;
}
:global(.tile-one) {
grid-area: one;
.sns-collage {
width: 100%;
aspect-ratio: auto;
min-height: 0;
}
:global(.tile-two) {
grid-area: two;
.feed :global(.sns-tile) {
position: relative;
inset: auto;
width: 100%;
height: auto;
aspect-ratio: 4 / 5;
transform: none;
}
.sns-number,
.sns-caption {
display: none;
}
}
</style>

View File

@@ -3,7 +3,13 @@
// the chosen image (cover) when filled. Layout (size / grid placement) is
// supplied by the parent via `class` and `style` so the same tile works in
// both the moodboard and the SNS feed.
let { label = null, class: klass = '', style = '', file = $bindable(null) } = $props();
let {
label = null,
showLabel = true,
class: klass = '',
style = '',
file = $bindable(null)
} = $props();
let preview = $state(null);
@@ -44,14 +50,14 @@
{#if preview}
<img src={preview} alt={label ?? ''} class="h-full w-full object-cover" />
<div class="absolute inset-0 bg-gradient-to-t from-black/45 to-transparent"></div>
{#if label}
<div class="absolute inset-0 bg-linear-to-t from-ink/45 to-transparent"></div>
{#if label && showLabel}
<span class="absolute bottom-3 left-4 text-sm tracking-[0.15em] text-surface uppercase"
>{label}</span
>
{/if}
<span
class="absolute top-3 right-3 rounded-full bg-black/40 px-2.5 py-1 text-xs text-surface opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100"
class="absolute top-3 right-3 rounded-full bg-ink/40 px-2.5 py-1 text-xs text-surface opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100"
>
Change
</span>
@@ -63,7 +69,7 @@
class="flex size-10 items-center justify-center rounded-full border border-current text-xl leading-none"
aria-hidden="true">+</span
>
{#if label}
{#if label && showLabel}
<span class="text-sm tracking-[0.15em] uppercase">{label}</span>
{/if}
</div>