feat: add flower db images
* feat: add options/map flow, dev seed, and artwork fixes Options page, Kakao map with florist order message, dev tooling, and create/message dummy gating — without secrets in .env.example. Co-authored-by: Cursor <cursoragent@cursor.com> * with generating page + art work * with flower images --------- Co-authored-by: 이지은 <ijieun@ijieun-ui-MacBookPro.local> Co-authored-by: Cursor <cursoragent@cursor.com>
10
.env.example
@@ -8,7 +8,11 @@ GEMINI_TEXT_MODEL=gemini-2.5-flash-lite
|
|||||||
IMAGE_PROVIDER=openai
|
IMAGE_PROVIDER=openai
|
||||||
OPENAI_API_KEY=your_openai_api_key_here
|
OPENAI_API_KEY=your_openai_api_key_here
|
||||||
OPENAI_IMAGE_MODEL=gpt-image-1
|
OPENAI_IMAGE_MODEL=gpt-image-1
|
||||||
|
# Bouquet preview (generating flow)
|
||||||
OPENAI_IMAGE_SIZE=1024x1024
|
OPENAI_IMAGE_SIZE=1024x1024
|
||||||
|
# Flower catalog batch (scripts/generate-flower-catalog.js) — portrait cards
|
||||||
|
OPENAI_IMAGE_CATALOG_SIZE=1024x1536
|
||||||
|
OPENAI_IMAGE_CATALOG_QUALITY=low
|
||||||
GEMINI_IMAGE_MODEL=gemini-3.1-flash-image
|
GEMINI_IMAGE_MODEL=gemini-3.1-flash-image
|
||||||
|
|
||||||
# Kakao REST API (shop search for /map)
|
# Kakao REST API (shop search for /map)
|
||||||
@@ -25,3 +29,9 @@ SUPABASE_STORAGE_BUCKET=flower-bouquets
|
|||||||
# Dev seed button: shown only when `npm run dev` (production build hides it).
|
# Dev seed button: shown only when `npm run dev` (production build hides it).
|
||||||
# To mute during local dev, set DEV_SEED_MUTED = true in DevSeedButton.svelte.
|
# To mute during local dev, set DEV_SEED_MUTED = true in DevSeedButton.svelte.
|
||||||
# Replace static/dev/bouquet-{s,m,l}.jpg with real photos for richer UI previews.
|
# Replace static/dev/bouquet-{s,m,l}.jpg with real photos for richer UI previews.
|
||||||
|
|
||||||
|
# Flower catalog (result cards) — one-time batch, not per user request:
|
||||||
|
# npm run generate:flowers -- --dry-run
|
||||||
|
# npm run generate:flowers -- --missing-only
|
||||||
|
# npm run generate:flowers -- --ids 7,14
|
||||||
|
# Output: static/flowers/{flowerDB.id}.png
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"prepare": "svelte-kit sync || echo ''",
|
"prepare": "svelte-kit sync || echo ''",
|
||||||
"lint": "prettier --check . && eslint .",
|
"lint": "prettier --check . && eslint .",
|
||||||
"format": "prettier --write ."
|
"format": "prettier --write .",
|
||||||
|
"generate:flowers": "node scripts/generate-flower-catalog.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/compat": "^2.0.4",
|
"@eslint/compat": "^2.0.4",
|
||||||
|
|||||||
172
scripts/generate-flower-catalog.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/**
|
||||||
|
* flowerDB 카탈로그 이미지 batch 생성 (1회 실행 → static/flowers/{id}.png)
|
||||||
|
*
|
||||||
|
* 사용:
|
||||||
|
* npm run generate:flowers -- --dry-run
|
||||||
|
* npm run generate:flowers -- --missing-only
|
||||||
|
* npm run generate:flowers -- --ids 7,14,18
|
||||||
|
* npm run generate:flowers -- --force --ids 14
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import OpenAI from 'openai';
|
||||||
|
import { flowerDB } from '../src/lib/server/flowerFlow/flowerDB.js';
|
||||||
|
import {
|
||||||
|
buildFlowerCardPrompt,
|
||||||
|
getPromptNameForFlower
|
||||||
|
} from '../src/lib/flowerFlow/flowerCatalogPrompt.js';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const ROOT = join(__dirname, '..');
|
||||||
|
const OUT_DIR = join(ROOT, 'static', 'flowers');
|
||||||
|
|
||||||
|
/** @param {string} flag */
|
||||||
|
function hasFlag(flag) {
|
||||||
|
return process.argv.includes(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} flag */
|
||||||
|
function readFlagValue(flag) {
|
||||||
|
const index = process.argv.indexOf(flag);
|
||||||
|
if (index === -1) return null;
|
||||||
|
return process.argv[index + 1] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadEnvFile() {
|
||||||
|
const envPath = join(ROOT, '.env');
|
||||||
|
if (!existsSync(envPath)) return;
|
||||||
|
|
||||||
|
for (const line of readFileSync(envPath, 'utf8').split('\n')) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||||
|
|
||||||
|
const separator = trimmed.indexOf('=');
|
||||||
|
if (separator === -1) continue;
|
||||||
|
|
||||||
|
const key = trimmed.slice(0, separator).trim();
|
||||||
|
const value = trimmed.slice(separator + 1).trim();
|
||||||
|
// .env 값을 항상 우선 (터미널에 남은 옛 OPENAI_API_KEY 덮어씀)
|
||||||
|
if (key) {
|
||||||
|
process.env[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} value */
|
||||||
|
function parseIdList(value) {
|
||||||
|
return value
|
||||||
|
.split(',')
|
||||||
|
.map((part) => Number(part.trim()))
|
||||||
|
.filter((id) => Number.isInteger(id) && id > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {number} ms */
|
||||||
|
function wait(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} prompt
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
async function generateFlowerPng(prompt) {
|
||||||
|
const apiKey = process.env.OPENAI_API_KEY;
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error('OPENAI_API_KEY is not configured (.env)');
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = process.env.OPENAI_IMAGE_CATALOG_SIZE || '1024x1536';
|
||||||
|
const quality = process.env.OPENAI_IMAGE_CATALOG_QUALITY || 'low';
|
||||||
|
|
||||||
|
const client = new OpenAI({ apiKey });
|
||||||
|
const response = await client.images.generate({
|
||||||
|
model: process.env.OPENAI_IMAGE_MODEL || 'gpt-image-1',
|
||||||
|
prompt,
|
||||||
|
size,
|
||||||
|
quality,
|
||||||
|
n: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const image = response.data?.[0];
|
||||||
|
if (image?.b64_json) {
|
||||||
|
return Buffer.from(image.b64_json, 'base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image?.url) {
|
||||||
|
const imageResponse = await fetch(image.url);
|
||||||
|
return Buffer.from(await imageResponse.arrayBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('OpenAI image model did not return image data');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
loadEnvFile();
|
||||||
|
|
||||||
|
const dryRun = hasFlag('--dry-run');
|
||||||
|
const force = hasFlag('--force');
|
||||||
|
const missingOnly = hasFlag('--missing-only');
|
||||||
|
const delayMs = Number(readFlagValue('--delay') ?? 2000);
|
||||||
|
const idsArg = readFlagValue('--ids');
|
||||||
|
|
||||||
|
/** @type {typeof flowerDB} */
|
||||||
|
let targets = [...flowerDB];
|
||||||
|
|
||||||
|
if (idsArg) {
|
||||||
|
const ids = new Set(parseIdList(idsArg));
|
||||||
|
targets = targets.filter((flower) => ids.has(flower.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingOnly) {
|
||||||
|
targets = targets.filter((flower) => !existsSync(join(OUT_DIR, `${flower.id}.png`)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targets.length === 0) {
|
||||||
|
console.log('생성할 꽃이 없습니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdirSync(OUT_DIR, { recursive: true });
|
||||||
|
|
||||||
|
const size = process.env.OPENAI_IMAGE_CATALOG_SIZE || '1024x1536';
|
||||||
|
const quality = process.env.OPENAI_IMAGE_CATALOG_QUALITY || 'low';
|
||||||
|
console.log(`대상: ${targets.length}종 · ${size} · quality=${quality}${dryRun ? ' (dry-run)' : ''}`);
|
||||||
|
|
||||||
|
for (const flower of targets) {
|
||||||
|
const outPath = join(OUT_DIR, `${flower.id}.png`);
|
||||||
|
const promptName = getPromptNameForFlower(flower);
|
||||||
|
const prompt = buildFlowerCardPrompt(promptName);
|
||||||
|
|
||||||
|
if (existsSync(outPath) && !force) {
|
||||||
|
console.log(`skip id=${flower.id} ${flower.name} (already exists)`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\nid=${flower.id} ${flower.name}`);
|
||||||
|
console.log(`promptName: ${promptName}`);
|
||||||
|
console.log(`prompt: ${prompt}`);
|
||||||
|
|
||||||
|
if (dryRun) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const bytes = await generateFlowerPng(prompt);
|
||||||
|
writeFileSync(outPath, bytes);
|
||||||
|
console.log(`saved → static/flowers/${flower.id}.png`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`failed id=${flower.id}:`, err instanceof Error ? err.message : err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delayMs > 0) {
|
||||||
|
await wait(delayMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n완료.');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
49
src/lib/flowerFlow/flowerCatalogPrompt.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/** OpenAI flower card batch — 프롬프트 이름·템플릿 (flowerDB 레코드는 수정하지 않음) */
|
||||||
|
|
||||||
|
/** @type {Record<number, string>} id → 프롬프트에 넣을 영문 꽃 이름 */
|
||||||
|
export const PROMPT_NAME_OVERRIDES = {
|
||||||
|
33: 'red spider lily',
|
||||||
|
36: 'bird of paradise flower',
|
||||||
|
40: 'wax flower',
|
||||||
|
41: 'caspia statice',
|
||||||
|
47: 'craspedia billy balls',
|
||||||
|
49: "queen anne's lace",
|
||||||
|
50: 'nigella love-in-a-mist',
|
||||||
|
60: 'statice limonium',
|
||||||
|
62: 'strawflower helichrysum',
|
||||||
|
64: 'chinese lantern physalis',
|
||||||
|
65: 'globe amaranth gomphrena',
|
||||||
|
74: 'pussy willow branch',
|
||||||
|
80: 'foxtail millet stem',
|
||||||
|
83: 'silver grass miscanthus'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DB display name → 프롬프트용 이름 (괄호 앞, lowercase)
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
|
export function normalizeFlowerPromptName(name) {
|
||||||
|
const primary = name.split('(')[0].trim();
|
||||||
|
return primary.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ id: number, name: string }} flower
|
||||||
|
*/
|
||||||
|
export function getPromptNameForFlower(flower) {
|
||||||
|
return PROMPT_NAME_OVERRIDES[flower.id] ?? normalizeFlowerPromptName(flower.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} flowerName — getPromptNameForFlower 결과
|
||||||
|
*/
|
||||||
|
export function buildFlowerCardPrompt(flowerName) {
|
||||||
|
return (
|
||||||
|
`A single ${flowerName} flower stem, isolated object, transparent background, ` +
|
||||||
|
`realistic botanical style, front-facing, centered composition, ` +
|
||||||
|
`full stem visible from base to bloom, flower head in upper third of frame, ` +
|
||||||
|
`stem centered vertically, consistent catalog framing for all species, ` +
|
||||||
|
`no vase, no bouquet, no hand, no text, soft natural lighting, consistent scale, ` +
|
||||||
|
`PNG asset for UI card`
|
||||||
|
);
|
||||||
|
}
|
||||||
13
src/lib/flowerFlow/flowerImagePaths.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/** flowerDB id → result 꽃 카드용 정적 이미지 경로 (런타임 AI 생성 없음) */
|
||||||
|
|
||||||
|
export const FLOWER_IMAGE_BASE = '/flowers';
|
||||||
|
export const FLOWER_IMAGE_PLACEHOLDER = `${FLOWER_IMAGE_BASE}/placeholder.svg`;
|
||||||
|
|
||||||
|
/** @param {number} id flowerDB id (1–93) */
|
||||||
|
export function getFlowerImageSrc(id) {
|
||||||
|
if (!Number.isFinite(id) || id < 1) {
|
||||||
|
return FLOWER_IMAGE_PLACEHOLDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${FLOWER_IMAGE_BASE}/${id}.png`;
|
||||||
|
}
|
||||||
BIN
static/flowers/1.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
static/flowers/10.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
static/flowers/11.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
static/flowers/12.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
static/flowers/13.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/14.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/15.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
static/flowers/16.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/17.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
static/flowers/18.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/19.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
static/flowers/2.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/20.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/21.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/22.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/23.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
static/flowers/24.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/25.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
static/flowers/26.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
static/flowers/27.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/28.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
static/flowers/29.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
static/flowers/3.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/30.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/31.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/32.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/33.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
static/flowers/34.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/35.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
static/flowers/36.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/37.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
static/flowers/38.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/39.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/4.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
static/flowers/40.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/41.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/42.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/43.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/44.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/45.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/46.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/47.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/48.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
static/flowers/49.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/5.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/50.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/51.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/52.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/53.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
static/flowers/54.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/55.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
static/flowers/56.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/57.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/58.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/59.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/6.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
static/flowers/60.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/61.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/62.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/63.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/64.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/65.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/66.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
static/flowers/67.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/68.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
static/flowers/69.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/7.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/70.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/71.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
static/flowers/72.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
static/flowers/73.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/74.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/75.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/76.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/77.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/78.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
static/flowers/79.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/8.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/80.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/81.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
static/flowers/82.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/83.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/84.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
static/flowers/85.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/86.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/87.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/88.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/89.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
static/flowers/9.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
static/flowers/90.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
static/flowers/91.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/92.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/flowers/93.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
7
static/flowers/placeholder.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="640" viewBox="0 0 512 640" fill="none">
|
||||||
|
<rect width="512" height="640" fill="#E8E8E8"/>
|
||||||
|
<circle cx="256" cy="200" r="56" fill="#CFCFCF"/>
|
||||||
|
<rect x="248" y="248" width="16" height="280" rx="8" fill="#B8B8B8"/>
|
||||||
|
<ellipse cx="220" cy="420" rx="28" ry="14" fill="#CFCFCF"/>
|
||||||
|
<ellipse cx="292" cy="460" rx="28" ry="14" fill="#CFCFCF"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 405 B |