import { useState, useEffect, useRef, useCallback } from "react"; import * as d3 from "d3"; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 大沼竜也 LP — Hybrid Dark/Light, Archive-First // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ const COLORS = { dark: "#0a0908", darkSurface: "#141210", darkCard: "#1c1a17", light: "#f7f4ef", lightCard: "#ffffff", cream: "#eee8dc", text: "#1a1714", textSub: "#5a5347", textMuted: "#8a7e6b", accent: "#8b7355", accentLight: "#c4b9a8", gold: "#b8a88a", border: "#e8e2d8", darkBorder: "rgba(255,255,255,0.08)", white: "#f5f0e8", }; // ━━━ Scroll animation hook ━━━ function useScrollReveal(threshold = 0.15) { const ref = useRef(null); const [visible, setVisible] = useState(false); useEffect(() => { const el = ref.current; if (!el) return; const obs = new IntersectionObserver( ([e]) => { if (e.isIntersecting) { setVisible(true); obs.unobserve(el); } }, { threshold } ); obs.observe(el); return () => obs.disconnect(); }, [threshold]); return [ref, visible]; } function Reveal({ children, delay = 0, direction = "up", style = {} }) { const [ref, visible] = useScrollReveal(0.12); const transforms = { up: "translateY(40px)", down: "translateY(-40px)", left: "translateX(40px)", right: "translateX(-40px)", none: "none", }; return (
{children}
); } // ━━━ Parallax scroll hook ━━━ function useParallax(speed = 0.3) { const ref = useRef(null); const [offset, setOffset] = useState(0); useEffect(() => { const handle = () => { if (!ref.current) return; const rect = ref.current.getBoundingClientRect(); const center = rect.top + rect.height / 2; const viewCenter = window.innerHeight / 2; setOffset((center - viewCenter) * speed); }; window.addEventListener("scroll", handle, { passive: true }); handle(); return () => window.removeEventListener("scroll", handle); }, [speed]); return [ref, offset]; } // ━━━ Interactive Causal Network (D3) ━━━ function CausalNetwork() { const svgRef = useRef(null); const containerRef = useRef(null); const [hovered, setHovered] = useState(null); const nodes = [ { id: "身体", x: 0, y: 0, r: 42, primary: true }, { id: "思考", x: -140, y: -100, r: 30 }, { id: "環境", x: 140, y: -100, r: 30 }, { id: "出来事", x: -140, y: 100, r: 30 }, { id: "関係性", x: 140, y: 100, r: 30 }, { id: "感情", x: 0, y: -150, r: 26 }, { id: "社会", x: -200, y: 0, r: 26 }, { id: "時間", x: 200, y: 0, r: 26 }, ]; const links = [ ["身体", "思考"], ["身体", "環境"], ["身体", "出来事"], ["身体", "関係性"], ["身体", "感情"], ["思考", "感情"], ["環境", "社会"], ["出来事", "時間"], ["思考", "出来事"], ["環境", "関係性"], ["関係性", "社会"], ["思考", "社会"], ["感情", "関係性"], ["出来事", "環境"], ["時間", "身体"], ]; useEffect(() => { if (!svgRef.current) return; const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); const w = 520, h = 380; const g = svg.append("g").attr("transform", `translate(${w / 2},${h / 2})`); // Animated pulse rings on body node for (let i = 0; i < 3; i++) { g.append("circle") .attr("cx", 0).attr("cy", 0).attr("r", 42) .attr("fill", "none") .attr("stroke", COLORS.gold) .attr("stroke-width", 0.5) .attr("opacity", 0) .transition() .delay(i * 1200) .duration(3600) .ease(d3.easeCubicOut) .attr("r", 120) .attr("opacity", 0.15) .transition() .duration(0) .attr("r", 42) .attr("opacity", 0) .on("end", function repeat() { d3.select(this) .attr("r", 42).attr("opacity", 0) .transition().delay(i * 1200).duration(3600).ease(d3.easeCubicOut) .attr("r", 120).attr("opacity", 0.15) .transition().duration(0) .attr("r", 42).attr("opacity", 0) .on("end", repeat); }); } // Links links.forEach(([a, b]) => { const na = nodes.find((n) => n.id === a); const nb = nodes.find((n) => n.id === b); g.append("line") .attr("x1", na.x).attr("y1", na.y) .attr("x2", nb.x).attr("y2", nb.y) .attr("stroke", "rgba(184,168,138,0.2)") .attr("stroke-width", 1); }); // Nodes nodes.forEach((n) => { const ng = g.append("g").style("cursor", "pointer"); ng.append("circle") .attr("cx", n.x).attr("cy", n.y).attr("r", n.r) .attr("fill", n.primary ? COLORS.accent : COLORS.darkCard) .attr("stroke", n.primary ? COLORS.gold : "rgba(184,168,138,0.3)") .attr("stroke-width", n.primary ? 2 : 1); ng.append("text") .attr("x", n.x).attr("y", n.y + 1) .attr("text-anchor", "middle").attr("dominant-baseline", "central") .attr("fill", n.primary ? "#fff" : COLORS.white) .attr("font-size", n.primary ? 14 : 11) .attr("font-family", "'Noto Sans JP', sans-serif") .attr("font-weight", n.primary ? 700 : 400) .text(n.id); }); }, []); return (
); } // ━━━ Blog Archive Card ━━━ function BlogCard({ num, title, thinker, concepts, slug, delay }) { const [hover, setHover] = useState(false); const baseUrl = "https://www.somaticstudiojapan.com/tatsuyaonuma-blog"; const url = slug ? `${baseUrl}/${slug}` : baseUrl; return (
setHover(true)} onMouseLeave={() => setHover(false)} style={{ background: hover ? "#fff" : COLORS.lightCard, border: `1px solid ${hover ? COLORS.accentLight : COLORS.border}`, borderRadius: 10, padding: "28px 24px", minWidth: 300, maxWidth: 300, cursor: "pointer", transform: hover ? "translateY(-4px)" : "none", boxShadow: hover ? "0 12px 32px rgba(0,0,0,0.08)" : "0 2px 8px rgba(0,0,0,0.03)", transition: "all 0.3s cubic-bezier(0.16,1,0.3,1)", flexShrink: 0, }} onClick={() => window.open(url, "_blank")} >
ARCHIVE {num}
{title}
{thinker}
{concepts.map((c, i) => ( {c} ))}
); } // ━━━ Podcast Episode Card (Dark) ━━━ function PodcastCard({ title, desc, delay }) { const [hover, setHover] = useState(false); return (
setHover(true)} onMouseLeave={() => setHover(false)} style={{ background: hover ? "rgba(255,255,255,0.1)" : "rgba(255,255,255,0.04)", border: `1px solid ${hover ? "rgba(184,168,138,0.3)" : "rgba(255,255,255,0.06)"}`, borderRadius: 10, padding: "26px 22px", transition: "all 0.3s ease", cursor: "default", }} >
EPISODE
{title}
{desc}
); } // ━━━ Juku Part Card ━━━ function JukuPart({ num, title, question, delay }) { const [hover, setHover] = useState(false); return (
setHover(true)} onMouseLeave={() => setHover(false)} style={{ background: hover ? "#fff" : COLORS.lightCard, border: `1px solid ${COLORS.border}`, borderRadius: 10, padding: "32px 24px", textAlign: "center", transform: hover ? "translateY(-3px)" : "none", boxShadow: hover ? "0 8px 24px rgba(0,0,0,0.06)" : "none", transition: "all 0.3s ease", }} >
PART {num}
{title}
{question}
); } // ━━━ Thinker pill ━━━ function ThinkerPill({ name, delay }) { const [hover, setHover] = useState(false); return ( setHover(true)} onMouseLeave={() => setHover(false)} style={{ display: "inline-block", fontFamily: "'Noto Sans JP', sans-serif", fontSize: 13, fontWeight: 400, background: hover ? COLORS.accent : "rgba(255,255,255,0.06)", color: hover ? "#fff" : "rgba(245,240,232,0.7)", border: `1px solid ${hover ? COLORS.accent : "rgba(255,255,255,0.1)"}`, borderRadius: 999, padding: "9px 22px", transition: "all 0.3s ease", cursor: "default", }} > {name} ); } // ━━━━━━━━━━━━━━━━━━━━━━━━━ // MAIN COMPONENT // ━━━━━━━━━━━━━━━━━━━━━━━━━ export default function LP() { const [scrollY, setScrollY] = useState(0); const [heroParRef, heroOffset] = useParallax(0.2); useEffect(() => { const h = () => setScrollY(window.scrollY); window.addEventListener("scroll", h, { passive: true }); return () => window.removeEventListener("scroll", h); }, []); const heroOpacity = Math.max(0, 1 - scrollY / 600); const blogs = [ { num: "01", title: "〈身〉が知っていること", thinker: "市川浩 × メルロ=ポンティ", concepts: ["精神でも物質でもない〈身〉", "身体図式", "原感覚"], slug: "-" }, { num: "02", title: "影を慈しむ身体", thinker: "谷崎潤一郎", concepts: ["陰翳と身体", "脱力の哲学", "暗がりの感覚"], slug: "-1" }, { num: "03", title: "止まらない心、動かない身体", thinker: "沢庵禅師", concepts: ["不動智", "思考の檻", "注意の解放"], slug: "-2" }, { num: "04", title: "疲れを知らない身体という嘘", thinker: "ハン・ビョンチョル", concepts: ["疲労社会", "身体の教養", "能力主義と身体"], slug: "e27clas5yhkaw2g3x23e2xja7pmls2" }, { num: "05", title: "あなたが繊細なのではない", thinker: "ピーター・ラヴィーン", concepts: ["名前がつく前の身体", "HSPの再解釈", "閉ざされ"], slug: "ey9m3y4nsmahw4dz6calwc77b8lg2c" }, { num: "06", title: "言葉になる前の知", thinker: "ユージン・ジェンドリン", concepts: ["暗在的身体", "フェルトセンス", "言語化以前"], slug: "swwetttmw8nybbal8xdxakbt3w754s" }, { num: "07", title: "「感謝しなさい」では感謝できない", thinker: "アントニオ・ダマシオ", concepts: ["ソマティック・マーカー", "感情の身体性", "原感覚"], slug: "6pse7bl6sctlbjgtrwl9zt7pkz83sl" }, { num: "08", title: "眠れない夜は、身体が関を閉じている", thinker: "黄帝内経", concepts: ["衛気の生理学", "東洋医学と不眠", "開かれ/閉ざされ"], slug: null }, { num: "09", title: "同じ街が違って見える日", thinker: "J.J. ギブソン", concepts: ["アフォーダンス", "環境と身体", "知覚の生態学"], slug: "gibson-affordance-body" }, { num: "10", title: "考えすぎる人は、考えていない", thinker: "中村雄二郎", concepts: ["臨床の知", "パトスの知", "思考の檻"], slug: "56677769e59caf4f3bz9f9ae8yc4lr" }, { num: "11", title: "「自分がわからない」のは、頭のせいではない", thinker: "千葉雅也", concepts: ["センスの身体性", "直観と原感覚", "意味以前の層"], slug: null }, ]; const podcasts = [ { title: "考えすぎる人は、考えていない", desc: "中村雄二郎の「臨床の知」を手がかりに、思考の檻と身体のつながりを語る" }, { title: "同じ街が違って見える日", desc: "ギブソンのアフォーダンスと身体合理性。環境が変わるのではなく、身体が変わる" }, { title: "疲れを知らない身体という嘘", desc: "ハン・ビョンチョルの疲労社会。能力主義が身体に何をしているか" }, { title: "入れ替え可能性", desc: "個人の唯一性と身体。入れ替え不可能な存在としての身体を考える" }, { title: "「自分がわからない」のは、頭のせいではない", desc: "千葉雅也のセンスの哲学から、直観と原感覚の接続を探る" }, ]; const thinkers = [ "メルロ=ポンティ", "市川浩", "ユージン・ジェンドリン", "J.J. ギブソン", "アントニオ・ダマシオ", "ピーター・ラヴィーン", "中村雄二郎", "千葉雅也", "ハン・ビョンチョル", "谷崎潤一郎", "沢庵禅師", "黄帝内経", "高岡英夫", "田中彰吾", "世阿弥", "湯浅泰雄", "ポージェス", ]; const reelTopics = [ "性格が悪いのではなく身体が不合理なだけ", "自分がわからないの正体", "肩を揉んでも治らない本当の理由", "考えすぎを止める方法は考えないこと", "怒りの正体は身体のアラーム", "呼吸が浅い人に深呼吸は逆効果", "HSPは性格ではない", "身体が開くと街が変わる", "背骨は一本の棒ではない", "脱力は技術である", "感謝は身体から始まる", "AI時代に身体が必要な理由", ]; // ─── Shared section styles ─── const sectionDark = { background: COLORS.dark, color: COLORS.white, padding: "100px 0", position: "relative", overflow: "hidden", }; const sectionLight = { background: COLORS.light, color: COLORS.text, padding: "100px 0", }; const sectionCream = { background: COLORS.cream, color: COLORS.text, padding: "100px 0", }; const container = { maxWidth: 1120, margin: "0 auto", padding: "0 28px", }; const narrowStyle = { maxWidth: 720, margin: "0 auto" }; const labelStyle = { fontFamily: "'Noto Sans JP', sans-serif", fontSize: 11, fontWeight: 500, letterSpacing: "0.25em", marginBottom: 12, }; const headingStyle = { fontFamily: "'Noto Sans JP', sans-serif", fontSize: 26, fontWeight: 700, lineHeight: 1.45, marginBottom: 20, letterSpacing: "0.02em", }; const subHeadingStyle = { fontFamily: "'Noto Sans JP', sans-serif", fontSize: 14, lineHeight: 1.85, marginBottom: 48, }; return (
{/* ════════════════════════════════════════════ HERO — DARK, MINIMAL ════════════════════════════════════════════ */}
{/* Subtle grain overlay */}
SOMATIC STUDIO JAPAN

大沼竜也

TATSUYA ONUMA

{[ "鍼灸師・柔道整復師・按摩マッサージ指圧師", "臨床17年", "著書 韓国語翻訳出版", "Instagram 26,000 followers", "サブスク会員 60名", ].map((s, i) => ( {s} ))}
{/* ════════════════════════════════════════════ QUESTIONS — LIGHT, FULL-WIDTH TYPOGRAPHY ════════════════════════════════════════════ */}
{[ "考えれば考えるほど、答えから遠ざかっていないか。", "性格の問題だと思っていたことが、身体の問題だったとしたら。", "あなたの身体は今、何と言っているか。", ].map((q, i) => (

{q}

))}
{/* ════════════════════════════════════════════ PHILOSOPHY + CAUSAL NETWORK — DARK ════════════════════════════════════════════ */}
PHILOSOPHY

因果のネットワーク——
身体は「重い変数」ではなく、すべてを感じ取る場所である

身体も環境も思考も出来事も、すべて因果のネットワークの中の変数である。変数間に客観的な重みの序列はない。ただし、すべての変数を感じ取る場所は自分の身体であり、身体は経験の起点である。

{[ ["自立性", "施術者に依存させず、本人が自分自身で感じ対処できるようにする"], ["解剖学的接続", "肋骨、頸椎、筋膜——具体的な構造と感覚を結びつける"], ["上書きではなく並べる", "身体の声と精神の声を机に並べて比較するリテラシー"], ["生の中で動く", "歩く、立つ、洗濯をする——日常のすべてが実践の場"], ].map(([t, d], i) => (
{t}
{d}
))}
{/* ════════════════════════════════════════════ BLOG — LIGHT, SCROLLABLE ARCHIVE (HERO SECTION) ════════════════════════════════════════════ */}
BLOG — 身体知の書庫

11人の思想家との対話、11の身体知

市川浩からメルロ=ポンティ、ギブソン、ダマシオ、千葉雅也まで。東洋医学、現象学、生態心理学、神経科学を横断し、身体性の解像度を高める連載。

{blogs.map((b, i) => ( ))}
すべての記事を読む →
{/* ════════════════════════════════════════════ PODCAST — DARK (HERO SECTION) ════════════════════════════════════════════ */}
PODCAST

ポッドキャスト

ブログ記事を声で語り直す。思想の温度を、そのまま届ける。

{podcasts.map((p, i) => ( ))}