UI: warm daylight design system (Tailwind v4 @theme palette, gh-* component classes, watercolor grain, Zen Maru Gothic + Klee One fonts), animated SSR-safe GhibliBackground (drifting clouds, meadow hills, soot sprites), and a full reskin of navbar, connect button, dapp page, loan cards, settings modal, and readme. Fixes the bg-white-on-dark loan-card inconsistency. Web3/business logic untouched. Docs: converted docs/ into an Obsidian vault (frontmatter, [[wikilinks]], callouts, Home MOC, folders Architecture/Operations/Audits) and added a full-project audit note (Project Audit 2026-06). Redacted a real leaked Schedy key value from the security audit example (rotate it at Schedy). Also commits the previously-untracked server layer: app/api (cron + tasks routes) and lib (redis, ssrf-guard, task-store). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
109 lines
3.5 KiB
TypeScript
109 lines
3.5 KiB
TypeScript
// Purely decorative, server-rendered Ghibli landscape that sits behind all
|
|
// content. No JS, no state — just layered CSS-animated SVG/divs. Positions and
|
|
// timings are hardcoded so SSR and client markup match (no hydration drift).
|
|
|
|
const clouds = [
|
|
{ top: "8%", scale: 1.0, duration: "70s", delay: "0s", opacity: 0.9 },
|
|
{ top: "18%", scale: 0.7, duration: "95s", delay: "-30s", opacity: 0.75 },
|
|
{ top: "30%", scale: 1.3, duration: "120s", delay: "-70s", opacity: 0.85 },
|
|
{ top: "46%", scale: 0.55, duration: "85s", delay: "-15s", opacity: 0.6 },
|
|
];
|
|
|
|
// Soot sprites (susuwatari) drifting up from the meadow
|
|
const sprites = [
|
|
{ left: "12%", size: 10, duration: "16s", delay: "0s" },
|
|
{ left: "26%", size: 7, duration: "21s", delay: "-6s" },
|
|
{ left: "44%", size: 12, duration: "18s", delay: "-11s" },
|
|
{ left: "61%", size: 8, duration: "23s", delay: "-3s" },
|
|
{ left: "78%", size: 9, duration: "19s", delay: "-14s" },
|
|
{ left: "89%", size: 6, duration: "25s", delay: "-9s" },
|
|
];
|
|
|
|
function Cloud({ opacity }: { opacity: number }) {
|
|
return (
|
|
<svg viewBox="0 0 200 80" width="200" height="80" style={{ opacity }}>
|
|
<g fill="#fffdf7">
|
|
<ellipse cx="60" cy="50" rx="55" ry="26" />
|
|
<ellipse cx="105" cy="40" rx="45" ry="34" />
|
|
<ellipse cx="145" cy="52" rx="42" ry="24" />
|
|
<ellipse cx="95" cy="58" rx="70" ry="20" />
|
|
</g>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
export default function GhibliBackground() {
|
|
return (
|
|
<div aria-hidden className="pointer-events-none fixed inset-0 -z-10 overflow-hidden">
|
|
{/* warm sun glow, lower-left */}
|
|
<div
|
|
className="gh-anim absolute rounded-full"
|
|
style={{
|
|
left: "-6rem",
|
|
top: "-6rem",
|
|
width: "26rem",
|
|
height: "26rem",
|
|
background:
|
|
"radial-gradient(circle, rgba(244,210,122,0.55) 0%, rgba(244,210,122,0) 68%)",
|
|
animation: "breathe 9s ease-in-out infinite",
|
|
}}
|
|
/>
|
|
|
|
{/* drifting clouds */}
|
|
{clouds.map((c, i) => (
|
|
<div
|
|
key={i}
|
|
className="gh-anim absolute left-0"
|
|
style={{
|
|
top: c.top,
|
|
transform: `scale(${c.scale})`,
|
|
animation: `drift ${c.duration} linear infinite`,
|
|
animationDelay: c.delay,
|
|
}}
|
|
>
|
|
<Cloud opacity={c.opacity} />
|
|
</div>
|
|
))}
|
|
|
|
{/* layered meadow hills along the bottom */}
|
|
<svg
|
|
className="absolute bottom-0 left-0 w-full"
|
|
viewBox="0 0 1440 320"
|
|
preserveAspectRatio="none"
|
|
style={{ height: "38vh", minHeight: "220px" }}
|
|
>
|
|
<path
|
|
fill="#9cb87f"
|
|
fillOpacity="0.55"
|
|
d="M0,224 C240,160 420,272 720,224 C1020,176 1200,272 1440,208 L1440,320 L0,320 Z"
|
|
/>
|
|
<path
|
|
fill="#6f9a5a"
|
|
fillOpacity="0.7"
|
|
d="M0,272 C260,224 480,304 760,272 C1040,240 1240,304 1440,272 L1440,320 L0,320 Z"
|
|
/>
|
|
<path
|
|
fill="#466a3a"
|
|
fillOpacity="0.85"
|
|
d="M0,300 C300,276 520,320 820,300 C1120,280 1280,316 1440,300 L1440,320 L0,320 Z"
|
|
/>
|
|
</svg>
|
|
|
|
{/* soot sprites rising from the meadow */}
|
|
{sprites.map((s, i) => (
|
|
<span
|
|
key={i}
|
|
className="gh-soot gh-anim"
|
|
style={{
|
|
left: s.left,
|
|
width: `${s.size}px`,
|
|
height: `${s.size}px`,
|
|
animation: `rise ${s.duration} ease-in-out infinite`,
|
|
animationDelay: s.delay,
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|