// app.jsx — Dream diagnosis single-flow app // User writes a dream + small Q's → AI generates a diagnosis + dream type card const { useState, useEffect, useRef, useMemo } = React; // ============== util ============== function todayInfo() { const d = new Date(); const yobi = ["日", "月", "火", "水", "木", "金", "土"]; const month = ["JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"]; // moon phase const lp = 29.530588853; const known = new Date(2000, 0, 6, 18, 14).getTime() / 86400000; const days = d.getTime() / 86400000; const phase = ((days - known) % lp + lp) % lp / lp; let phaseName, phaseGlyph; if (phase < 0.04 || phase > 0.96) { phaseName = "新月"; phaseGlyph = "new"; } else if (phase < 0.22) { phaseName = "三日月"; phaseGlyph = "crescent-w"; } else if (phase < 0.28) { phaseName = "上弦"; phaseGlyph = "half-w"; } else if (phase < 0.47) { phaseName = "十三夜"; phaseGlyph = "gibbous-w"; } else if (phase < 0.53) { phaseName = "満月"; phaseGlyph = "full"; } else if (phase < 0.72) { phaseName = "居待月"; phaseGlyph = "gibbous-e"; } else if (phase < 0.78) { phaseName = "下弦"; phaseGlyph = "half-e"; } else { phaseName = "有明月"; phaseGlyph = "crescent-e"; } return { iso: d.toISOString().slice(0,10), full: `${d.getFullYear()}.${String(d.getMonth()+1).padStart(2,"0")}.${String(d.getDate()).padStart(2,"0")} ${yobi[d.getDay()]}`, en: `${month[d.getMonth()]} ${String(d.getDate()).padStart(2,"0")}, ${d.getFullYear()}`, phase, phaseName, phaseGlyph, }; } function MoonGlyph({ kind, size = 14 }) { const cx = size/2, cy = size/2, r = size/2 - 1; const off = "#C89B5E"; const on = "#FAF2E1"; switch (kind) { case "full": return ; case "new": return ; case "half-w": return ; case "half-e": return ; case "crescent-w": return ; case "crescent-e": return ; case "gibbous-w": return ; case "gibbous-e": return ; default: return null; } } const MOODS = [ { id: "しあわせ", emoji: "✿" }, { id: "ふしぎ", emoji: "✦" }, { id: "なつかしい", emoji: "❀" }, { id: "せつない", emoji: "✧" }, { id: "ふあん", emoji: "✶" }, { id: "こわい", emoji: "✺" }, { id: "わからない", emoji: "◌" }, ]; const PEOPLE = ["自分だけ", "知っている人", "知らない人", "好きな人", "家族", "動物", "誰もいない"]; const COLORS = [ { id: "白", sw: "#F4ECDA" }, { id: "青", sw: "#7A9BB8" }, { id: "赤", sw: "#C76060" }, { id: "ピンク", sw: "#E8B5BA" }, { id: "黄", sw: "#E8C868" }, { id: "緑", sw: "#9CB89A" }, { id: "紫", sw: "#A893B8" }, { id: "金", sw: "#D4A574" }, { id: "黒", sw: "#3A2E26" }, { id: "たくさん", sw: "linear-gradient(135deg,#E8B5BA,#C9B8D9,#9CB89A)" }, { id: "覚えてない", sw: "rgba(74,56,40,0.1)" }, ]; // ========== InputForm ========== function InputForm({ onSubmit, initialData }) { const [dream, setDream] = useState(initialData?.dream || ""); const [mood, setMood] = useState(initialData?.mood || null); const [people, setPeople] = useState(initialData?.people || []); const [colors, setColors] = useState(initialData?.colors || []); const [image, setImage] = useState(initialData?.image || null); // dataURL const [shaking, setShaking] = useState(false); const taRef = useRef(null); const fileRef = useRef(null); const togglePerson = (p) => setPeople(prev => prev.includes(p) ? prev.filter(x => x !== p) : [...prev, p]); const toggleColor = (c) => setColors(prev => prev.includes(c) ? prev.filter(x => x !== c) : [...prev, c]); const handleFile = (file) => { if (!file || !file.type.startsWith("image/")) return; const reader = new FileReader(); reader.onload = (ev) => { // Resize for storage efficiency const img = new Image(); img.onload = () => { const max = 1200; const ratio = Math.min(1, max / Math.max(img.width, img.height)); const w = Math.round(img.width * ratio); const h = Math.round(img.height * ratio); const c = document.createElement("canvas"); c.width = w; c.height = h; c.getContext("2d").drawImage(img, 0, 0, w, h); setImage(c.toDataURL("image/jpeg", 0.85)); }; img.src = ev.target.result; }; reader.readAsDataURL(file); }; const onFileChange = (e) => handleFile(e.target.files?.[0]); const onDrop = (e) => { e.preventDefault(); handleFile(e.dataTransfer.files?.[0]); }; const submit = () => { if (dream.trim().length < 10) { setShaking(true); setTimeout(() => setShaking(false), 350); taRef.current?.focus(); return; } onSubmit({ dream: dream.trim(), mood, people, colors, image }); }; return (
01

夢の内容を聞かせてください

Tell me your dream