diff --git a/src/lib/components/ui/Artwork/Artwork.svelte b/src/lib/components/ui/Artwork/Artwork.svelte index 57a0953..4429c21 100644 --- a/src/lib/components/ui/Artwork/Artwork.svelte +++ b/src/lib/components/ui/Artwork/Artwork.svelte @@ -7,6 +7,8 @@ let { title = 'Title', description = 'Description Description Description', + /** @type {'instruction' | 'summary'} */ + cardMode = 'summary', /** @type {import('./artworkVariants.js').ArtworkVariant} */ variant = 'create1', /** edit Continue 이후 확정된 꽃다발만 전달 (그 전에는 null → Vase) */ @@ -42,7 +44,7 @@
- +
{#if comingSoon} diff --git a/src/lib/components/ui/Artwork/DescriptionCard.svelte b/src/lib/components/ui/Artwork/DescriptionCard.svelte index dbdfec7..23618a3 100644 --- a/src/lib/components/ui/Artwork/DescriptionCard.svelte +++ b/src/lib/components/ui/Artwork/DescriptionCard.svelte @@ -1,8 +1,24 @@
-

{title}

-

{description}

+

+ {title} +

+

+ {description} +

diff --git a/src/lib/components/ui/landing/LandingHero.svelte b/src/lib/components/ui/landing/LandingHero.svelte index 26995c2..7431a62 100644 --- a/src/lib/components/ui/landing/LandingHero.svelte +++ b/src/lib/components/ui/landing/LandingHero.svelte @@ -1,7 +1,9 @@
@@ -21,16 +23,20 @@

-
-
-

AI Florist

-

- DearYou -

-
- -
- -
+
+

AI Florist

+

+ Fleumuse +

+ + + + +
diff --git a/src/lib/components/ui/map/MapPanel.svelte b/src/lib/components/ui/map/MapPanel.svelte index 3e26f93..e1fab7b 100644 --- a/src/lib/components/ui/map/MapPanel.svelte +++ b/src/lib/components/ui/map/MapPanel.svelte @@ -2,6 +2,7 @@ import FloristOrderMessage from './FloristOrderMessage.svelte'; import KakaoMap from './KakaoMap.svelte'; import ShopList from './ShopList.svelte'; + import { DEFAULT_MAP_CENTER } from '$lib/map/userLocation.js'; let { shops = [], @@ -12,14 +13,14 @@ fitBounds = false, orderPlainText = '', orderKoPlainText = '', + initialLat = DEFAULT_MAP_CENTER.lat, + initialLng = DEFAULT_MAP_CENTER.lng, + locationNotice = '', onrefresh } = $props(); - const DEFAULT_LAT = 37.5665; - const DEFAULT_LNG = 126.978; - - let mapCenterLat = $state(DEFAULT_LAT); - let mapCenterLng = $state(DEFAULT_LNG); + let mapCenterLat = $state(initialLat); + let mapCenterLng = $state(initialLng); let panTarget = $state(null); function handleCenterChange(lat, lng) { @@ -46,6 +47,9 @@ Find a nearby florist

Move the map, then refresh to search this area.

+ {#if locationNotice} +

{locationNotice}

+ {/if} {#if mock}

Showing sample shops (no Kakao API key).

{/if} @@ -63,8 +67,8 @@
} */ +export const ARTWORK_CARD_DEFAULTS = { + create: { + title: 'Who is this bouquet for?', + description: + 'Choose who will receive it, the occasion, and a style on the right. Adjust the budget if you like.' + }, + message: { + title: 'Write a card message', + description: + 'Type a short note or pick a preset on the right. It will appear on your bouquet card.' + }, + generating: { + title: 'Crafting your bouquet', + description: + 'We are turning their mood, photos, and message into a one-of-a-kind arrangement.' + }, + map: { + title: 'Choose a florist', + description: + 'Browse nearby shops on the map and select where you would like to place your order.' + } +}; diff --git a/src/lib/landing/landingGrowthStages.js b/src/lib/landing/landingGrowthStages.js index 24f56cf..c1507f0 100644 --- a/src/lib/landing/landingGrowthStages.js +++ b/src/lib/landing/landingGrowthStages.js @@ -1,14 +1,31 @@ -import seedSrc from '$lib/assets/landing/seed.svg'; -import sproutSrc from '$lib/assets/landing/sprout.svg'; -import flowerSrc from '$lib/assets/landing/flower.svg'; -import bouquetSrc from '$lib/assets/landing/bouquet.svg'; +import { ARTWORK_SRC } from '$lib/components/ui/Artwork/artworkVariants.js'; -/** 랜딩 growth metaphor — ref/route illustration SVG 4단계 */ +/** 랜딩 growth metaphor — artwork 2 → 3 → 5 → 6 순서 */ export const LANDING_GROWTH_STAGES = [ - { id: 'seed', src: seedSrc, heightClass: 'h-[2.125rem] sm:h-9', delayMs: 0 }, - { id: 'sprout', src: sproutSrc, heightClass: 'h-16 sm:h-20', delayMs: 520 }, - { id: 'flower', src: flowerSrc, heightClass: 'h-24 sm:h-28', delayMs: 1040 }, - { id: 'bouquet', src: bouquetSrc, heightClass: 'h-36 sm:h-44 lg:h-52', delayMs: 1560 } + { + id: 'create2', + src: ARTWORK_SRC.create2, + heightClass: 'h-16 sm:h-20', + delayMs: 0 + }, + { + id: 'upload1', + src: ARTWORK_SRC.upload1, + heightClass: 'h-24 sm:h-28', + delayMs: 520 + }, + { + id: 'message1', + src: ARTWORK_SRC.message1, + heightClass: 'h-32 sm:h-36 lg:h-40', + delayMs: 1040 + }, + { + id: 'generated', + src: ARTWORK_SRC.generated, + heightClass: 'h-36 sm:h-44 lg:h-52', + delayMs: 1560 + } ]; export const LANDING_STAGE_GAP_MS = 520; diff --git a/src/lib/map/userLocation.js b/src/lib/map/userLocation.js new file mode 100644 index 0000000..ea7d712 --- /dev/null +++ b/src/lib/map/userLocation.js @@ -0,0 +1,39 @@ +/** 서울시청 — 위치 권한 거부·미지원 시 fallback */ +export const DEFAULT_MAP_CENTER = { lat: 37.5665, lng: 126.978 }; + +/** + * @typedef {{ lat: number, lng: number, fromDevice: boolean }} UserMapCenter + */ + +/** + * 브라우저 Geolocation API로 현재 위치를 가져옵니다. + * 실패 시 DEFAULT_MAP_CENTER를 반환합니다. + * + * @returns {Promise} + */ +export function getUserMapCenter() { + return new Promise((resolve) => { + if (typeof navigator === 'undefined' || !navigator.geolocation) { + resolve({ ...DEFAULT_MAP_CENTER, fromDevice: false }); + return; + } + + navigator.geolocation.getCurrentPosition( + (position) => { + resolve({ + lat: position.coords.latitude, + lng: position.coords.longitude, + fromDevice: true + }); + }, + () => { + resolve({ ...DEFAULT_MAP_CENTER, fromDevice: false }); + }, + { + enableHighAccuracy: true, + timeout: 10_000, + maximumAge: 60_000 + } + ); + }); +} diff --git a/src/routes/create/+page.svelte b/src/routes/create/+page.svelte index 76fb485..64f31d4 100644 --- a/src/routes/create/+page.svelte +++ b/src/routes/create/+page.svelte @@ -15,6 +15,7 @@ isDevSeeded, saveFlow } from '$lib/flowerFlow/session.js'; + import { ARTWORK_CARD_DEFAULTS } from '$lib/flowerFlow/artworkCardCopy.js'; // 항상 빈 폼으로 시작 — Dev Fill은 onMount에서 1회만 스냅샷 적용 let who = $state(null); @@ -25,7 +26,7 @@ const hasAnySelection = $derived(who !== null || whatFor !== null || style !== null); const artworkTitle = $derived.by(() => { - if (!hasAnySelection) return 'Title'; + if (!hasAnySelection) return ARTWORK_CARD_DEFAULTS.create.title; const occasion = whatFor ? `A ${whatFor} bouquet for` : 'A bouquet for'; return `${occasion} ${who ?? '...'}`; }); @@ -34,10 +35,12 @@ const artworkDescription = $derived( hasAnySelection - ? `${style ?? '...'} style · ₩${budget.toLocaleString('ko-KR')} budget` - : 'Description Description Description' + ? `${style ?? '—'} style · ₩${budget.toLocaleString('ko-KR')} budget` + : ARTWORK_CARD_DEFAULTS.create.description ); + const artworkCardMode = $derived(hasAnySelection ? 'summary' : 'instruction'); + onMount(() => { const hadSnapshot = !!getFlowObject('devCreateSnapshot'); const snap = consumeDevCreateSnapshot(); @@ -87,7 +90,12 @@
- +
diff --git a/src/routes/generating/+page.svelte b/src/routes/generating/+page.svelte index 1608d39..7ee6c97 100644 --- a/src/routes/generating/+page.svelte +++ b/src/routes/generating/+page.svelte @@ -16,6 +16,7 @@ loadFlow, saveFlow } from '$lib/flowerFlow/session.js'; + import { ARTWORK_CARD_DEFAULTS } from '$lib/flowerFlow/artworkCardCopy.js'; const MAX_RETRIES = 5; const userInput = getFlowUserInput(); @@ -24,12 +25,20 @@ const artworkTitle = $derived.by(() => { const who = typeof userInput.relationship === 'string' ? userInput.relationship : null; const whatFor = typeof userInput.occasion === 'string' ? userInput.occasion : null; - if (!who && !whatFor) return 'Your bouquet'; + if (!who && !whatFor) return ARTWORK_CARD_DEFAULTS.generating.title; const occasion = whatFor ? `A ${whatFor} bouquet for` : 'A bouquet for'; return `${occasion} ${who ?? '...'}`; }); - const artworkDescription = $derived(cardMessage || '잠시 관리중 ~'); + const artworkDescription = $derived( + cardMessage?.trim() || ARTWORK_CARD_DEFAULTS.generating.description + ); + + const artworkCardMode = $derived.by(() => { + const who = typeof userInput.relationship === 'string' ? userInput.relationship : null; + const whatFor = typeof userInput.occasion === 'string' ? userInput.occasion : null; + return who || whatFor || cardMessage?.trim() ? 'summary' : 'instruction'; + }); /** @type {import('$lib/components/ui/Artwork/artworkVariants.js').ArtworkVariant} */ let artworkVariant = $state('create2'); @@ -212,7 +221,13 @@
- +
@@ -98,20 +118,34 @@
- +
- loadShops(lat, lng, { fitBounds: false })} - /> + {#if locationReady} + loadShops(lat, lng, { fitBounds: false })} + /> + {:else} +
+ Getting your location... +
+ {/if}
diff --git a/src/routes/message/+page.svelte b/src/routes/message/+page.svelte index 0604a46..ce9db3a 100644 --- a/src/routes/message/+page.svelte +++ b/src/routes/message/+page.svelte @@ -19,6 +19,7 @@ loadFlow, saveFlow } from '$lib/flowerFlow/session.js'; + import { ARTWORK_CARD_DEFAULTS } from '$lib/flowerFlow/artworkCardCopy.js'; const userInput = getFlowUserInput(); @@ -27,11 +28,19 @@ let error = $state(''); let skipping = $state(false); - const artworkVariant = $derived(message.trim() ? 'message1' : 'upload2'); + const hasMessage = $derived(message.trim().length > 0); - const artworkTitle = $derived(message ? 'Your message' : 'Title'); + const artworkVariant = $derived(hasMessage ? 'message1' : 'upload2'); - const artworkDescription = $derived(message || 'Description Description Description'); + const artworkTitle = $derived( + hasMessage ? 'Your message' : ARTWORK_CARD_DEFAULTS.message.title + ); + + const artworkDescription = $derived( + hasMessage ? message.trim() : ARTWORK_CARD_DEFAULTS.message.description + ); + + const artworkCardMode = $derived(hasMessage ? 'summary' : 'instruction'); onMount(() => { const hadSnapshot = !!getFlowObject('devMessageSnapshot'); @@ -107,7 +116,12 @@
- +
diff --git a/src/routes/upload/+page.svelte b/src/routes/upload/+page.svelte index e99e506..9a14eb5 100644 --- a/src/routes/upload/+page.svelte +++ b/src/routes/upload/+page.svelte @@ -127,6 +127,15 @@ const artworkTitle = $derived(artworkCopy.title); const artworkDescription = $derived(artworkCopy.description); + const artworkCardMode = $derived.by(() => { + if (mode === 'sns') return snsHasImage ? 'summary' : 'instruction'; + + const count = ['color', 'season', 'character', 'location'].filter( + (key) => moodboardTiles[key] + ).length; + return count === 0 ? 'instruction' : 'summary'; + }); + /** create2(시작) → upload1(1장+) → upload2(전체 채움) */ const artworkVariant = $derived.by(() => { if (mode === 'sns') { @@ -193,7 +202,12 @@
- +