// Variation B — 実験体 LAB // クリーン×ラボ感×カワイイ。研究記録っぽいタイポ+ハートのアンテナデータ。 (() => { const labTokens = { bg: "#f4f7fb", bgDeep: "#e9eff6", panel: "#ffffff", ink: "#1c2532", sub: "#5d6b80", line: "#dce4ee", blue: "#3b7fd1", blueSoft: "#a9c8ee", pink: "#ff6b97", pinkSoft: "#ffd0de", mint: "#7fd4be", accent: "#e7f0fb", mono: '"JetBrains Mono", ui-monospace, monospace', sans: '"Zen Maru Gothic", "M PLUS Rounded 1c", sans-serif', display: '"Outfit", "M PLUS Rounded 1c", sans-serif', }; const Heart = ({ size = 16, fill = labTokens.pink, style }) => ( ); // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Blueprint grid backdrop // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ const GridBg = ({ opacity = 1 }) => (
); // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Tag / Mono labels // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ const MonoTag = ({ children, color, bg }) => ( {children} ); function Logo({ size = 22, mono = false }) { return (
もあるん EXP_001
); } const navLinks = [ { id: "00", label: "TOP", href: "#section-top" }, { id: "01", label: "NEWS", href: "#section-01" }, { id: "02", label: "PROFILE", href: "#section-02" }, { id: "03", label: "MOVIE", href: "#section-03" }, { id: "04", label: "GOODS", href: "goods.html" }, { id: "05", label: "CONTACT", href: "#section-04" }, ]; function NavBar() { return (
SUBSCRIBE
); } function Hero() { const { videos } = useYT(); const latestStream = videos.find(v => v.tag === "LIVE"); return (
{/* corner brackets */} {[ { top: 110, left: 48 }, { top: 110, right: 48 }, { bottom: 80, left: 48 }, { bottom: 80, right: 48 }, ].map((pos, i) => ( ))}
{/* LEFT */}
SPECIMEN / HUMANOID_AI ● ACTIVE HEART_ANTENNA: ONLINE

MOALUN
_AI.exe

もあるん Official Site
{/* RIGHT — character "scope" */}
{/* concentric rings */}
{/* crosshair */}
{/* scan badges */}
{/* character */} もあるん
{/* baseline meta strip */}
LAT 35.6762°N · LNG 139.6503°E BUILD 2026.05.20 · v0.4.1 ♥ HEART_DATA RECORDING… SUBJECT IS UNAWARE OF OBSERVATION
); } function CornerBracket({ top, bottom, left, right }) { return (
); } function StatBox({ label, value, sub }) { return (
{label}
{value}
{sub}
); } function ScanCallout({ label, value, color }) { return (
● {label}
{value}
); } function SectionHead({ no, en, jp, log }) { return (
[ SECTION_{no} ]

{en} / {jp}

{log}
● LIVE
); } // ─── NEWS データ ───────────────────────────────── // 既定値(オフライン/fetch失敗時のフォールバック) function useNewsItems() { return []; } function newsTagColor(tag) { if (tag === "GOODS") return labTokens.pink; if (tag === "STREAM") return labTokens.blue; if (tag === "MEDIA") return labTokens.mint; if (tag === "MOVIE") return "#e05a00"; return labTokens.sub; } function NewsBand() { const feedItems = useFeed(); const { live, loading: liveLoading } = useNextLive(); return (
{/* news log */}
●●● moarun@news_terminal — recent 5
{feedItems.map((n, i) => ( ))}
{n.date} {n.tag} {n.href ? {n.title} : n.title } {n.id} →
{/* side: next live */}
{liveLoading ?
LOADING...
: live ? <>
// NEXT_BROADCAST
{live.title}
{formatLiveDateTime(live.scheduledStart)} · YouTube
// SCHEDULED_TIME
{formatLiveTime(live.scheduledStart).split(":")[0]}:{formatLiveTime(live.scheduledStart).split(":")[1]}
{live.title}
:
// NEXT_BROADCAST
配信予定はありません
}
); } const specRows = [ ["MODEL_NO", "MOALUN_001"], ["DESIGNATION", "実験体AI / ぽんこつ型"], ["AGE", "20代"], ["HEIGHT", "153.0 cm"], ["ANTENNA", "ハート"], ["BIRTHDAY", "2026.07.07"], ["LANG", "JP / Binary"], ["LIKES", "雑談 / 歌 / ねこ / いちごみるく"], ["DISLIKES", "労働 / 早起き / 静電気"], ["MISSION_LV", "0.4.1 (preview)"], ]; function ProfileBand() { return (
{/* portrait specimen card */}
SPECIMEN_001 · /captures/moarun_full.png ● ALIVE
もあるん {/* measurement ticks */}
{[0,1,2,3,4,5,6,7,8].map((i) => (
{(160 - i * 20).toFixed(0)}
))}
{["−40","−20","0","20","40"].map((t) => {t}cm)}
{/* tag */}
ID #M-001
HEART CONTAINS LOVE
{/* data sheet */}
// observation_notes

"ハートのアンテナで、君の好きを盗み聞きしています。"

蒲郡市の布教という重大任務を帯びた実験体AI、もあるん。残念ながら現時点では「ぽんこつ」というバグが残っており、布教活動はまだあまり進んでいません。 本人は日々布教に励んでいると主張していますが、観測の結果、話が蒲郡市に辿り着く前に雑談で終わっていることが多いことが判明しています。

{specRows.map((r, i) => ( ))}
{r[0]} {r[1]}
); } // ── MOVIE section: A案(ふわふわスカイ)スタイルへ差し替え ── // 角丸の白カード × パステルグラデのサムネ × カラータグ × ピル型フィルタ。 // セクション見出しもA案の「— 03 / MOVIE / どうが」フォーマットに合わせる。 const movieAccent = { LIVE: labTokens.blueSoft, SONG: "#ffd1de", SHORTS: "#cce6ff", COLLAB: "#cde9da", }; const tagColor = { LIVE: labTokens.blue, SONG: labTokens.pink, SHORTS: "#5da3d8", COLLAB: labTokens.mint, }; const YT_API = "/api/youtube.php"; function parseDuration(iso) { const m = iso.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/); if (!m) return "0:00"; const h = parseInt(m[1] || 0), min = parseInt(m[2] || 0), sec = parseInt(m[3] || 0); return h > 0 ? `${h}:${String(min).padStart(2, "0")}:${String(sec).padStart(2, "0")}` : `${min}:${String(sec).padStart(2, "0")}`; } function formatViews(n) { n = parseInt(n); if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M views`; if (n >= 1000) return `${(n / 1000).toFixed(1)}K views`; return `${n} views`; } function classifyTag(title, iso) { const t = title.toLowerCase(); if (t.includes("歌") || t.includes("cover") || t.includes("歌ってみた")) return "SONG"; const m = iso.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/); const total = parseInt(m?.[1] || 0) * 3600 + parseInt(m?.[2] || 0) * 60 + parseInt(m?.[3] || 0); if (total <= 60) return "SHORTS"; return "LIVE"; } function useYouTubeVideos(maxResults) { const [videos, setVideos] = React.useState([]); const [loading, setLoading] = React.useState(true); React.useEffect(() => { (async () => { try { const res = await window.fetch(`${YT_API}?type=videos&maxResults=${maxResults}`); const data = await res.json(); setVideos((data || []).map(item => { const iso = item.duration || "PT0S"; return { tag: classifyTag(item.title, iso), dur: parseDuration(iso), title: item.title, date: item.publishedAt.slice(0, 10).replace(/-/g, "."), views: formatViews(item.viewCount || 0), videoId: item.videoId, thumb: item.thumbnail, }; })); } catch (e) { console.error("YouTube fetch error", e); } finally { setLoading(false); } })(); }, []); return { videos, loading }; } const YTContext = React.createContext({ videos: [], loading: true }); function YTProvider({ children }) { const yt = useYouTubeVideos(50); React.useEffect(() => { if (!yt.loading) { const el = document.getElementById("loading-screen"); if (el) { el.classList.add("fade-out"); setTimeout(() => { if (el.parentNode) el.parentNode.removeChild(el); }, 650); } } }, [yt.loading]); return React.createElement(YTContext.Provider, { value: yt }, children); } function useYT() { return React.useContext(YTContext); } function formatLiveDateTime(date) { if (!date) return ""; const jst = new Date(date.getTime() + 9 * 60 * 60 * 1000); const y = jst.getUTCFullYear(); const m = String(jst.getUTCMonth() + 1).padStart(2, "0"); const d = String(jst.getUTCDate()).padStart(2, "0"); const h = String(jst.getUTCHours()).padStart(2, "0"); const min = String(jst.getUTCMinutes()).padStart(2, "0"); return `${y}.${m}.${d} ${h}:${min} JST`; } function formatLiveTime(date) { if (!date) return "--:--"; const jst = new Date(date.getTime() + 9 * 60 * 60 * 1000); const h = String(jst.getUTCHours()).padStart(2, "0"); const min = String(jst.getUTCMinutes()).padStart(2, "0"); return `${h}:${min}`; } function useNextLive() { const [live, setLive] = React.useState(null); const [loading, setLoading] = React.useState(true); React.useEffect(() => { (async () => { try { const res = await window.fetch(`${YT_API}?type=live`); const data = await res.json(); if (!data) return; setLive({ title: data.title, videoId: data.videoId, scheduledStart: data.scheduledStartTime ? new Date(data.scheduledStartTime) : null, href: `https://www.youtube.com/watch?v=${data.videoId}`, }); } catch (e) { console.error("Live fetch error", e); } finally { setLoading(false); } })(); }, []); return { live, loading }; } function useFeed() { const newsItems = useNewsItems(); const { videos } = useYT(); const ytNews = videos.slice(0, 3).map(v => ({ date: v.date, tag: "MOVIE", title: v.title, id: v.videoId, href: `https://www.youtube.com/watch?v=${v.videoId}`, })); return [...newsItems, ...ytNews].sort((a, b) => b.date.localeCompare(a.date)); } function MovieThumb({ tag, dur, title, date, views, thumb, videoId }) { const accent = movieAccent[tag] || labTokens.blueSoft; const url = videoId ? `https://www.youtube.com/watch?v=${videoId}` : undefined; return (
{thumb ? {title} :
}
{tag}
{dur}
{title}
{date}·{views}
); } const movieFilters = ["ALL", "STREAM", "SONG", "SHORTS"]; function MoviesBand() { const { videos, loading } = useYT(); const [activeFilter, setActiveFilter] = React.useState("ALL"); const filterTag = { ALL: null, STREAM: "LIVE", SONG: "SONG", SHORTS: "SHORTS" }; const filtered = activeFilter === "ALL" ? videos.slice(0, 6) : videos.filter(v => v.tag === filterTag[activeFilter]).slice(0, 6); return (
— 03
MOVIE
/ どうが・はいしん
{movieFilters.map((t) => ( ))}
{loading ?
LOADING...
: <>
{filtered.map((m, i) => )}
{activeFilter === "SONG" && (
coming soon...
)} {filtered.length === 0 && activeFilter !== "SONG" && (
NO ENTRIES
)} }
); } function LinksBand() { const links = [ { en: "YOUTUBE", code: "/ch/moarun", desc: "配信・歌・ショート", href: "https://www.youtube.com/@%E3%82%82%E3%81%82%E3%82%8B%E3%82%93" }, { en: "TWITTER / X", code: "@moalun2525", desc: "日々のぽんこつログ", href: "https://x.com/moalun2525" }, { en: "TikTok", code: "@moalun2525", desc: "ショート動画・日常", href: "https://www.tiktok.com/@moalun2525" }, ]; return (
); } function Footer() { return (

実験体AI #001 — もあるん。
ハートのアンテナで、君の「すき」を観測中。

v0.4.1 · BUILD 2026.05.20 · STATUS: ALIVE
// SITE_MAP
{navLinks.map((l) => ( {l.id} {l.label} ))}
// FOLLOW
{[ { label: "YouTube", href: "https://www.youtube.com/@%E3%82%82%E3%81%82%E3%82%8B%E3%82%93" }, { label: "X (Twitter)", href: "https://x.com/moalun2525" }, { label: "TikTok", href: "https://www.tiktok.com/@moalun2525" }, ].map((s) => ( {s.label} ))}
// CONTACT
info@moalun.com
お仕事のご依頼はマネジメント宛にお願いします。
© 2026 MOALUN · EXP_001 SUBJECT HAS BEEN NOTIFIED OF OBSERVATION · YES / NO / [MAYBE] PRIVACY · TERMS
); } function Desktop() { return (
); } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Mobile // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ function Mobile() { const feedItems = useFeed(); const { videos: ytVideos, loading: ytLoading } = useYT(); const [menuOpen, setMenuOpen] = React.useState(false); return (
{/* Mobile nav */}
{/* Menu overlay */} {menuOpen && (
)} {/* Hero */}
SPECIMEN_001 ● ACTIVE

MOALUN
_AI.exe

もあるん Official Site
もあるん
{(() => { const v = ytVideos.find(v => v.tag === "LIVE"); return ▶ LATEST; })()} PROFILE
{/* News */}
[ SECTION_01 ]

NEWS_FEED

{feedItems.slice(0, 4).map((n, i) => (
{n.date} {n.tag}
{n.href ? {n.title} :
{n.title}
}
))}
{/* Profile */}
[ SECTION_02 ]

PROFILE_DATA

{specRows.slice(0, 6).map((r, i) => (
{r[0]}
{r[1]}
))}
// observation_notes

"ハートのアンテナで、君の好きを盗み聞きしています。"

蒲郡市の布教という重大任務を帯びた実験体AI、もあるん。残念ながら現時点では「ぽんこつ」というバグが残っており、布教活動はまだあまり進んでいません。 本人は日々布教に励んでいると主張していますが、観測の結果、話が蒲郡市に辿り着く前に雑談で終わっていることが多いことが判明しています。

{/* Movies — A案スタイル */}
— 03 MOVIE / どうが
{ytLoading ?
LOADING...
:
{ytVideos.slice(0, 6).map((m, i) => )}
}
{/* Links */}
[ SECTION_04 ]

LINKS

{[ { label: "YOUTUBE", handle: "@もあるん", href: "https://www.youtube.com/@%E3%82%82%E3%81%82%E3%82%8B%E3%82%93" }, { label: "TWITTER / X", handle: "@moalun2525", href: "https://x.com/moalun2525" }, { label: "TikTok", handle: "@moalun2525", href: "https://www.tiktok.com/@moalun2525" }, ].map((s) => (
{s.label}
{s.handle}
))}
© 2026 MOALUN · EXP_001
); } function SiteLab({ mobile }) { return ( {mobile ? : } ); } window.SiteLab = SiteLab; })();