You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

171 lines
7.3 KiB
JavaScript

/* 아리 — 하루 마감 브리핑 (이브닝 다크 톤) */
const { useState, useEffect } = React;
const W = window.AriWrap;
/* ---------- 아이콘 ---------- */
const WP = {
spark: "M12 3l1.9 5.2L19 10l-5.1 1.8L12 17l-1.9-5.2L5 10l5.1-1.8z|M19 4v3|M5 17v3",
moon: "M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z",
back: "M19 12H5|M12 19l-7-7 7-7",
tick: "M20 6 9 17l-5-5",
cal: "M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z",
arrow: "M5 12h14|M13 6l6 6-6 6",
swap: "M16 3h5v5|M21 3l-7 7|M8 21H3v-5|M3 21l7-7",
rain: "M16 13v6M8 13v6M12 15v6|M20 16.6A5 5 0 0 0 18 7h-1.3A8 8 0 1 0 4 14.9",
pen: "M12 20h9|M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z",
bell: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9|M10.3 21a1.94 1.94 0 0 0 3.4 0",
sun: "M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10z|M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4",
};
function I({ d, w }) {
return (
<svg className="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor"
strokeWidth={w || 1.9} strokeLinecap="round" strokeLinejoin="round">
{WP[d].split("|").map((s, i) => <path key={i} d={s} />)}
</svg>
);
}
function load(key, fallback) {
try {
const v = localStorage.getItem("ari.wrap." + key);
return v === null ? fallback : JSON.parse(v);
} catch (e) { return fallback; }
}
function save(key, val) {
try { localStorage.setItem("ari.wrap." + key, JSON.stringify(val)); } catch (e) {}
}
function App() {
const [sleep, setSleep] = useState(() => load("sleep", false));
const [note, setNote] = useState(() => load("note", ""));
const [undone, setUndone] = useState({}); // rollover id → true(되돌림)
useEffect(() => save("sleep", sleep), [sleep]);
useEffect(() => save("note", note), [note]);
const toggleRoll = (id) => setUndone((u) => ({ ...u, [id]: !u[id] }));
return (
<div className="wrap">
{/* ---- 상단 ---- */}
<header className="w-top">
<a className="w-back" href="대시보드.html"><I d="back" />대시보드</a>
<div className="w-now">{W.date} · {W.now}</div>
</header>
{/* ---- 인사 ---- */}
<section className="w-hero">
<span className="w-badge"><I d="moon" />하루 마감 브리핑</span>
<h1>오늘 하루, 수고했어요 <em>{W.user.name}</em></h1>
<p className="w-lede" dangerouslySetInnerHTML={{ __html: W.lede }} />
</section>
{/* ---- 오늘 숫자 ---- */}
<section className="w-stats">
{W.stats.map((s, i) => (
<div className="ws" key={i}>
<div className="ws-n">{s.n}<span className="u">{s.unit}</span></div>
<div className="ws-l">{s.label}{s.sub && <em> · {s.sub}</em>}</div>
</div>
))}
</section>
<div className="w-cols">
{/* ============ 왼쪽 — 오늘 정리 ============ */}
<div className="w-col">
<section className="w-card">
<div className="w-ch"><I d="tick" /><h3>오늘 해낸 </h3></div>
<div className="hl-list">
{W.highlights.map((h, i) => (
<div className={"hl" + (h.big ? " big" : "")} key={i} style={{ "--tone": `var(--${h.tone})` }}>
<span className="hl-tick"><I d="tick" w="3" /></span>
<div className="hl-body">
<div className="hl-title">{h.title}</div>
<div className="hl-meta">{h.meta}</div>
</div>
</div>
))}
</div>
</section>
{/* 한 줄 회고 */}
<section className="w-card">
<div className="w-ch"><I d="pen" /><h3>오늘 </h3><span className="w-hint"> </span></div>
<div className="w-note">
<input
value={note}
onChange={(e) => setNote(e.target.value)}
placeholder="오늘은 어떤 하루였나요? 한 줄이면 충분해요…"
/>
{note.trim() && <span className="w-note-ok"><I d="tick" w="3" />저장됨</span>}
</div>
</section>
</div>
{/* ============ 오른쪽 — 내일 준비 ============ */}
<div className="w-col">
<section className="w-card">
<div className="w-ch"><I d="swap" /><h3>남은 일은 내일로 옮겨뒀어요</h3></div>
<div className="ro-list">
{W.rollover.map((r) => {
const off = undone[r.id];
return (
<div className={"ro" + (off ? " off" : "")} key={r.id}>
<div className="ro-body">
<div className="ro-title">{r.title}<span className="ro-proj">{r.project}</span></div>
<div className="ro-to"><I d="arrow" />{off ? "이월 취소됨 — 오늘 할 일로 남겨둘게요" : r.to}</div>
{!off && <div className="ro-why">{r.reason}</div>}
</div>
<button className="ro-undo" onClick={() => toggleRoll(r.id)}>
{off ? "다시 이월" : "되돌리기"}
</button>
</div>
);
})}
</div>
</section>
<section className="w-card">
<div className="w-ch"><I d="cal" /><h3>내일 미리보기</h3><span className="w-hint"><I d="rain" />{W.weatherTomorrow}</span></div>
<div className="tm-list">
{W.tomorrow.map((t, i) => (
<div className="tm" key={i} style={{ "--tone": `var(--${t.tone})` }}>
<span className="tm-time">{t.time}</span>
<span className="tm-title">{t.title}</span>
{t.note && <span className="tm-note"><I d="spark" />{t.note}</span>}
</div>
))}
</div>
<div className="prep">
<div className="prep-label"><I d="spark" />아리가 미리 끝내둔 준비</div>
{W.prep.map((p, i) => (
<div className="prep-row" key={i}>
<span className="prep-tick"><I d="tick" w="3" /></span>
<span className="prep-text">{p.text}</span>
<button className="prep-undo">{p.undo}</button>
</div>
))}
</div>
</section>
</div>
</div>
{/* ---- 수면 모드 CTA ---- */}
<section className={"w-sleep" + (sleep ? " on" : "")}>
<div className="ws-ic"><I d={sleep ? "bell" : "moon"} /></div>
<div className="ws-body">
<div className="ws-title">{sleep ? "수면 모드가 켜져 있어요" : "오늘은 여기까지"}</div>
<div className="ws-sub">{sleep ? W.sleepNote : "수면 모드를 켜면 알림을 음소거하고, 내일 아침 브리핑을 예약해둘게요."}</div>
</div>
<button className="ws-cta" onClick={() => setSleep((s) => !s)}>
{sleep ? "해제" : "수면 모드 시작"}
</button>
</section>
<footer className="w-foot">아리는 밤에도 메일과 일정을 조용히 정리해둘게요 · 급한 일만 깨워드려요</footer>
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);