/* shell.jsx — router, header, cursor, preloader, footer */

const { useState, useEffect, useRef, useContext, createContext, useMemo, useCallback } = React;

// ---------------- Language ----------------
const KR_DICT = {
  // header / nav
  "Index": "인덱스",
  "About": "소개",
  "Research": "리서치",
  "Projects": "프로젝트",
  "Venture": "벤처",
  "Archive": "아카이브",
  "Writing": "글",
  "Contact": "연락",
  "Home": "홈",
  "Daejeon · KST": "대전 · 한국시간",
  "Menu ≡": "메뉴 ≡",
  "Close ×": "닫기 ×",

  "Aligning physics,\ncode, and culture.": "물리·코드·문화를\n한 축으로.",
  "Toward a civilization-scale solution.": "문명 단위로 답하기 위해.",
  "External": "외부",
  "© 2026 Taewoo Park": "© 2026 박태우",
  "Last edit · 2026.04 · Built in Daejeon": "최종 수정 · 2026.04 · 대전에서 만듦",
  "v0.1 — prototype": "v0.1 — 프로토타입",

  // preloader
  "Loading manifest…": "매니페스트 로딩…",
  "Aligning domain wall…": "도메인 월 정렬 중…",
  "Ready": "준비 완료",
  "TAEWOO PARK / PERSONAL SITE / v0.1": "박태우 / 개인 사이트 / v0.1",
  "◦◦◦ traversing ◦◦◦": "◦◦◦ 이동 중 ◦◦◦",

  // venture page (already in use)
  "Venture · CLASSIFIED": "벤처 · 기밀",
  "SYSTEM · STEALTH CONSOLE": "시스템 · 스텔스 콘솔",
  "TELEMETRY": "텔레메트리",
  "MISSION · DECRYPTED 01 / 09": "미션 · 복호화 01 / 09",
  "ROLE": "역할",
  "SINCE": "기간",
  "PRESS": "보도",
  "UPTIME": "가동",
  "STAGE": "단계",
  "SECTOR": "분야",
  "CORE": "코어",
  "TEAM": "팀",
  "SIGNAL": "시그널",
  "LOCATION": "위치",
  "LAST PING": "최근 신호",
  "INQUIRIES": "문의",
  "NEXT MILESTONE": "다음 마일스톤",
  "SYSLOG · LIVE": "시스로그 · 라이브",
  "pre-launch": "출시 전",
  "ENCRYPTED ⌁ 256-bit": "암호화 ⌁ 256-bit",
  "Daejeon, KR": "대전",
  "ACCEPTING": "접수 중",
  "Co-Founder · CEO": "공동창업자 · CEO",
  "· algorithm + UI + marketing": "· 알고리즘 + UI + 마케팅",
  "2025 · 08 — present": "2025 · 08 — 현재",
  "Developing a real-time optimization-driven B2C AI service.":
    "실시간 최적화 기반 B2C AI 서비스를 만드는 중.",
  "booting": "부팅 중",
  "INTERESTED?": "관심 있으신가요?",
  "If any of this resonates,": "이 중 하나라도 걸리는 게 있다면,",
  "we should talk.": "한번 얘기하자.",
  "Investors, candidates, or fellow builders — open a line. Replies sent under embargo until launch.":
    "투자자, 합류 고민 중인 분, 같이 짓는 사람들 — 편하게 연락. 출시 전까진 다 비공개로 답한다.",
  "Further detail withheld until launch. Strategic partners, candidates, and press: write — replies sent under embargo.":
    "출시 전까진 더 풀지 않는다. 전략 파트너, 합류 후보, 기자분들 — 메시지 주면 비공개로 답.",
  "Tip:": "팁:",
  "bars are clickable.": "막대 눌러봐도 된다.",
  "If you can help — write ✉": "도울 수 있다면 — 한 줄 ✉",
  "Contact →": "연락 →",

  // hero / index
  "Vol.": "Vol.",
  "· Iss.": "· Iss.",
  "Subj.": "주제",
  "Physics × Code × Culture": "물리 × 코드 × 문화",
  "Loc.": "위치",
  "KAIST · Daejeon": "KAIST · 대전",
  "Stamp": "발행",
  "Index 00 — Manifest": "인덱스 00 — 매니페스트",
  "Aligning physics,": "물리,",
  "code, and culture": "코드, 문화를",
  "toward a civilization-": "한 축으로 —",
  "scale solution.": "문명 단위까지.",
  "01 · Researcher": "01 · 연구자",
  "KAIST Ultrafast Spin Dynamics Lab. Ferrimagnetic domain-wall motion.":
    "KAIST 초고속 스핀 동역학 연구실. 페리자성 도메인 월 운동을 봅니다.",
  "02 · Founder": "02 · 창업자",
  "Stealth AI co-founder. Real-time optimization for B2C.":
    "스텔스 AI 공동창업. B2C에 실시간 최적화를 답니다.",
  "03 · Director": "03 · 디렉터",
  "HUSTLY ARCHIV — art × philosophy. 3K+ followers, 1M+ views.":
    "HUSTLY ARCHIV — 예술 × 철학. 팔로워 3K+, 누적 조회 100만.",
  "KAIST Ultrafast Spin Dynamics Lab": "KAIST 초고속 스핀 동역학 연구실",
  "Stealth AI · pre-launch": "스텔스 AI · 출시 전",
  "HUSTLY ARCHIV · 3K+": "HUSTLY ARCHIV · 3K+",
  "GDG on Campus KAIST": "GDG on Campus KAIST",
  "Physics ∩ Code ∩ Culture": "물리 ∩ 코드 ∩ 문화",
  "Mark Lombardi was right": "마크 롬바르디가 옳았다",
  "§ 0.1 — On unification": "§ 0.1 — 통합에 관하여",
  "Read 4 min ↓": "읽기 4분 ↓",
  "\"Three identities held in one body. Not a portfolio of careers, but a single thesis on how the world should be assembled.\"":
    "\"한 몸에 정체성 셋. 직업의 포트폴리오가 아니라, 세계가 어떻게 짜여야 하는지에 대한 한 줄의 주장.\"",
  "A ferrimagnetic domain wall is two opposing magnetizations balanced exactly so the wall moves fastest. The same logic — opposing strengths, intentionally cancelled, producing forward motion — is the geometry I'm trying to build for myself.":
    "페리자성 도메인 월은, 정반대의 두 자화가 정확히 상쇄될 때 가장 빠르게 움직입니다. 반대 힘을 일부러 맞부딪쳐 앞으로 나아가게 하는 — 이게 제가 저 자신한테 적용해 보려는 기하학입니다.",
  "Physics gives me the substrate. Code gives me the lever. Culture gives me the audience. Each on its own is incomplete; held together they become a project I'd describe — not lightly — as":
    "물리는 바닥. 코드는 지렛대. 문화는 듣는 사람. 따로 떼면 다 모자라고, 셋이 묶일 때만 — 가볍게 쓰는 말이 아닙니다 — 이렇게 부를 만한 일이 됩니다:",
  "civilization-scale": "문명 단위",
  "Continue · The Thesis →": "이어 읽기 · 주장 →",
  "Index — Seven entries": "인덱스 — 일곱 갈래",
  "↓ pick a thread": "↓ 한 갈래 골라 들어가세요",
  "Open ": "열기 ",
  "Read ": "읽기 ",
  "about": "소개",
  "research": "리서치",
  "projects": "프로젝트",
  "venture": "벤처",
  "archive": "아카이브",
  "writing": "글",
  "contact": "연락",
  // hero index card titles
  "The Thesis": "주장",
  "Why three identities held in one body. The geometry of the project.":
    "왜 한 몸에 셋을 묶었나. 이 프로젝트의 기하학.",
  "Long-form · 9 min": "장문 · 9분",
  "Spintronics": "스핀트로닉스",
  "Ferrimagnetic domain-wall motion at angular-momentum compensation.":
    "각운동량 보상점에서의 페리자성 도메인 월 운동.",
  "4 papers · 2 labs": "논문 4편 · 연구실 2곳",
  "NodePrompt + utilities": "NodePrompt + 유틸",
  "A canvas for thinking with LLMs in graphs. Plus tooling, simulations.":
    "LLM을 그래프 위에서 굴리는 캔버스. 그리고 잡다한 도구·시뮬레이션.",
  "7 entries": "7개 항목",
  "Stealth": "스텔스",
  "Real-time optimization for B2C. Co-founded. Pre-launch.":
    "B2C에 실시간 최적화. 공동창업. 출시 전.",
  "Month 03 · stealth": "3개월차 · 스텔스",
  "HUSTLY ARCHIV": "HUSTLY ARCHIV",
  "Aesthetics, philosophy, cinema, architecture — curated weekly.":
    "미학·철학·영화·건축 — 매주 한 편.",
  "@hustlyarchiv.kr · 3K+": "@hustlyarchiv.kr · 3K+",
  "Long-form notes": "장문 노트",
  "Drafts that survived a second reading. Physics ∩ AI ∩ culture.":
    "다시 읽어도 버틴 초고들. 물리 ∩ AI ∩ 문화.",
  "12 entries": "12개 항목",
  "Open node": "열린 노드",
  "Builders and thinkers across disciplines. 1–3 day reply.":
    "분야 가로지르는 빌더와 사상가들. 1–3일 안 답장.",
  "Scroll": "스크롤",

  // about page
  "The Thesis · Long-form": "주장 · 장문",
  "An interview with self ·": "스스로와의 인터뷰 ·",
  "April 2026 · Daejeon": "2026년 4월 · 대전",
  "I do not see physics, code, and culture as three jobs. I see them as three ways of asking the same question — what is the substrate of attention, and how do you bend it?":
    "물리, 코드, 문화는 직업 셋이 아니다. 같은 질문을 던지는 세 가지 방식이다 — 사람의 주의(attention)는 어디에 얹혀 있고, 어떻게 휘어지는가.",
  "Timeline / Sticky": "타임라인 / 고정",
  "§ 1 — Origins": "§ 1 — 시작",
  "n high school I expected to be a physicist and nothing else. The labs at Gyeonggi Science HS were the first place where reality started behaving like a system you could prod, and I was greedy about it.":
    "n 고등학교 때는 그냥 물리학자가 될 줄 알았다. 경기과학고 실험실은, 현실이 처음으로 \"한번 찔러볼 수 있는 시스템\"처럼 굴기 시작한 곳이었고, 거기에 욕심이 많았다.",
  "KAIST gave me the language. Double-majoring in Physics and Mathematical Science felt less like two degrees and more like one degree pulled along two axes — proofs on the math side, intuition-pumps on the physics side, both feeding the same engine.":
    "KAIST는 다룰 언어를 줬다. 물리·수리과학 복수전공은 학위 두 개가 아니라, 두 축으로 늘여 놓은 학위 하나에 가까웠다 — 수학 쪽은 증명, 물리 쪽은 직관 펌프. 둘 다 같은 엔진에 연료가 들어갔다.",
  "The first time I saw a ferrimagnetic domain wall propagate in a mumax3 simulation, I felt something I hadn't expected — recognition. The wall moves because two opposing things have been balanced. That's not a metaphor. That's the geometry.":
    "mumax3에서 페리자성 도메인 월이 앞으로 가는 걸 처음 봤을 때, 예상 못한 감정이 들었다 — 알아봄에 가까웠다. 그 벽이 움직이는 이유는, 정반대 두 힘이 정확히 균형을 이뤘기 때문이다. 비유가 아니라 기하학이다.",
  "Pull quote — § 1.4": "발췌 — § 1.4",
  "The wall moves because two opposing things have been balanced. That is not a metaphor. That is the geometry.":
    "벽이 움직이는 건 정반대 두 힘이 균형을 이뤘기 때문이다. 비유가 아니라, 기하학이다.",
  "§ 2 — Why three at once": "§ 2 — 왜 셋을 동시에",
  "Friends ask me why I don't pick one. The honest answer: the three pieces aren't redundant. Research gives me a 30-year horizon. A startup gives me a 2-year horizon. HUSTLY gives me weekly contact with how people actually feel.":
    "친구들은 왜 하나만 안 고르냐고 묻는다. 솔직히 말하면, 이 셋은 겹치지 않는다. 연구는 30년짜리 시야를, 스타트업은 2년짜리 시야를, HUSTLY는 사람들이 실제로 뭘 느끼는지를 매주 확인할 통로를 준다.",
  "Without the long horizon, I'd build forgettable tools. Without the short horizon, I'd never ship. Without the cultural feedback, I'd build for nobody real.":
    "긴 시야가 빠지면 금세 잊힐 도구를 만든다. 짧은 시야가 빠지면 절대 못 낸다. 문화 쪽 피드백이 빠지면, 실제론 없는 사람들 앞에서 만들고 있게 된다.",
  "The arrangement isn't elegant; it's load-bearing. Each pillar exists because the other two were unable to hold up that part of the building alone.":
    "우아한 배치가 아니라, 하중을 버티는 구조다. 세 기둥 중 어느 하나도, 나머지 둘만으로는 자기 자리를 못 받친다.",
  "§ 3 — Spin, in particular": "§ 3 — 그중에서도 스핀",
  "At Prof. Kab-jin Kim's Ultrafast Spin Dynamics Lab I work on ferrimagnetic domain-wall motion at angular-momentum compensation. In plain language: how to move information through a magnet faster than the magnet itself wants to be moved.":
    "김갑진 교수님 초고속 스핀 동역학 연구실에서, 각운동량 보상점 부근의 페리자성 도메인 월 운동을 본다. 쉽게 말하면, 자석이 원래 움직일 수 있는 한계보다 더 빠르게 자석 안 정보를 옮기는 방법이다.",
  "Earlier, in Prof. Se-Kwon Kim's Quantum Spin Dynamics Lab, I worked on magnonic analogues — quasiparticles in spin lattices. Both labs converge on the same thesis:":
    "그 전엔 김세권 교수님 양자 스핀 동역학 연구실에서, 스핀 격자 속 준입자인 마그논 대응체를 다뤘다. 결국 두 연구실 다 같은 명제로 모인다:",
  "computation does not have to live in electrons": "계산이 꼭 전자 위에서 일어날 필요는 없다",
  "I'm interested in the boring version of this becoming real. Not science fiction. The kind of substrate that ends up in the room you're sitting in, ten years from now, and you don't notice.":
    "이 명제의 \"심심한 버전\"이 현실이 되는 쪽이 궁금하다. SF가 아니라, 10년 뒤 당신 방 어딘가에 들어와 있는데 눈치도 못 챌, 그런 기반.",
  "§ 4 — On HUSTLY": "§ 4 — HUSTLY 얘기",
  "HUSTLY ARCHIV started as a notebook. I was reading too much philosophy and watching too much cinema and I wanted somewhere to put it that wasn't only mine. It became a small public — three thousand and growing — that I now feel responsible to.":
    "HUSTLY ARCHIV는 노트 한 권으로 시작했다. 그 시기엔 철학을 너무 많이 읽고 영화를 너무 많이 봤고, 그걸 나만의 공간이 아닌 어딘가에 두고 싶었다. 그게 작은 대중이 됐고 — 3천 명, 계속 느는 중 — 이젠 그들한테 책임감을 느낀다.",
  "The archive is, secretly, the user research arm of everything else. You learn very fast which ideas land and which were never as good as you thought.":
    "이 아카이브는 사실 다른 모든 일의 사용자 리서치 창구다. 어떤 아이디어가 진짜 가닿는지, 어떤 건 처음부터 생각만큼 좋지 않았는지를 아주 빨리 배운다.",
  "§ 5 — The thesis, in one line": "§ 5 — 한 줄로",
  "I am building toward a": "결국 내가 짓는 건 —",
  "civilization-scale solution": "문명 단위의 답",
  "I do not yet know its final shape. I know it will require physics that does not exist yet, software that has not been written yet, and a public that does not currently see itself as one. So I'm working on all three.":
    "최종 모습은 아직 모른다. 다만, 아직 없는 물리학과, 아직 쓰이지 않은 소프트웨어와, 스스로를 하나로 인식하지 않는 대중이 다 필요하다는 건 안다. 그래서 셋을 동시에 한다.",
  "End · § 5": "끝 · § 5",
  "2,481 words · 9 min read": "2,481단어 · 9분",
  "Continue → Research": "이어 읽기 → 리서치",
  // about timeline labels
  "Gyeongnam Science HS": "경남과학고",
  "Early graduation track.": "조기 졸업 트랙.",
  "KAIST · Physics + Math Sci": "KAIST · 물리 + 수리과학",
  "Double major. National S&T scholarship.": "복수전공. 국가과학기술 장학금.",
  "Quantum Spin Dynamics Lab": "양자 스핀 동역학 연구실",
  "Prof. Se-Kwon Kim. mumax3, domain-wall theory.": "김세권 교수. mumax3, 도메인 월 이론.",
  "Republic of Korea Army": "대한민국 육군",
  "Sergeant. Honorably discharged.": "병장. 만기 전역.",
  "HUSTLY ARCHIV launched": "HUSTLY ARCHIV 시작",
  "Jan. Art × philosophy curation.": "1월. 예술 × 철학 큐레이션.",
  "Stealth AI · co-founded": "스텔스 AI · 공동창업",
  "Aug. Real-time optimization B2C.": "8월. 실시간 최적화 B2C.",
  "Ultrafast Spin Dynamics Lab": "초고속 스핀 동역학 연구실",
  "Oct. Prof. Kab-jin Kim. FiM DW motion.": "10월. 김갑진 교수. 페리자성 DW 운동.",
  "GDG on Campus, KAIST": "GDG on Campus, KAIST",
  "Mar. Member.": "3월. 회원.",

  // research page
  "Research · Spintronics": "리서치 · 스핀트로닉스",
  "arXiv-style abstract": "arXiv 스타일 초록",
  "2 labs · 2023–present": "연구실 2곳 · 2023–현재",
  "Spin textures that move information faster than the medium that carries them. Ferrimagnetic domain walls at angular-momentum compensation; magnonic transport in topologically non-trivial lattices.":
    "매질이 허락하는 속도보다 더 빨리 정보를 옮기는 스핀 텍스처. 각운동량 보상점의 페리자성 도메인 월, 위상적으로 비자명한 격자 위 마그논 수송.",
  "Abstract": "초록",
  "PI": "지도교수",
  "Prior PI": "이전 지도교수",
  "Ultrafast Spin Dynamics Lab": "초고속 스핀 동역학 연구실",
  "Quantum Spin Dynamics Lab": "양자 스핀 동역학 연구실",
  "Scrub by scrolling ↓": "스크롤로 프레임 넘기기 ↓",
  "A wall moves through the strip. At compensation, the wall behaves more like a relativistic soliton than a precessing magnetization.":
    "벽 하나가 스트립을 가로지른다. 보상점에선 세차하는 자화보단 상대론적 솔리톤에 가깝다.",
  "Source data: 60-frame mumax3 sequence, 1024 × 1024, monochrome.":
    "원본 데이터: mumax3 60프레임 시퀀스, 1024 × 1024, 흑백.",
  "[placeholder — replace with real PNG sequence]":
    "[자리표시자 — 실제 PNG 시퀀스로 교체 예정]",
  "Selected work / publications": "주요 작업 · 논문",
  "Ferrimagnetic domain-wall motion at angular-momentum compensation":
    "각운동량 보상점에서의 페리자성 도메인 월 운동",
  "Ultrafast Spin Dynamics Lab · Prof. Kab-jin Kim": "초고속 스핀 동역학 연구실 · 김갑진 교수님",
  "in progress": "진행 중",
  "experimental + mumax3": "실험 + mumax3",
  "Theoretical modeling and simulation of domain-wall motion in spintronics":
    "스핀트로닉스에서의 도메인 월 운동: 이론 모델링과 시뮬레이션",
  "Quantum Spin Dynamics Lab · Prof. Se-Kwon Kim": "양자 스핀 동역학 연구실 · 김세권 교수님",
  "undergraduate research": "학부 연구",
  "mumax3, Python": "mumax3, Python",
  "Tooling": "도구",
  "mumax3 · Python · Mathematica · CUDA": "mumax3 · Python · Mathematica · CUDA",
  "Most simulations run on a small in-house GPU cluster. ML-based fitting of velocity curves is in progress.":
    "시뮬레이션은 거의 자체 GPU 클러스터에서 돌린다. 속도 곡선 ML 피팅도 진행 중.",
  "Other affiliations": "그 밖의 소속",
  "· Department Representative, KAIST Physics (2023–24)": "· KAIST 물리학과 학과대표 (2023–24)",
  "· Student Ambassador (KAINURI), KAIST Physics (2022–24)":
    "· KAIST 물리학과 학생 홍보대사 (KAINURI, 2022–24)",
  "· National S&T Scholarship · Korea Student Aid Foundation, 2022":
    "· 한국장학재단 국가과학기술 장학생, 2022",

  // projects page
  "Projects · Built things": "프로젝트 · 만든 것들",
  "Tools that take a hard idea and make it small enough to hold in one hand.":
    "어려운 아이디어를 한 손에 쥘 만큼 작게 깎아낸 도구들.",
  "A short list. The hero is NodePrompt — a Mark-Lombardi-coded canvas for thinking with LLMs in graphs instead of chats. Below, smaller utilities, simulations, and one-night experiments.":
    "짧은 목록. 대표작은 NodePrompt — 채팅이 아니라 그래프로 LLM을 굴리는, 마크 롬바르디 풍 캔버스. 그 아래는 작은 유틸, 시뮬, 하룻밤짜리 실험들.",
  "01 / Hero": "01 / 대표작",
  "2025 — present": "2025 — 현재",
  "A node-graph environment for LLM workflows. Mark Lombardi's diagrams, but the edges are prompts.":
    "LLM 워크플로를 위한 노드 그래프. 마크 롬바르디의 다이어그램인데, 간선이 곧 프롬프트.",
  "↳ React Three Fiber": "↳ React Three Fiber",
  "↳ TypeScript": "↳ TypeScript",
  "↳ Live demo": "↳ 라이브 데모",
  "02 · Tool": "02 · 도구",
  "PyQt5 viewer for mumax3 simulation outputs. Frame scrub, vector overlay, side-by-side compare.":
    "mumax3 결과 보려고 만든 PyQt5 뷰어. 프레임 스크럽, 벡터 오버레이, 나란히 비교.",
  "Python · PyQt5": "Python · PyQt5",
  "03 · Open source": "03 · 오픈소스",
  "Velocity-curve fitter": "속도 곡선 피팅 도구",
  "GPU FFT benchmark for spin lattices.": "스핀 격자를 위한 GPU FFT 벤치마크.",
  "Visual explorer for Berry curvature in 2D bands.": "2D 띠의 Berry 곡률을 눈으로 보는 탐색기.",
  "Internal scheduler for the lab. PWA.": "연구실 내부용 스케줄러. PWA.",
  "Real-time Ising sim, Monte-Carlo, browser.": "브라우저 안에서 도는 실시간 Ising 시뮬, 몬테카를로.",
  "CUDA · C++": "CUDA · C++",
  "WebGL": "WebGL",
  "Next.js": "Next.js",
  "WebGPU": "WebGPU",
  "Util": "유틸",
  "Case study · NodePrompt": "케이스 스터디 · NodePrompt",
  "A canvas for thinking, not a chat for asking.": "묻기 위한 채팅이 아니라, 사고하기 위한 캔버스.",
  "Problem": "문제",
  "Linear chat hides the shape of an investigation.": "한 줄짜리 채팅은 탐구의 모양을 가린다.",
  "Approach": "접근",
  "Treat prompts as nodes; let edges encode dependency.":
    "프롬프트는 노드로 두고, 간선에 의존 관계를 싣는다.",
  "Result": "결과",
  "Used internally for research planning and writing decomposition.":
    "연구 계획 짜기, 글 쪼개기에 내부적으로 쓰는 중.",
  "Stack": "스택",
  "Next 15, R3F, Postgres, Lenis. Vercel deploy.":
    "Next 15, R3F, Postgres, Lenis. 배포는 Vercel.",

  // archive page
  "Archive · HUSTLY ARCHIV": "아카이브 · HUSTLY ARCHIV",
  "Editor's note": "에디터의 말",
  "A continuous curation of Korean hip-hop — album readings, conversations, and editorial notes. Each entry below is a real post; click through for the full essay on Instagram.":
    "한국 힙합을 꾸준히 큐레이션한다 — 앨범 읽기, 대화, 에디토리얼 노트. 아래 항목은 다 실제 게시물. 누르면 인스타그램의 전체 글로 넘어간다.",
  "@hustlyarchiv.kr · since Jan 2025": "@hustlyarchiv.kr · 2025년 1월부터",
  "Entries": "항목",
  "Reviews": "리뷰",
  "Talks": "토크",
  "Editorial": "에디토리얼",
  "Since": "시작",
  "All": "전체",
  "The archive lives natively on Instagram. The grid above is a static mirror.":
    "아카이브의 본진은 인스타그램. 위 그리드는 그걸 옮겨 놓은 정적 사본일 뿐.",
  "@hustlyarchiv.kr on Instagram →": "인스타그램에서 @hustlyarchiv.kr →",
  "grid": "그리드",
  "index": "인덱스",

  // mindmap scene
  "FIG. 01 — KNOWLEDGE GRAPH": "FIG. 01 — 지식 그래프",
  "Three Strata of Inquiry": "탐구의 세 층",
  "PHILOSOPHY · ART · SCIENCE  ·  N=24  ·  E=42": "철학 · 예술 · 과학  ·  N=24  ·  E=42",
  "OVERVIEW": "오버뷰",
  "ACTIVE STRATUM": "활성 층",
  "three strata": "세 층",
  "scroll to traverse": "스크롤로 탐색",
  "PHILOSOPHY": "철학",
  "ART": "예술",
  "SCIENCE": "과학",
  "foundations": "기반",
  "expression": "표현",
  "inquiry": "탐구",

  // writing page
  "Writing · Long-form notes": "글 · 장문 노트",
  "Drafts that survived a second reading.": "다시 읽어도 버틴 초고들.",
  "Most of these started in Obsidian. They survive here because they kept holding up. Physics, AI, culture, philosophy — usually all four in the same sentence.":
    "대부분 옵시디언에서 시작된 글. 여기까지 온 이유는 단순하다 — 시간이 지나도 안 무너졌다. 물리, AI, 문화, 철학. 한 문장 안에 이 넷이 다 들어가 있는 경우가 대부분.",
  "Physics": "물리",
  "Philosophy": "철학",
  "Design": "디자인",
  "Founding": "창업",
  "Culture": "문화",
  "Personal": "개인",
  "Sample · § 1 of post 011": "발췌 · 게시물 011의 § 1",
  "\"A ferrimagnet is two opposing magnetizations bound together. At one specific temperature, the angular momentum cancels but the magnetization does not. The wall, untethered, moves at the speed the lattice will allow.\"":
    "\"페리자성체는 정반대 두 자화가 묶여 있는 상태. 어떤 온도에선 각운동량은 상쇄돼 사라지는데, 자화는 그대로 남는다. 묶임에서 풀려난 벽은 격자가 허락하는 한계 속도로 달린다.\"",
  "1 · Sidenote": "1 · 각주",
  "Specifically, T<sub>A</sub>: the angular momentum compensation temperature, distinct from the magnetization compensation point.":
    "정확히는 T<sub>A</sub> — 각운동량 보상 온도. 자화 보상점과는 다른 개념이다.",
  // writing post titles
  "On the politics of attention": "주의(attention)의 정치학에 관하여",
  "Why ferrimagnets are the right substrate, finally": "왜 결국 페리자성체가 옳은 기반인가",
  "Notes from a stealth quarter": "스텔스 분기에서 적은 메모",
  "Mark Lombardi as software": "마크 롬바르디, 소프트웨어로서",
  "Reading: Bachelard, then immediately rereading him": "독서 — 바슐라르, 그리고 곧바로 다시 읽기",
  "What I expect from 2026, with error bars": "오차 범위까지 표시한 2026년 전망",
  "Magnonics, in plain language": "마그노닉스, 쉬운 말로",
  "Curating is doing user research": "큐레이션은 사용자 리서치다",
  "On the form of personal sites": "개인 사이트의 형식에 대하여",

  // contact page
  "Contact · Open node": "연락 · 열린 노드",
  "Available for · Collaboration · Research · Ideas · Co-conspiracy ·":
    "환영 · 협업 · 연구 · 아이디어 · 공모 ·",
  "Open to connecting with builders and thinkers across disciplines.":
    "분야 가로지르는 빌더, 사상가들과 닿고 싶다.",
  "I read everything that comes in. The shorter and more specific the message, the faster I write back. If you're building something that touches physics, software, or culture — or all three — I want to hear from you.":
    "들어오는 메시지는 다 읽는다. 짧고 구체적일수록 답이 빨라진다. 물리, 소프트웨어, 문화 — 혹은 셋 다 — 에 닿는 무언가를 짓고 있다면, 꼭 듣고 싶다.",
  "Response time · usually 1–3 days · Korean or English":
    "회신 · 보통 1–3일 · 한국어 또는 영어",
  "Fig. 07 — You, on the graph": "Fig. 07 — 그래프 위의 당신",
  "move cursor — connections form within radius r < 220px":
    "커서를 움직여 보면 — 반경 r < 220px 안에서 연결이 잡힌다",
  "Card · Channel graph": "카드 · 채널 그래프",
  "hover a node — click to copy or open": "노드 위에 올려봐 — 누르면 복사 또는 열림",
  "Email": "이메일",
  "GitHub": "GitHub",
  "X / Twitter": "X / 트위터",
  "Instagram": "인스타그램",
  "LinkedIn": "링크드인",
  "Affiliation": "소속",
  "KAIST · Daejeon, KR": "KAIST · 대전",
  "Copy": "복사",
  "Open": "열기",
  "✓ copied to clipboard": "✓ 클립보드에 복사됨",
  "→ click to copy": "→ 눌러서 복사",
  "→ open": "→ 열기",
  "Mailing": "우편",
  "KAIST E6 (자연과학동)": "KAIST E6 (자연과학동)",
  "291 Daehak-ro, Yuseong-gu": "대전광역시 유성구 대학로 291",
  "Daejeon 34141, KR": "34141",
  "Hours": "시간",
  "Mon — Sat": "월 — 토",
  "10:00 — 22:00 KST": "10:00 — 22:00 KST",
  "Sundays read-only": "일요일은 읽기만",
  "Now": "요즘",
  "Reading:": "읽는 중:",
  "Speed and Politics": "《속도와 정치》",
  ", Virilio": ", 폴 비릴리오",
  "Watching: Bresson re-runs": "보는 중: 브레송 재상영",
  "Building: see /venture": "만드는 중: /venture 참조",

  // project-detail page
  "Case study · NodePrompt": "케이스 스터디 · NodePrompt",
  "← Index 03 / Projects": "← 인덱스 03 / 프로젝트",
  "2025 — present · Co-built": "2025 — 현재 · 공동 개발",
  "A canvas environment for prompting LLMs in graphs, not chats. Each node is a step; each edge a dependency. Inspired by Mark Lombardi's diagrams of power.":
    "채팅이 아니라 그래프 위에서 LLM에 프롬프트를 던지는 캔버스. 노드 하나가 한 단계, 간선이 의존 관계. 마크 롬바르디가 그린 권력의 지도에서 가져왔다.",
  "Fig. 01 · Live mini scene": "Fig. 01 · 라이브 미니 씬",
  "Fig. 03 · Live mini scene": "Fig. 03 · 라이브 미니 씬",
  "R3F · drei · postprocessing": "R3F · drei · postprocessing",

  // NodePrompt — interactive demo
  "Fig. 02 · Interactive demo": "Fig. 02 · 인터랙티브 데모",
  "Pick a sentence. Watch it split into six registers.":
    "문장 하나를 고른다. 여섯 갈래로 나뉘어 풀리는 걸 본다.",
  "ens · res · unum · aliquid · verum · bonum": "ens · res · unum · aliquid · verum · bonum",
  "Decompose": "분해하기",
  "Research proposal": "연구 제안서",
  "Quantum, for a 14-year-old": "양자, 14세에게",
  "Vinyl-label logo": "바이닐 레이블 로고",
  "Write a research proposal on spintronics for a graduate fellowship.":
    "스핀트로닉스 주제로 대학원 펠로우십용 연구 제안서를 써줘.",
  "Explain quantum entanglement to a high-school student.":
    "고등학생한테 양자 얽힘을 설명해 줘.",
  "Design a logo for an independent vinyl record label.":
    "독립 바이닐 레이블 로고를 디자인해 줘.",
  "spintronics": "스핀트로닉스",
  "research framework": "연구 프레임",
  "fellowship reader": "펠로우십 심사자",
  "novelty vs prior art": "선행 연구와의 차이",
  "scientific rigor": "과학적 엄밀성",
  "career stake": "커리어가 걸린 자리",
  "entanglement": "얽힘",
  "quantum mechanics": "양자역학",
  "high-school context": "고등학생이라는 맥락",
  "vs classical correlation": "고전적 상관관계와의 대비",
  "what is physically true": "물리적으로 참인 것",
  "wonder, motivation": "경이, 동기",
  "logo": "로고",
  "brand system": "브랜드 시스템",
  "vinyl culture": "바이닐 문화",
  "vs major-label marks": "메이저 레이블 마크와의 대비",
  "design principles": "디자인 원칙",
  "tone, attitude": "톤, 태도",
  "Hover a node to isolate its edges. Each register is a different lens on the same sentence — not six kinds of being, six aspects of one.":
    "노드 위에 커서를 올리면 그 노드의 간선만 남는다. 여섯 갈래는 같은 문장을 보는 여섯 개의 렌즈 — 존재의 여섯 종류가 아니라, 한 존재의 여섯 측면.",
  "After Aquinas, De Veritate q.1 a.1": "토마스 아퀴나스 《진리론》 q.1 a.1에서.",

  // NodePrompt — redesigned demo (input + 17 nodes + inspector)
  "Type a prompt. Watch it map.": "프롬프트를 입력하고, 매핑되는 걸 본다.",
  "Preset": "프리셋",
  "Prompt": "프롬프트",
  "Pick": "고르기",
  "Submit": "제출",
  "Reset": "초기화",
  "Decompose ↓": "분해 ↓",
  "Re-map ↻": "다시 매핑 ↻",
  "Three pre-mapped sentences. Pick one, edit if you like, then decompose.":
    "미리 매핑해 둔 문장 셋. 하나 골라서, 원하면 고쳐 쓰고, 분해를 누른다.",
  "Ready. Press Decompose to map this prompt to its concept graph.":
    "준비 완료. 분해를 누르면 이 프롬프트가 개념 그래프로 매핑된다.",
  "Mapping… {n} / {total} nodes": "매핑 중… {n} / {total} 노드",
  "Mapped. Hover any node for the inspector. Click to pin.":
    "매핑 완료. 노드 위에 커서를 올리면 인스펙터가 뜬다. 클릭하면 고정.",
  "17 nodes · 1 root, 6 register parents, 10 leaves. Each node carries a type, a weight, and a one-line gloss in the inspector.":
    "17개 노드 · 루트 1, 레지스터 부모 6, 잎 10. 각 노드는 타입·가중치·한 줄 해설을 가진다.",

  // type glosses (inspector)
  "Being": "존재",
  "Essence": "본질",
  "Unity": "통일성",
  "Difference": "차이",
  "Truth": "참",
  "Value": "선",
  "What does this prompt posit as existing?": "이 프롬프트가 존재하는 것으로 두는 게 무엇인가?",
  "What is it, as a formal structure?": "그것은 형식적 구조로서 무엇인가?",
  "What holds it together as one?": "그것을 하나로 묶어 주는 건 무엇인가?",
  "What distinguishes it from what it is not?": "그것을 그것이 아닌 것과 구분 짓는 건 무엇인가?",
  "How is it true to a knower?": "그것은 인식자에게 어떻게 참인가?",
  "How is it desirable to a will?": "그것은 의지에게 어떻게 욕망의 대상이 되는가?",

  // updated long-form sentences
  "Write a research proposal on spintronics for a graduate fellowship, balancing rigor against the reader's limited attention.":
    "스핀트로닉스 주제로 대학원 펠로우십용 연구 제안서를 써. 엄밀성과 심사자의 한정된 주의력 사이에서 균형을 맞추면서.",
  "Explain quantum entanglement to a high-school student in a way that builds wonder before it builds formalism.":
    "고등학생한테 양자 얽힘을 설명해. 형식 이전에 경이부터 쌓는 방식으로.",
  "Design a logo for an independent vinyl record label, refusing the shorthand of major-label marks while staying readable at 7-inch scale.":
    "독립 바이닐 레이블 로고를 디자인해. 메이저 레이블 마크의 손쉬운 어법을 거부하되, 7인치 스케일에서도 읽힐 만큼은 분명하게.",

  // node labels — research
  "spintronics proposal": "스핀트로닉스 제안서",
  "methods section": "방법론 섹션",
  "aims": "목표",
  "budget arc": "예산 곡선",
  "evaluation rubric": "평가 기준",
  "register": "톤·격",
  "stake": "걸린 것",
  "reproducibility": "재현 가능성",
  "falsifiability": "반증 가능성",
  "prior literature": "선행 문헌",
  "open gap": "남아 있는 틈",
  "measurement chain": "측정 사슬",

  // node labels — quantum
  "entanglement, taught": "얽힘, 가르치는 행위",
  "wonder before rigor": "엄밀 이전의 경이",
  "explanatory ladder": "설명의 사다리",
  "Bell pair": "벨 쌍",
  "state vector": "상태 벡터",
  "prior knowledge": "사전 지식",
  "metaphor budget": "비유 예산",
  "aha-moment": "아하-모멘트",
  "no-signaling": "신호 불가능성",
  "measurement role": "측정의 역할",
  "shared-coin trap": "공유 동전의 함정",
  "Bell inequality": "벨 부등식",
  "step ordering": "설명 순서",

  // node labels — vinyl
  "label mark": "레이블 마크",
  "press constraints": "인쇄 제약",
  "type system": "타입 시스템",
  "grid": "그리드",
  "record-shop bin": "레코드숍 진열대",
  "DJ subculture": "DJ 서브컬처",
  "indie posture": "인디 자세",
  "legibility": "가독성",
  "honest construction": "정직한 구성",
  "corporate gloss": "기업적 광택",
  "trend cycles": "트렌드 사이클",
  "ink coverage": "잉크 커버리지",

  // node descriptions (research)
  "The whole. What the prompt posits as a thing — the proposal itself, taken as one object.":
    "전체. 프롬프트가 하나의 사물로 두는 것 — 제안서 그 자체, 하나의 물건으로.",
  "The formal structure: thesis, claims, the architecture that holds the argument.":
    "형식적 구조 — 명제, 주장, 그 주장을 떠받치는 구조.",
  "The frame that holds it together — who reads this, under what evaluation rubric.":
    "전체를 묶는 틀 — 누가 읽고, 어떤 기준으로 평가하는가.",
  "The desirability axis: tone, urgency, what is at risk for the writer.":
    "욕망의 축 — 톤, 긴박함, 글 쓴 사람이 걸고 있는 것.",
  "What it owes the intellect — methodological commitments, reproducibility, falsifiability.":
    "지성에게 갚아야 할 것 — 방법론적 약속, 재현 가능성, 반증 가능성.",
  "Difference. What it is not — distinguishing this from the existing literature.":
    "차이. 그것이 아닌 것 — 기존 문헌과 이 작업을 구분 짓는 자리.",
  "Secondary structural register — the experimental procedure read as form, not content.":
    "두 번째 구조 레지스터 — 실험 절차를 내용이 아니라 형식으로 읽는 자리.",
  "A child of the framework — the deliverable claims the proposal commits to.":
    "프레임의 자식 — 제안서가 약속하는 결과물의 주장들.",
  "How resources map onto the structural skeleton over the funding period.":
    "지원 기간 동안 자원이 구조의 골격 위에 어떻게 얹히는가.",
  "The frame the reader applies — explicit and implicit scoring axes.":
    "심사자가 적용하는 틀 — 명시적·암묵적 채점 축.",
  "The institutional voice the document must speak in to be heard at all.":
    "들리려면 어쨌든 써야 하는 제도의 목소리.",
  "What the writer loses if the proposal fails — the affective center of gravity.":
    "제안서가 실패할 때 글쓴이가 잃는 것 — 정서적 무게중심.",
  "A specific commitment to a knower — code, data, methods exposed for audit.":
    "인식자에 대한 구체적 약속 — 감사 가능한 코드·데이터·방법.",
  "What would count as the proposal being wrong — required for it to count as knowledge.":
    "제안서가 틀렸다고 칠 수 있는 조건 — 그래야 지식으로 친다.",
  "The not-it. What this proposal explicitly defines itself against.":
    "그것 아닌 것. 이 제안서가 명시적으로 자기를 가르는 자리.",
  "The seam between what is known and what this work intends to add.":
    "이미 아는 것과 이 작업이 더하려는 것 사이의 솔기.",
  "The instrument-to-claim path. Where rigor lives or fails, structurally.":
    "장비에서 주장으로 가는 경로. 엄밀성이 살거나 죽는 구조적 자리.",

  // node descriptions (quantum)
  "The whole. The act of explanation as one object — not the topic alone, but the teaching of it.":
    "전체. 설명이라는 행위 자체를 하나의 사물로 — 주제가 아니라, 그것을 가르치는 일.",
  "The formal structure underneath the lesson. The thing whose surface is being shown.":
    "수업 아래 깔린 형식적 구조. 표면이 보여지는 그 무엇.",
  "Pedagogical frame. What this listener already has — what the lesson can stand on.":
    "교육적 틀. 듣는 사람이 이미 가진 것 — 수업이 발 디딜 자리.",
  "The affective stance the lesson is asked to take. The desirable shape of the encounter.":
    "수업이 취해야 하는 정서적 입장. 만남의 바람직한 모양.",
  "The non-negotiable: claims that have to remain accurate even when simplified.":
    "양보 불가 — 단순화해도 정확해야 하는 주장들.",
  "Difference. The crucial not-it — entanglement is most clearly seen against what it is not.":
    "차이. 결정적인 \"그것 아닌 것\" — 얽힘은 그것이 아닌 것에 비춰야 가장 분명히 보인다.",
  "Structural form of the explanation itself: where to start, where to stop, in what order.":
    "설명 자체의 구조 — 어디서 시작해서, 어디서 멈추고, 어떤 순서로.",
  "The minimal example. The simplest formal object that exhibits the phenomenon.":
    "최소 예시. 현상을 보여 주는 가장 단순한 형식적 대상.",
  "The mathematical handle. Optional for a 14-year-old; load-bearing if invoked.":
    "수학적 손잡이. 14세에겐 선택. 일단 꺼내면 부담을 진다.",
  "What the listener already trusts — coins, dice, polarizers — anchors for new claims.":
    "듣는 사람이 이미 신뢰하는 것 — 동전, 주사위, 편광기 — 새 주장의 닻.",
  "How many analogies the lesson can spend before they start lying for it.":
    "수업이 비유 몇 개까지 써도 거짓말을 시작하지 않는가.",
  "The affective payoff the lesson is engineered around. The desirable peak.":
    "수업이 설계되는 중심의 정서적 보상. 욕망되는 정점.",
  "A constraint that must survive any simplification — entanglement carries no usable signal.":
    "어떤 단순화에서도 살아남아야 하는 제약 — 얽힘은 유용한 신호를 옮기지 않는다.",
  "What measurement means in this regime — different from the classical reading.":
    "이 영역에서 \"측정\"의 뜻 — 고전적 독해와 다른 자리.",
  "The classical model that almost works. Best contrast for showing where it breaks.":
    "거의 작동하는 고전 모형. 어디서 깨지는지 보여 주기에 가장 좋은 대비.",
  "The line that classical correlations cannot cross — the place difference becomes visible.":
    "고전 상관관계가 넘을 수 없는 선 — 차이가 보이는 자리.",
  "The sequence of explanation. Wonder first, mechanism second, formalism only on demand.":
    "설명의 순서. 경이 먼저, 메커니즘 다음, 형식은 청해야만.",

  // node descriptions (vinyl)
  "The whole. The mark itself, taken as a single object — not the brand system, the mark.":
    "전체. 마크 그 자체를 하나의 사물로 — 브랜드 시스템이 아니라, 마크.",
  "The formal scaffolding the mark belongs to: typography, palette, lockups.":
    "마크가 얹히는 형식적 골격 — 타이포그래피, 팔레트, 락업.",
  "What holds the mark in place culturally — the listener it speaks to.":
    "마크를 문화적으로 잡아 주는 것 — 그것이 말을 거는 청자.",
  "The affective register — independent, considered, slightly hostile to the obvious.":
    "정서 레지스터 — 독립적이고, 정제됐고, 뻔한 것에 약간 적대적인.",
  "Commitments to a knower — what would make a designer call this work honest.":
    "인식자에 대한 약속 — 디자이너가 이 작업을 \"정직하다\"고 부를 조건.",
  "Difference. The not-it — the corporate marks the brief explicitly refuses.":
    "차이. \"그것 아닌 것\" — 브리프가 명시적으로 거부하는 기업 마크들.",
  "The other formal register: physical print, ink coverage, 7-inch legibility.":
    "또 하나의 형식 레지스터 — 실제 인쇄, 잉크 커버리지, 7인치 가독성.",
  "Custom or licensed; condensed or wide. Carries more of the label's voice than the mark itself.":
    "커스텀이냐 라이선스냐, 컨덴스트냐 와이드냐. 마크 자체보다 레이블의 목소리를 더 많이 운반한다.",
  "The proportions the rest of the system has to obey.":
    "나머지 시스템이 따라야 하는 비율.",
  "Where the mark will actually first be seen — sideways, half-occluded, fluorescent light.":
    "마크가 실제로 처음 보이는 자리 — 옆으로 누이고, 절반쯤 가려지고, 형광등 아래에서.",
  "The community whose vocabulary the mark should already speak.":
    "마크가 이미 그 어휘로 말해야 하는 공동체.",
  "Refusal as aesthetic — a stance, not just a style.":
    "거부를 미학으로 — 스타일이 아니라 자세.",
  "Survives reduction. A non-negotiable for a print mark.":
    "축소를 견딘다. 인쇄 마크에서는 양보 불가.",
  "No false depth, no AI-render texture standing in for craft.":
    "가짜 입체 없음. 장인성 대신 들어가는 AI-렌더 텍스처도 없음.",
  "The aesthetic to refuse — gradients, fake bevels, inevitability.":
    "거부할 미학 — 그라디언트, 가짜 베벨, 필연인 척.",
  "Differentiation across time, not just across the shelf.":
    "선반 위에서뿐만 아니라 시간 위에서도 구별되기.",
  "The press's tolerance for thin lines and large solids — a real constraint, not a stylistic one.":
    "인쇄기의 가는 선과 큰 솔리드에 대한 한도 — 스타일 문제가 아니라 진짜 제약.",

  // updated figure captions
  "Fig. 01 · A reduced graph that runs in the browser tab — same visual grammar as the full canvas, two orders of magnitude smaller.":
    "Fig. 01 · 브라우저 탭에서 도는 축소판 그래프 — 풀 캔버스와 같은 시각 문법을, 두 자릿수 작게.",
  "Fig. 03 · Sphere mode — 50+ extracted nodes laid out on a Fibonacci lattice, edges arced in Lombardi sweeps. Orbit, zoom, click to drop into a node.":
    "Fig. 03 · 스피어 모드 — 추출된 50+ 노드가 피보나치 격자 위에 놓이고, 간선은 롬바르디식 호로 휜다. 궤도 회전·줌·클릭으로 노드에 진입.",
  "Fig. 04 · 0:42 walkthrough — prompt enters, sphere assembles, mode switches to radial for editing, then inside the sphere via interior fisheye. Sound off.":
    "Fig. 04 · 0:42 둘러보기 — 프롬프트 입력, 스피어 조립, 편집을 위해 래디얼로 전환, 마지막으로 인테리어 어안으로 구 안쪽까지. 소리는 꺼져 있다.",
  "Fig. 05 · Radial mode — 2D editing surface. Concentric rings encode hierarchy depth; drag to reposition, scroll to reweight, shift-click to wire a new edge.":
    "Fig. 05 · 래디얼 모드 — 2D 편집 표면. 동심 링이 계층 깊이를 인코딩한다. 끌어 옮기고, 스크롤로 가중치를 바꾸고, 시프트-클릭으로 새 간선을 잇는다.",
  "Fig. 06 · Inspector — type, weight, parent, and relations exposed for a single node. The same surface the demo above borrows from.":
    "Fig. 06 · 인스펙터 — 한 노드의 타입·가중치·부모·관계가 펼쳐진다. 위 데모가 빌려 온 바로 그 표면.",

  "Fig. 03 · Sphere mode — 50+ nodes, Fibonacci lattice, Bezier edges":
    "Fig. 03 · 스피어 모드 — 50+ 노드, 피보나치 격자, 베지에 간선",
  "Build · NODEPROMPT 2026.04": "빌드 · NODEPROMPT 2026.04",
  "Fig. 04 · Walkthrough — sphere → radial → interior, with multimodal input":
    "Fig. 04 · 둘러보기 — 스피어 → 래디얼 → 인테리어, 멀티모달 입력 포함",
  "muted, looping": "음소거 · 무한 반복",
  "Fig. 05 · Radial mode — drag, reweight, reconnect":
    "Fig. 05 · 래디얼 모드 — 끌고, 재가중하고, 다시 잇기",
  "Fig. 06 · Inspector — type, weight, relations":
    "Fig. 06 · 인스펙터 — 타입, 가중치, 관계",
  "NodePrompt sphere mode — concept nodes distributed on a 3D sphere via Fibonacci lattice, connected by Bezier edges in Mark Lombardi black-and-white network aesthetic.":
    "NodePrompt 스피어 모드 — 피보나치 격자로 3D 구 위에 분포된 개념 노드들이 마크 롬바르디 풍 흑백 베지에 간선으로 이어진 모습.",
  "NodePrompt radial mode — concept nodes laid out in concentric hierarchical rings around a central theme.":
    "NodePrompt 래디얼 모드 — 중심 테마를 둘러싼 동심 계층 링 위에 배치된 개념 노드들.",
  "NodePrompt inspector — node weights, types, and relationships exposed in a sidebar.":
    "NodePrompt 인스펙터 — 사이드바에 펼쳐진 노드 가중치, 타입, 관계.",
  "Linear chat hides the shape of an investigation. Branches, retries, and dead-ends collapse into a single scroll.":
    "한 줄짜리 채팅은 탐구의 모양을 가린다. 분기, 재시도, 막다른 길이 전부 한 줄 스크롤로 짓눌린다.",
  "Treat prompts as nodes. Edges encode dependency. Graph state is the artifact, not the chat log.":
    "프롬프트는 노드, 간선은 의존 관계. 남는 결과물은 채팅 로그가 아니라 그래프 자체.",
  "Used internally for research planning, writing decomposition, paper-outline generation. Closed beta with a small group.":
    "연구 계획, 글 쪼개기, 논문 개요 짜기에 내부적으로 쓴다. 소수 인원과 비공개 베타 중.",
  "Next.js 15 · React Three Fiber · Postgres · Lenis · Vercel. Custom shader for the node-glow.":
    "Next.js 15 · React Three Fiber · Postgres · Lenis · Vercel. 노드 글로우 셰이더는 직접 짰다.",
  "§ 01 — Why a graph": "§ 01 — 왜 그래프인가",
  "The chat metaphor was a brilliant onboarding choice and a terrible long-term tool. It rewards ephemerality: each turn is one branch, the rest are gone. But real research is parallel.":
    "채팅이란 비유는 입문용으론 훌륭했지만, 오래 쓰기엔 형편없는 도구다. 한 번 주고받을 때마다 갈래 하나만 남고 나머지는 흩어진다 — 휘발성을 상으로 주는 구조다. 그러나 진짜 탐구는 병렬이다.",
  "On a canvas you keep the dead ends. You see, materially, where you spent attention. The prompt becomes an object you can rearrange, not a transcript you scroll past.":
    "캔버스 위에선 막다른 길까지 그대로 남는다. 내가 어디에 주의를 썼는지가 눈에 보인다. 프롬프트는 스크롤로 흘려보내는 대본이 아니라, 옮기고 다시 배치하는 사물이 된다.",
  "Demo video · NodePrompt walkthrough · 0:42 · [placeholder mp4]":
    "데모 영상 · NodePrompt 둘러보기 · 0:42 · [자리표시자 mp4]",
  "§ 02 — On Mark Lombardi": "§ 02 — 마크 롬바르디에 대하여",
  "Mark Lombardi drew financial conspiracies in pencil — flows of money and influence as node-and-edge diagrams. Beautiful, illegible at full scale, undeniable up close.":
    "마크 롬바르디는 금융 음모를 연필로 그렸다 — 돈과 영향력의 흐름을 노드와 간선으로 풀어낸 그림. 전체로 보면 아름답지만 한눈엔 안 읽히고, 가까이서 보면 부정할 수 없다.",
  "NodePrompt borrows this aesthetic and the political sensibility behind it: that information has a topology, that the topology matters, and that drawing it is a form of argument.":
    "NodePrompt는 그 미학과 그 뒤의 정치적 감각을 같이 빌려 왔다 — 정보에는 위상이 있고, 그 위상이 중요하고, 그걸 그리는 행위 자체가 논증이라는 것.",
  "← Previous": "← 이전",
  "Index of all projects": "전체 프로젝트 목록",
  "Next →": "다음 →",
  "Research · Spintronics": "리서치 · 스핀트로닉스",
  "Back": "뒤로",
  "View": "보기",
  "View Project": "프로젝트 보기",
  "Read": "읽기",
  "Filter": "필터",
  "Next": "다음",
  "Previous": "이전",
  "English": "English",
  "한국어": "한국어",

  // ---------------- Paideia ----------------
  "Case study · Paideia": "케이스 스터디 · Paideia",
  "2026 — present · Solo · Open source": "2026 — 현재 · 1인 · 오픈소스",
  "A Claude Code plugin that turns a course folder into a self-formatting study system. Fourteen commands, six artifacts, one descending counter to the exam.":
    "수업 폴더 자체를 자기-정리되는 학습 시스템으로 만들어 주는 Claude Code 플러그인. 명령어 14개, 산출물 6개, 시험까지 줄어드는 카운터 하나.",
  "Claude Code plugin. Turns a course folder into a self-formatting study system. Six artifacts, one descending counter.":
    "Claude Code 플러그인. 수업 폴더를 자기-정리되는 학습 시스템으로. 산출물 6개, 줄어드는 카운터 하나.",
  "Claude Code · Markdown": "Claude Code · 마크다운",
  "02 · Plugin": "02 · 플러그인",

  // four-col Paideia
  "A graduate course is a stream of PDFs, slides, and hand-written homework, and a fixed exam date. Most of the work is bookkeeping the bookkeeper does not want to do.":
    "대학원 수업은 PDF, 슬라이드, 손글씨 과제의 흐름에 고정된 시험일이 더해진 형태다. 대부분의 일은 정작 본인이 하고 싶지 않은 사무·정리 작업이다.",
  "Treat the course folder as the system of record. Each plugin command takes the previous artifact as input and writes the next one — markdown all the way down, no app, no UI.":
    "수업 폴더 자체를 \"기록의 시스템\"으로 다룬다. 플러그인 명령은 이전 산출물을 입력으로 받아 다음 산출물을 쓴다 — 끝까지 마크다운, 앱 없음, UI 없음.",
  "Used through one full semester. Cheatsheet revisions converged after four cycles. Time spent on exam prep dropped by half; coverage of high-density topics went up.":
    "한 학기 내내 실사용. 치트시트 리비전은 4 사이클 만에 수렴. 시험 준비 시간은 절반으로 줄고, 고밀도 주제 커버리지는 올라감.",
  "Claude Code plugin · markdown artifacts · slash commands · zsh. Designed to be readable by a future maintainer who is just you, two semesters later.":
    "Claude Code 플러그인 · 마크다운 산출물 · 슬래시 명령 · zsh. 두 학기 뒤의 자기 자신이 읽어도 알아볼 수 있도록 설계.",

  // demo A — formation cycle
  "Fig. 01 · Formation cycle": "Fig. 01 · 형성 사이클",
  "Six stages, six artifacts, one descending counter.": "여섯 단계, 여섯 산출물, 하나의 줄어드는 카운터.",
  "ingest · analyze · drill · grade · weakmap · cheatsheet": "ingest · analyze · drill · grade · weakmap · cheatsheet",
  "course": "수업",
  "Stage": "단계",
  "ingest": "ingest", "analyze": "analyze", "drill": "drill",
  "grade": "grade", "weakmap": "weakmap", "cheatsheet": "cheatsheet",
  "Ingest": "Ingest", "Analyze": "Analyze", "Drill": "Drill",
  "Grade": "Grade", "Weakmap": "Weakmap", "Cheatsheet": "Cheatsheet",
  "Pull every PDF, slide deck, and lecture transcript in materials/, normalize to markdown, drop into converted/.":
    "materials/의 모든 PDF·슬라이드·강의록을 끌어와 마크다운으로 정규화하고 converted/에 떨어뜨린다.",
  "Read every converted file. Cluster topics. Cross-reference HW frequency. Score each topic for its likely exam weight.":
    "converted/의 모든 파일을 읽는다. 주제를 클러스터링하고, 과제 빈도와 교차 참조해, 주제별 시험 출제 가중치를 매긴다.",
  "Generate practice quizzes from the high-density topics. Mix HW twins (same form) and chains (build on each other).":
    "고밀도 주제에서 연습 퀴즈를 생성한다. 과제 트윈(같은 꼴)과 체인(누적되는 형태)을 섞는다.",
  "Compare student answers against the source. Log every error with its topic, type, and a one-line diagnosis.":
    "학생 답안을 원자료와 대조한다. 오류마다 주제·유형·한 줄 진단을 로그로 남긴다.",
  "Aggregate the error log against the topic graph. Surface the seams — where errors cluster, where review must concentrate.":
    "에러 로그를 주제 그래프에 합쳐 본다. 솔기를 드러낸다 — 오류가 몰리는 곳, 복습이 집중되어야 하는 곳.",
  "Compress the weakmap and the high-density topics into a single page. The page tightens with each cycle.":
    "weakmap과 고밀도 주제를 한 페이지로 압축한다. 사이클이 돌수록 페이지는 더 빽빽해진다.",
  "Writes to": "기록 위치",
  "Artifact preview": "산출물 미리 보기",
  "Idle. Press Advance to walk through one cycle. Each step writes a real artifact and ticks the exam counter.":
    "대기. Advance를 누르면 사이클 한 바퀴를 돈다. 매 단계에서 실제 산출물이 기록되고, 시험 카운터가 줄어든다.",
  "Cycle in progress · {n} / {total} stages complete · D−{d} until exam":
    "사이클 진행 중 · {n} / {total} 단계 완료 · 시험까지 D−{d}",
  "Cycle complete. Cheatsheet committed. Ready for the next pass — or the exam.":
    "사이클 종료. cheatsheet 커밋 완료. 다음 패스도, 시험도 준비됨.",
  "Advance →": "Advance →",
  "Cycle done": "완료",

  // demo B — heatmap
  "Fig. 02 · HW-density heatmap": "Fig. 02 · 과제 밀도 히트맵",
  "Where the homework piled up — and where the exam will follow.": "과제가 쌓였던 자리 — 그리고 시험이 따라올 자리.",
  "By chapter": "챕터순",
  "By exam-tier ↓": "시험 등급순 ↓",
  "no HW": "과제 없음", "1 HW": "과제 1회", "2 HW": "과제 2회", "3+ HW": "과제 3회+",
  "exam": "출제",
  "low": "낮음", "moderate": "보통", "high": "높음", "very high": "매우 높음",
  "Not seen in homework. Likely background or foundation only.":
    "과제에 등장하지 않음. 배경·기초 정도에 그칠 가능성.",
  "Touched once. Could appear; not central.":
    "한 번 다뤄짐. 나올 수 있으나 중심은 아님.",
  "Repeated emphasis. Strong candidate for an exam item.":
    "반복적으로 강조됨. 시험 문항의 유력 후보.",
  "Heavy density. Treat as guaranteed exam material.":
    "고밀도. 사실상 시험 출제 확정으로 다룸.",
  "Inspect": "보기",
  "Sort": "정렬",
  "HW": "과제",
  "Hover any cell. Sort by exam-tier to see the eight or so topics that will, statistically, show up.":
    "셀에 마우스를 올려 보기. 시험 등급순으로 정렬하면, 통계적으로 시험에 나올 8개 정도의 주제가 위에 모인다.",
  "48 topics across 12 chapters · pattern, never colour. Pattern density = HW count = exam probability.":
    "12개 챕터, 48개 주제 · 색이 아닌 패턴으로. 패턴 밀도 = 과제 횟수 = 출제 확률.",
  "Generated by /paideia:analyze": "/paideia:analyze 산출물",

  // demo C — terminal
  "Fig. 03 · Terminal walkthrough": "Fig. 03 · 터미널 워크스루",
  "A course folder, six commands, the artifacts on disk.": "수업 폴더 하나, 명령어 여섯, 디스크 위의 산출물들.",
  "zsh · paideia plugin · Claude Code": "zsh · paideia 플러그인 · Claude Code",
  "Run": "실행",
  "course/": "course/",
  "new": "신규",
  "Open": "열기",
  "Preview": "미리 보기",
  "Run a stage above, or click a file in the tree to open its contents.":
    "위에서 단계를 실행하거나, 트리에서 파일을 클릭해 내용을 연다.",
  "Every artifact is a real markdown file on disk. The plugin's job is to write them, version them, and bring them back to your attention.":
    "모든 산출물은 디스크 위의 실제 마크다운 파일이다. 플러그인이 하는 일은 그것들을 쓰고, 버전을 매기고, 다시 시야로 되돌리는 것이다.",
  "Plugin · /paideia · 14 commands": "플러그인 · /paideia · 명령 14개",

  // §-headers Paideia
  "§ 01 — On density": "§ 01 — 밀도에 대하여",
  "The exam is not a test of the syllabus. It is a test of the homework, projected onto the syllabus. Topics that the lecturer drilled in problem sets show up; topics they only mentioned, mostly do not.":
    "시험은 강의계획서의 시험이 아니다. 시험은 \"과제\"의 시험이고, 그것이 강의계획서 위에 투영된 것뿐이다. 강의자가 문제집으로 반복시킨 주제는 나오고, 그저 언급에 그친 주제는 대체로 나오지 않는다.",
  "Paideia treats homework count as the simplest, most honest signal of exam probability. The pattern below is just that count, made visible.":
    "Paideia는 과제 횟수를 시험 출제 확률의 가장 단순하고 정직한 신호로 취급한다. 아래 패턴은 그 횟수를 그저 눈에 보이게 해 둔 것뿐이다.",
  "§ 02 — Why a folder, not an app": "§ 02 — 왜 앱이 아닌 폴더인가",
  "Apps decide the shape of the work for you. Folders do not. Paideia is a plugin precisely because it has to compose with the rest of how a graduate student already works — Obsidian, git, a terminal, a stack of PDFs.":
    "앱은 일의 모양을 대신 정해 준다. 폴더는 그렇지 않다. Paideia가 플러그인인 이유는 정확히, 대학원생이 이미 하고 있는 작업 — Obsidian, git, 터미널, 쌓여 있는 PDF — 과 합쳐져야 하기 때문이다.",
  "Each command writes a markdown file you can open in any editor, diff in git, copy onto an exam-ready iPad, or send to a peer. The plugin is the choreography; the artifacts are the work.":
    "각 명령은 어떤 에디터로도 열 수 있고, git으로 diff할 수 있고, 시험용 iPad에 복사할 수 있고, 동료에게 보낼 수 있는 마크다운 파일을 쓴다. 플러그인은 동선이고, 산출물이 본 작업이다.",
  "§ 03 — Παιδεία": "§ 03 — Παιδεία",
  "Paideia is the Greek word for the formation of a person — not training, not certification, but the slow shaping that lets a citizen meet their city.":
    "Paideia는 \"사람의 형성\"을 가리키는 그리스어다. 훈련도 자격도 아닌, 한 시민이 자기 도시 앞에 설 수 있게 만드는 느린 빚어냄.",
  "This is a small tool that takes the name seriously: it does not promise to teach you, only to keep the formation legible while you do the work yourself.":
    "이 작은 도구는 그 이름을 진지하게 받아들인다 — 가르쳐 주겠다고 약속하지 않고, 다만 본인이 직접 일하는 동안 그 형성이 눈에 보이도록 지켜낼 뿐이다.",

  "Paideia": "Paideia",

  // about page — restructured (2026.04 patch)
  "§ 1 — The question that opens the road": "§ 1 — 길을 여는 질문",
  "§ 2 — Emotion is the keystone, not the noise": "§ 2 — 감정은 노이즈가 아니라 키스톤이다",
  "§ 3 — Three approaches to one problem": "§ 3 — 한 문제를 푸는 세 가지 접근",
  "§ 4 — The military, and the four-month sprint": "§ 4 — 군 복무, 그리고 넉 달의 스프린트",
  "§ 5 — Twenty years out": "§ 5 — 20년 뒤",

  "\"There is only one truly serious philosophical problem, and that is suicide.\"":
    "\"진정으로 심각한 철학적 문제는 단 하나, 자살이다.\"",
  "Albert Camus · The Myth of Sisyphus": "알베르 카뮈 · 시지프 신화",

  "We rehearsed civilization. We never rehearsed the leap.":
    "우리는 문명은 연습했지만, 도약은 한 번도 연습한 적이 없다.",
  "Centuries of scientific progress stack up — yet at the individual level we still ask the questions the Greeks asked in Athens.":
    "수 세기의 과학적 진보가 쌓였지만, 개인의 차원에선 여전히 아테네의 그리스인들이 던졌던 질문을 그대로 묻고 있다.",

  "KAIST sharpened it — didn't blur it.":
    "KAIST는 이 질문을 흐리지 않았다. 더 날카롭게 만들었다.",
  "The friends here — the national peak of personal capability — are just as fragile in front of small life-questions. This is the most important problem in front of us. More than AI, more than robotics.":
    "여기서 만난 친구들은 모든 지표로 따져 한국 개인 역량의 정점이지만, 작은 삶의 문제 앞에선 누구만큼이나 약하다. 이게 우리 앞에 놓인 가장 중요한 문제다. AI보다, 로봇보다.",

  "Not philosophy's job alone.":
    "철학만의 일이 아니다.",
  "Vector and graph knowledge structures dissolved Schopenhauer's old complaint. The philosophical question turns out to touch very specific technical surfaces.":
    "벡터·그래프 기반 지식 구조는 쇼펜하우어의 오랜 불평을 이미 지웠다. 순수 철학처럼 보이는 질문이 사실은 매우 구체적인 기술 표면에 닿아 있다.",

  "Pull quote — § 1.4": "발췌 — § 1.4",
  "Don't remove emotion. Change the soil it runs on.":
    "감정을 제거하지 말 것. 감정이 작동하는 토양을 바꿀 것.",

  "\"Science is cold.\" The newest tech keeps pulling emotion back in.":
    "\"과학은 차갑다\"고 한다. 그런데 최신 기술은 계속 감정을 다시 끌어들인다.",
  "A 1997 Science paper: damage the emotional centers, and decision-making collapses. The final commit is written by emotion.":
    "1997년 Science 논문 한 편: 감정 중추가 손상되면 의사결정이 무너진다. 결정의 최종 커밋은 결국 감정이 쓴다.",

  "Regret has a shape: a model you couldn't see.":
    "후회에는 모양이 있다 — 자기가 보지 못한 모델 하나.",
  "Decisions live on huge data and personal parameters. Most people overweight the indicator of the moment — because they can't see their own model.":
    "결정은 거대한 데이터와 개인적 파라미터 위에 얹혀 있는데, 대부분의 사람은 순간의 지표에 과하게 끌린다. 자기 모델이 안 보이기 때문이다.",

  "Engineered for the collective. Untouched at the individual.":
    "집단 단위에서는 이미 엔지니어링된 문제. 개인 단위에선 거의 손도 안 댄 영역.",
  "Palantir-style B2B AI already solves this for organizations: data shaping, digital twins, mathematically optimal decisions. The individual layer is almost untouched. That gap is the one I'm working in.":
    "팰런티어식 B2B AI는 조직 단위에선 이미 답한다 — 데이터 정형화, 디지털 트윈, 수학적으로 최적인 결정. 개인 단위는 거의 비어 있다. 내가 일하는 곳이 정확히 그 틈이다.",

  "Software, content, hardware. From the outside it looks like three careers; from the inside it's one problem split across the three substrates that determine what an individual can become.":
    "소프트웨어, 콘텐츠, 하드웨어. 밖에서 보면 직업 셋처럼 보이지만, 안에서는 한 사람이 어떤 사람이 될 수 있는지를 결정하는 세 기반에 같은 문제를 나눠 둔 것이다.",

  "Engineering — a stealth startup.":
    "엔지니어링 — 스텔스 스타트업.",
  "Co-founded right after discharge, October 2025. A personal-optimization engine, B2B2C, built first for study and learning environments. Lets a person see and steer their own decision model.":
    "전역 직후, 2025년 10월에 공동창업. 개인 최적화 엔진, B2B2C, 학습 환경을 먼저 겨냥했다. 한 사람이 자기 결정 모델을 보고 직접 조정할 수 있게 한다.",
  "Early-stage. Conversations with investors and university partners under embargo until launch. Beyond it, I lead two open-source projects in PKM and team-context tooling.":
    "초기 단계. 투자자와 대학 파트너와의 대화는 출시 전까지 엠바고. 이외에도 PKM과 팀 컨텍스트 툴링 쪽 오픈소스 프로젝트 두 개를 리드 중.",

  "Experimental — HUSTLY ARCHIV.":
    "실험 — HUSTLY ARCHIV.",
  "Producing your own emotional leap is a different game from inducing one in someone else. Sharing an emotional layer — directly and indirectly — is its own first-class problem.":
    "자기 감정의 도약을 만들어내는 일과, 그것을 다른 사람에게 유도하는 일은 다른 게임이다. 감정의 층을 직간접적으로 공유하는 일은 그 자체로 일급 문제다.",
  "Long-text editorial against a shorts-driven scene — Korean hip-hop. A year in, collaborations with Beean, GongGongGoo009, Pohranos, and HD BL4CK.":
    "쇼츠 중심으로 굴러가는 씬 — 한국 힙합 — 위에서 장문 편집을 굴린다. 일 년 차, Beean, GongGongGoo009, Pohranos, HD BL4CK과의 협업이 시작됐다.",

  "Academic — spintronics → neuromorphic.":
    "아카데믹 — 스핀트로닉스 → 뉴로모픽.",
  "PCs democratized software. The iPhone internalized content. Hardware is what physically rewires what an individual life can be.":
    "PC는 소프트웨어를 민주화했고, iPhone은 콘텐츠를 내재화시켰다. 한 사람의 삶이 무엇이 될 수 있는지를 물리적으로 다시 배선하는 건 결국 하드웨어다.",
  "Jun 2023 – Mar 2024 with Prof. Se-Kwon Kim — higher-order phase-locking in a domain wall. Since Oct 2025 with Prof. Kab-jin Kim — domain-wall mass and inertia in ferrimagnets. Reviewer on the Korean Inoue & Itoh spintronics textbook.":
    "2023년 6월–2024년 3월, 김세권 교수님 연구실에서 도메인 월의 고차 위상 잠금. 2025년 10월부터 김갑진 교수님 연구실에서 페리자성 도메인 월의 질량·관성. 이노우에·이토 스핀트로닉스 한국어판 감수.",

  "The constraint did the work.":
    "제약이 일했다.",
  "Apr 2024 – Sep 2025. Inside the wire, no PC. The constraint forced longer, deeper plans to emerge. A year drafting a long plan I've been executing ever since.":
    "2024년 4월–2025년 9월. 부대 안, PC 없음. 그 제약이 더 길고 깊은 계획을 끌어냈다. 일 년 동안 다듬은 긴 계획을, 전역 이후 형태만 바꿔가며 실행 중이다.",

  "Built from a phone, mid-service.":
    "복무 도중, 휴대폰 한 대로 만든 것.",
  "HUSTLY ARCHIV started inside that period. From a single phone. Now 3K+ followers, 1M+ views — a strategic experiment in sharing an emotional layer with another person.":
    "HUSTLY ARCHIV는 그 기간 안에서 시작됐다. 휴대폰 하나로. 지금은 팔로워 3천+, 누적 조회수 100만+ — 다른 사람에게 감정의 층을 공유하는 방법에 대한 전략적 실험이다.",

  "One problem, three layers, four months.":
    "한 문제, 세 층, 넉 달.",
  "Almost everything visible on this page was built in the months immediately after discharge. One problem attacked from three layers at once.":
    "이 페이지에 보이는 거의 전부가 전역 직후 몇 달 안에 만들어졌다. 한 문제를 세 층에서 동시에 친 결과다.",

  "My long-term goal is to make": "장기적으로 내 목표는 —",
  "an individual mental leap technically possible":
    "개인의 정신적 도약을 기술적으로 가능하게 만드는 것",

  "Undergrad → bridge paper, validated pilots, documented method.":
    "학부 → 연결 논문, 검증된 파일럿, 문서화된 방법.",
  "Through ≈2028: deeper statistical physics, non-equilibrium thermodynamics, information theory, computational neuroscience. A paper aimed at the bridge between domain-wall physics and neuromorphic computing. The startup stays in technical-validation mode. HUSTLY moves from instinct to documentation.":
    "≈2028까지: 통계물리, 비평형 열역학, 정보이론, 계산신경과학을 더 깊이. 도메인 월 물리와 뉴로모픽 컴퓨팅을 잇는 논문을 한 편 겨냥. 스타트업은 기술 검증 모드 유지. HUSTLY는 직감에서 문서화로 옮긴다.",

  "PhD → spintronics-based neuromorphic. Venture in parallel.":
    "박사 → 스핀트로닉스 기반 뉴로모픽. 벤처는 병행.",
  "2028–2033, target Stanford / MIT / UC Berkeley. Domain-wall synapses, spin-orbit-torque low-power circuits. After: a researcher-and-founder track that fuses neuromorphic hardware with personal-optimization software inside one new kind of organization.":
    "2028–2033, 타깃은 스탠퍼드 / MIT / UC 버클리. 도메인 월 시냅스, 스핀-궤도 토크 저전력 회로. 이후: 뉴로모픽 하드웨어와 개인 최적화 소프트웨어를 한 새로운 조직 안에서 합치는 연구자–창업자 트랙.",

  "Reshape the geometry of the hill, not roll the rock.":
    "바위를 굴리지 말고, 언덕의 지형을 다시 짤 것.",
  "Twenty years from now, I want to be remembered as someone who answered the question of personal mental leap not philosophically but technically. Not by rolling Sisyphus's rock — by reshaping the hill itself.":
    "20년 뒤, 개인의 정신적 도약이라는 질문에 철학이 아니라 기술로 답한 사람으로 기억되고 싶다. 시지프의 바위를 굴리는 게 아니라, 언덕 자체를 다시 짜는 방식으로.",

  "End · § 5": "끝 · § 5",
  "Long read · ≈ 12 min": "긴 글 · 약 12분",

  // about timeline (updated)
  "Stealth startup · co-founded": "스텔스 스타트업 · 공동창업",
  "Oct. B2B2C personal-optimization engine.": "10월. B2B2C 개인 최적화 엔진.",
  "Oct. Prof. Kab-jin Kim. Ferrimagnetic domain-wall dynamics.":
    "10월. 김갑진 교수. 페리자성 도메인 월 동역학.",
  "Apr 2024 – Sep 2025. Sergeant. The thinking break.":
    "2024년 4월 – 2025년 9월. 병장. 사고의 휴지기.",
  "Jan. From a phone, mid-service. 3K+ followers, 1M+ views.":
    "1월. 복무 중, 휴대폰 한 대로 시작. 팔로워 3천+, 조회수 100만+.",

  // venture page (updated)
  "Venture · Stealth startup · pre-launch": "벤처 · 스텔스 스타트업 · 출시 전",
  "STEALTH · MISSION · DECRYPTED": "스텔스 · 미션 · 복호화",
  "A personal-optimization engine.": "개인 최적화 엔진.",
  "B2B2C — built first for study and learning environments. Lets an individual see and steer their own decision model.":
    "B2B2C — 학습 환경을 우선 겨냥. 한 사람이 자기 결정 모델을 보고 직접 조정할 수 있게 한다.",
  "Further detail withheld until launch. Strategic partners, candidates, and press: write — replies sent under embargo.":
    "추가 정보는 출시 전까지 비공개. 전략적 파트너·후보자·기자: 메일 — 답장은 엠바고 하에 보낸다.",
  "2025 · 10 — present": "2025 · 10 — 현재",
  "Personal Optimization": "개인 최적화",
  "pre-launch": "출시 전",

  // hero (updated)
  "Stealth startup, co-founded. Personal-optimization engine, B2B2C, study-environment first.":
    "스텔스 스타트업, 공동창업. 개인 최적화 엔진, B2B2C, 학습 환경을 먼저.",
  "Stealth startup · pre-launch": "스텔스 스타트업 · 출시 전",
  "Stealth startup — a personal-optimization engine. B2B2C. Co-founded. Pre-launch.":
    "스텔스 스타트업 — 개인 최적화 엔진. B2B2C. 공동창업. 출시 전.",
  "KAIST Ultrafast Spin Dynamics Lab. Ferrimagnetic domain-wall dynamics; bridge to neuromorphic.":
    "KAIST 초고속 스핀 동역학 연구실. 페리자성 도메인 월 동역학; 뉴로모픽으로의 다리.",

  // research (updated)
  "Spin textures that move information faster than the medium that carries them. Ferrimagnetic domain-wall mass and inertia — and the long path from there to neuromorphic hardware.":
    "매질이 허락하는 속도보다 더 빨리 정보를 옮기는 스핀 텍스처. 페리자성 도메인 월의 질량·관성, 그리고 거기서 뉴로모픽 하드웨어로 이어지는 긴 길.",
  "Domain-wall motion as a synapse. Ferrimagnet compensation as a low-power switching regime.":
    "도메인 월 운동을 시냅스로. 페리자성 보상점을 저전력 스위칭 레짐으로.",

  // contact (link behavior)
  "→ click to open": "→ 클릭해서 열기",

  // about — restored body prose with inline bold (2026.04 patch v3)
  "Camus's sentence is the starting point of the road I'm walking. Humanity has stacked centuries of scientific progress, and yet at the individual level we are still asking the same questions the Greeks asked in Athens.":
    "카뮈의 이 한 문장이 내가 걷고 있는 길의 출발점이다. 인류는 수 세기의 과학적 진보를 쌓아 올렸지만, 개인의 차원에선 여전히 아테네의 그리스인들이 던졌던 질문을 그대로 묻고 있다.",
  "We rehearsed civilizational evolution without ever experiencing a leap in personal cognition.":
    "우리는 문명적 진화는 연습했지만, 개인적 인지의 도약은 단 한 번도 경험해 본 적이 없다.",
  "KAIST sharpened that observation — it didn't blur it. The friends I met here, by every metric the national peak of personal capability, are just as fragile in front of small life-questions as anyone else. People still spend their few decades re-asking the meaning of their lives, and lose much of what they had to the fear of death.":
    "KAIST는 이 관찰을 흐리는 게 아니라 더 날카롭게 만들었다. 여기서 만난 친구들은 모든 지표로 따져 한국 개인 역량의 정점이지만, 작은 삶의 문제 앞에서는 누구만큼이나 약하다. 사람들은 여전히 몇십 년을 자기 삶의 의미를 다시 묻는 데 쓰고, 죽음에 대한 두려움 때문에 가진 것을 많이 잃는다.",
  "In my view, this is the single most important problem in front of us — more than AI, more than robotics, more than any deep-tech we keep naming in the same sentence.":
    "내가 보기에 이건 우리 앞에 놓인 가장 중요한 문제 하나다 — AI보다, 로봇보다, 우리가 같은 문장에 자꾸 호명하는 어떤 딥테크보다.",
  "And it isn't only philosophy's job. Schopenhauer once complained that a book is forced to march from first sentence to last; in the 21st century, vector and graph-based knowledge structures dissolved that limitation entirely. The question that looks purely philosophical turns out to touch very specific technical surfaces.":
    "그리고 이건 철학만의 일이 아니다. 쇼펜하우어는 책이 첫 문장에서 마지막 문장까지 행진해야 한다는 점을 한탄했지만, 21세기엔 벡터·그래프 기반 지식 구조가 그 제약을 완전히 녹였다. 순수 철학처럼 보이는 질문은 사실 매우 구체적인 기술 표면에 닿아 있다.",

  "Emotion is the keystone of the deepest life-problems.":
    "감정은 가장 깊은 삶의 문제들에서 키스톤이다.",
  "\"Science is cold\" is the usual line. Ironically, the newest technologies keep trying to pull emotion back into the technical layer. A 1997 paper in Science showed that people whose emotional centers are damaged exhibit severe deficits in decision-making.":
    "\"과학은 차갑다\"는 말이 흔한 대사지만, 역설적으로 최신 기술들은 계속 감정을 기술 층위로 다시 끌어들이려 한다. 1997년 Science 논문은, 감정 중추가 손상된 사람들이 의사결정에서 심각한 결함을 보인다는 사실을 보여줬다.",
  "We claim to prize rational, evidence-based judgment, but the final commit is usually written by emotion.":
    "우리는 합리적이고 증거 기반의 판단을 중시한다고 말하지만, 최종 커밋은 대개 감정이 쓴다.",
  "The work, then, is not to remove or route around emotion — it is to change the soil so that emotion runs on a richer context.":
    "그렇다면 일은 감정을 제거하거나 우회하는 게 아니라, 감정이 더 풍부한 맥락 위에서 작동하도록 토양 자체를 바꾸는 것이다.",
  "Human decisions live on top of huge data and personal parameters, but most people make calls that overweight the indicator of the moment — because they cannot see their own model.":
    "인간의 결정은 거대한 데이터와 개인적 파라미터 위에 얹혀 있지만, 대부분의 사람은 순간의 지표에 과하게 끌리는 결정을 내린다 — 자기 모델이 안 보이기 때문이다.",
  "That, in one line, is the root cause of regret.":
    "이게 한 줄로 말한 후회의 근본 이유다.",
  "At the collective level this has already been engineered around — Palantir-style B2B AI shops solve it with data shaping, context modeling, and digital twins that compute mathematically optimal decisions.":
    "집단 단위에서는 이미 답이 나와 있다 — 팰런티어식 B2B AI는 데이터 정형화, 컨텍스트 모델링, 수학적으로 최적인 결정을 계산하는 디지털 트윈으로 푼다.",
  "At the individual level it is almost untouched":
    "개인 단위에선 거의 손도 안 댄 영역",
  ": too little data per user, too many business obstacles. That gap is the one I'm working in.":
    " — 사용자 1인당 데이터가 너무 적고, 비즈니스 장애가 너무 많기 때문이다. 내가 일하는 곳이 정확히 그 틈이다.",

  "Software, content, hardware. From the outside it looks like three careers; from the inside it is one problem split across the three substrates that determine what an individual can become.":
    "소프트웨어, 콘텐츠, 하드웨어. 밖에서 보면 직업 셋처럼 보이지만, 안에서는 한 사람이 어떤 사람이 될 수 있는지를 결정하는 세 기반에 같은 문제를 나눠 둔 것이다.",
  "Co-founded right after discharge, in October 2025.":
    "전역 직후, 2025년 10월에 공동창업.",
  "A personal-optimization engine, B2B2C, built first for study and learning environments.":
    "개인 최적화 엔진, B2B2C, 학습 환경을 먼저 겨냥했다.",
  "The model is the technical answer to the problem of biased individual decision-making — a new paradigm that lets a person see and steer their own decision model.":
    "이 모델은 편향된 개인 의사결정 문제에 대한 기술적 답이다 — 한 사람이 자기 결정 모델을 보고 직접 조정할 수 있게 하는 새로운 패러다임.",
  "Early-stage. Conversations with investors and university partners under embargo until launch. Beyond the company, I lead two open-source projects in PKM (personal knowledge management) and team-context tooling.":
    "초기 단계. 투자자와 대학 파트너와의 대화는 출시 전까지 엠바고. 회사 외에도 PKM(개인 지식 관리)과 팀 컨텍스트 툴링 쪽 오픈소스 프로젝트 두 개를 리드 중.",
  "Producing one's own emotional leap is a different game from inducing it in someone else.":
    "자기 자신의 감정적 도약을 만들어내는 일과, 그것을 다른 사람에게 유도하는 일은 다른 게임이다.",
  "Building methods for sharing emotional layers — directly and indirectly — is, on its own, a first-class problem.":
    "감정의 층을 직간접적으로 공유하는 방법을 만드는 일은 그 자체로 일급 문제다.",
  "I think it begins with the meticulous design of content.":
    "그 출발점은 콘텐츠의 치밀한 설계라고 본다.",
  "Through HUSTLY ARCHIV I run long-text editorial against a scene that runs on shorts — Korean hip-hop. A year in, collaborations under the same vision with Beean (Dejavu producer), GongGongGoo009 (KHA 2023 Newcomer of the Year), Pohranos (indie distributor under Mound Media), and HD BL4CK (producer for The Quiett, nafla, and others).":
    "HUSTLY ARCHIV를 통해 쇼츠로 굴러가는 씬 — 한국 힙합 — 위에서 장문 편집을 굴린다. 일 년 차, 같은 비전 아래 Beean(Dejavu 프로듀서), GongGongGoo009(KHA 2023 신인상), Pohranos(Mound Media 산하 인디 디스트리뷰션), HD BL4CK(The Quiett, nafla 등의 프로듀서)와의 협업이 시작됐다.",
  "If the startup is the software approach and the archive is the cultural one, the research is the hardware approach.":
    "스타트업이 소프트웨어 접근이고 아카이브가 문화 접근이라면, 연구는 하드웨어 접근이다.",
  "PCs democratized software; the iPhone internalized content; hardware is what physically rewires what an individual life can be.":
    "PC는 소프트웨어를 민주화했고, iPhone은 콘텐츠를 내재화시켰다. 한 사람의 삶이 무엇이 될 수 있는지를 물리적으로 다시 배선하는 건 결국 하드웨어다.",
  "From June 2023 to March 2024, at Prof. Se-Kwon Kim's KAIST Quantum Spin Dynamics Lab, I worked on higher-order phase-locking in a domain wall. Since October 2025, at Prof. Kab-jin Kim's KAIST Ultrafast Spin Dynamics Lab, the topic is domain-wall mass and inertia in ferrimagnets. I am also a reviewer on the Korean edition of Inoue & Itoh's spintronics textbook.":
    "2023년 6월부터 2024년 3월까지, 김세권 교수님의 KAIST 양자 스핀 동역학 연구실에서 도메인 월의 고차 위상 잠금을 다뤘다. 2025년 10월부터는 김갑진 교수님의 KAIST 초고속 스핀 동역학 연구실에서 페리자성 도메인 월의 질량과 관성을 본다. 이노우에·이토 스핀트로닉스 한국어판 감수도 맡고 있다.",
  "Long-horizon, the work points toward neuromorphic computing. Domain-wall motion and pinning physically mimic synaptic weight updates; ferrimagnet dynamics open a low-power switching regime.":
    "장기적으로 이 일은 뉴로모픽 컴퓨팅을 가리킨다. 도메인 월의 운동과 핀닝은 시냅스 가중치 업데이트를 물리적으로 모사하고, 페리자성 동역학은 저전력 스위칭 레짐을 연다.",
  "The ultimate digital twin of a person, I suspect, will live on neuromorphic hardware":
    "한 사람의 궁극적인 디지털 트윈은, 결국 뉴로모픽 하드웨어 위에 살게 될 거라 본다",
  "— and that is the substrate the rest of my work is being prepared to land on.":
    " — 그리고 그게 나머지 모든 일이 결국 착륙하도록 준비 중인 기반이다.",

  "The military service from April 2024 to September 2025 was the most important inflection point in how I think. Inside the wire, the physical constraints were severe — no PC.":
    "2024년 4월부터 2025년 9월까지의 군 복무는 사고 방식에서 가장 중요한 변곡점이었다. 부대 안에서 물리적 제약은 가혹했다 — PC가 없었다.",
  "But the constraint forced longer, deeper plans to emerge.":
    "하지만 그 제약이 더 길고 깊은 계획을 끌어냈다.",
  "I spent roughly a year drafting a long plan I have been executing in different shapes ever since I was discharged.":
    "그 안에서 일 년 정도, 전역 이후 형태만 바꿔가며 실행 중인 긴 계획을 다듬었다.",
  "HUSTLY ARCHIV was started inside that period. I built it from a single phone — a non-profit blog at first — and it now sits at":
    "HUSTLY ARCHIV는 그 기간 안에서 시작됐다. 휴대폰 하나로 만들었다 — 처음엔 비영리 블로그 — 지금은",
  "3K+ followers and 1M+ cumulative views.":
    "팔로워 3천+, 누적 조회수 100만+ 수준이다.",
  "Not a content side-project. A strategic experiment in the direct and indirect methods of sharing an emotional layer with another person.":
    "콘텐츠 사이드 프로젝트가 아니라, 다른 사람에게 감정의 층을 직간접적으로 공유하는 방법에 대한 전략적 실험이다.",
  "Almost everything visible on this page was built in the months immediately after discharge. Not a full résumé being padded — one problem attacked from three layers at once: a tool that gives an individual context, a method for sharing emotion with others, and the physical substrate that lets both eventually run.":
    "이 페이지에 보이는 거의 전부가 전역 직후 몇 달 안에 만들어졌다. 이력서를 부풀린 게 아니라 — 한 문제를 세 층에서 동시에 친 결과다: 한 사람에게 컨텍스트를 주는 도구, 다른 사람과 감정을 공유하는 방법, 그리고 둘 모두가 결국 돌아갈 물리적 기반.",

  "The next twenty years are deliberately under-described here.":
    "앞으로의 20년은 여기에 일부러 적게 써 두었다.",
  "There is a specific roadmap behind each thread — the undergraduate horizon, the doctoral horizon, the venture horizon.":
    "각 줄기 — 학부 시야, 박사 시야, 벤처 시야 — 뒤에는 구체적인 로드맵이 있다.",
  "Targets, dates, labs, funding plans, the shape of the organization the three streams are eventually meant to converge inside.":
    "타깃, 일정, 연구실, 펀딩 계획, 세 흐름이 결국 하나로 수렴해 들어갈 조직의 모양.",
  "None of it is on this page on purpose.":
    "그 어떤 것도 의도적으로 이 페이지에 두지 않았다.",
  "Public roadmaps invite optimization for the wrong audience. The work tends to be better when the people who need to see the next step see it through a conversation — not through a homepage.":
    "공개된 로드맵은 잘못된 청중을 향한 최적화를 부른다. 다음 단계를 봐야 할 사람들이 그것을 — 홈페이지가 아니라 — 대화를 통해 보게 될 때, 일은 대체로 더 나아진다.",
  "If you want the actual plan — the labs I'm aiming at, the milestones the startup is moving against, the way the three threads finally fit —":
    "실제 계획이 궁금하다면 — 노리고 있는 연구실, 스타트업이 향하고 있는 마일스톤, 세 줄기가 결국 어떻게 맞물리는지 —",
  "write.": "메일을 쓰면 된다.",
  "Replies are sent under embargo, but they are sent.":
    "답장은 엠바고 하에 가지만, 어쨌든 간다.",
  "Twenty years from now, I want to be remembered as someone who answered the question of personal mental leap not philosophically but technically.":
    "20년 뒤, 개인의 정신적 도약이라는 질문에 철학이 아니라 기술로 답한 사람으로 기억되고 싶다.",
  "Not by rolling Sisyphus's rock — by reshaping the hill itself.":
    "시지프의 바위를 굴리는 방식이 아니라, 언덕 자체를 다시 짜는 방식으로.",
};

window.__lang = (() => {
  try { return localStorage.getItem("lang") || "en"; } catch { return "en"; }
})();
// English-only for the initial deploy. Language-toggle UI is hidden but
// kept intact in the JSX below — flip the `false &&` wrapper to re-enable.
window.__lang = "en";
document.documentElement.setAttribute("lang", window.__lang);

// Hover-capable detection — used by every onMouseEnter/Leave site to
// prevent touch taps from firing a sticky "hover" state with no
// matching mouseleave. Re-evaluated on media-change so that detached
// magic-trackpads etc. flip cleanly.
window.__hasHover = !!(window.matchMedia && window.matchMedia("(hover: hover)").matches);
if (window.matchMedia) {
  try {
    window.matchMedia("(hover: hover)").addEventListener("change", (e) => {
      window.__hasHover = e.matches;
    });
  } catch (_) { /* older Safari — silently skip */ }
}

function useLang() {
  const [lang, setLangState] = useState(window.__lang);
  useEffect(() => {
    const onChange = () => setLangState(window.__lang);
    window.addEventListener("lang:change", onChange);
    return () => window.removeEventListener("lang:change", onChange);
  }, []);
  const setLang = (l) => {
    window.__lang = l;
    try { localStorage.setItem("lang", l); } catch {}
    document.documentElement.setAttribute("lang", l);
    window.dispatchEvent(new Event("lang:change"));
  };
  return [lang, setLang];
}
function t(en) {
  if (window.__lang !== "kr") return en;
  return KR_DICT[en] || en;
}

// ---------------- Router ----------------
const RouteContext = createContext(null);

const ROUTES = [
  { id: "index",    label: "Index",    num: "00", path: "/" },
  { id: "about",    label: "About",    num: "01", path: "/about" },
  { id: "research", label: "Research", num: "02", path: "/research" },
  { id: "projects", label: "Projects", num: "03", path: "/projects" },
  { id: "venture",  label: "Venture",  num: "04", path: "/venture" },
  { id: "archive",  label: "Archive",  num: "05", path: "/archive" },
  { id: "writing",  label: "Writing",  num: "06", path: "/writing" },
  { id: "contact",  label: "Contact",  num: "07", path: "/contact" },
  { id: "nodeprompt", label: "NodePrompt", num: "03a", path: "/projects/nodeprompt", hidden: true },
  { id: "paideia",    label: "Paideia",    num: "03b", path: "/projects/paideia",    hidden: true },
  { id: "paideia-codex", label: "Paideia · Codex", num: "03c", path: "/projects/paideia-codex", hidden: true },
];

function RouterProvider({ children }) {
  const initial = (() => {
    const h = (window.location.hash || "").replace(/^#/, "");
    const found = ROUTES.find(r => r.id === h);
    return found ? found.id : "index";
  })();
  const [route, setRoute] = useState(initial);
  const [transitioning, setTransitioning] = useState(false);

  const go = useCallback((id) => {
    if (id === route) return;
    setTransitioning(true);
    window.scrollTo({ top: 0, behavior: "instant" });
    setTimeout(() => {
      setRoute(id);
      window.location.hash = id === "index" ? "" : `#${id}`;
      setTimeout(() => setTransitioning(false), 80);
    }, 540);
  }, [route]);

  useEffect(() => {
    const onHash = () => {
      const h = (window.location.hash || "").replace(/^#/, "");
      const found = ROUTES.find(r => r.id === h) || ROUTES[0];
      if (found.id !== route) {
        // External hash changes (e.g., a card setting window.location.hash
        // directly) must also reset scroll, so the user lands at the top
        // of the new page instead of inheriting the previous scroll position.
        window.scrollTo({ top: 0, behavior: "instant" });
        setRoute(found.id);
      }
    };
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, [route]);

  // Browsers will sometimes attempt to restore a prior scroll position when
  // the URL fragment changes (or on back/forward). Disable that — every route
  // change in this app explicitly handles its own scroll.
  useEffect(() => {
    if ("scrollRestoration" in window.history) {
      const prev = window.history.scrollRestoration;
      window.history.scrollRestoration = "manual";
      return () => { window.history.scrollRestoration = prev; };
    }
  }, []);

  return (
    <RouteContext.Provider value={{ route, go, transitioning, routes: ROUTES }}>
      {children}
    </RouteContext.Provider>
  );
}

const useRouter = () => useContext(RouteContext);

// ---------------- Custom Cursor ----------------
function CustomCursor() {
  const ref = useRef(null);
  const ringRef = useRef(null);
  const [variant, setVariant] = useState("default"); // default | link | text | hidden | spotlight
  const [label, setLabel] = useState("");

  useEffect(() => {
    let x = window.innerWidth / 2, y = window.innerHeight / 2;
    let rx = x, ry = y;
    let raf;

    const onMove = (e) => { x = e.clientX; y = e.clientY; };
    const onOver = (e) => {
      const t = e.target.closest("[data-cursor]");
      if (t) {
        setVariant(t.getAttribute("data-cursor"));
        setLabel(t.getAttribute("data-cursor-label") || "");
      } else {
        setVariant("default"); setLabel("");
      }
    };
    const onLeave = () => setVariant("hidden");
    const onEnter = () => setVariant("default");

    const tick = () => {
      rx += (x - rx) * 0.22;
      ry += (y - ry) * 0.22;
      if (ref.current) {
        // Node body: 12px filled dot, centered on the pointer.
        ref.current.style.transform = `translate(${x - 6}px, ${y - 6}px)`;
      }
      if (ringRef.current) {
        // Halo ring trails with easing.
        ringRef.current.style.transform = `translate(${rx - 14}px, ${ry - 14}px)`;
      }
      raf = requestAnimationFrame(tick);
    };
    tick();

    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseover", onOver);
    document.documentElement.addEventListener("mouseleave", onLeave);
    document.documentElement.addEventListener("mouseenter", onEnter);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseover", onOver);
      document.documentElement.removeEventListener("mouseleave", onLeave);
      document.documentElement.removeEventListener("mouseenter", onEnter);
    };
  }, []);

  if (window.innerWidth < 900) return null;

  return (
    <>
      {/* Node body — filled black circle, ~12px, the "node" itself */}
      <div
        ref={ref}
        style={{
          position: "fixed", top: 0, left: 0,
          width: variant === "link" ? 16 : 12,
          height: variant === "link" ? 16 : 12,
          marginLeft: variant === "link" ? -2 : 0,
          marginTop: variant === "link" ? -2 : 0,
          background: variant === "hidden" ? "transparent" : "var(--fg)",
          borderRadius: "50%", pointerEvents: "none", zIndex: 9998,
          boxShadow: "0 0 0 2px var(--bg)",
          transition: "width 240ms var(--easing-default), height 240ms var(--easing-default), margin 240ms var(--easing-default), background 200ms",
        }}
      />
      {/* Halo ring — thin outline that trails the node */}
      <div
        ref={ringRef}
        style={{
          position: "fixed", top: 0, left: 0,
          width: variant === "link" ? 56 : variant === "spotlight" ? 280 : 28,
          height: variant === "link" ? 56 : variant === "spotlight" ? 280 : 28,
          marginLeft: variant === "link" ? -14 : variant === "spotlight" ? -126 : 0,
          marginTop: variant === "link" ? -14 : variant === "spotlight" ? -126 : 0,
          border: variant === "spotlight" ? "none" : "1px solid var(--fg)",
          borderRadius: "50%", pointerEvents: "none", zIndex: 9997,
          background: variant === "spotlight" ? "radial-gradient(circle, rgba(255,255,255,0) 0%, rgba(255,255,255,0) 50%, rgba(10,10,10,0.95) 100%)" : "transparent",
          opacity: variant === "hidden" ? 0 : variant === "link" ? 0.55 : 0.4,
          transition: "width 400ms var(--easing-default), height 400ms var(--easing-default), margin 400ms var(--easing-default), background 300ms, border 300ms, opacity 240ms",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.1em",
          textTransform: "uppercase",
          color: "var(--fg)",
        }}
      >
        {variant === "link" && (
          <span style={{ position: "absolute", top: "100%", marginTop: 12, whiteSpace: "nowrap" }}>{label}</span>
        )}
      </div>
    </>
  );
}

// ---------------- Header ----------------
function Header() {
  const { route, go, routes } = useRouter();
  const [lang, setLang] = useLang();
  const [scrolled, setScrolled] = useState(false);
  const [hovered, setHovered] = useState(null);
  const [mobileOpen, setMobileOpen] = useState(false);

  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 24);
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  return (
    <header style={{
      position: "fixed", top: 0, left: 0, right: 0, zIndex: 100,
      padding: "20px var(--side-pad)",
      borderBottom: scrolled ? "1px solid var(--line-soft)" : "1px solid transparent",
      background: scrolled ? "rgba(255,255,255,0.85)" : "transparent",
      backdropFilter: scrolled ? "blur(12px)" : "none",
      transition: "all 400ms var(--easing-default)",
    }}>
      <div style={{
        display: "grid",
        gridTemplateColumns: "1fr auto 1fr",
        alignItems: "center", gap: 24,
      }}>
        <button
          onClick={() => go("index")}
          data-cursor="link" data-cursor-label={t("Home")}
          style={{
            display: "flex", alignItems: "baseline", gap: 10,
            fontFamily: "var(--font-mono)", fontSize: 12,
            letterSpacing: "0.08em", textTransform: "uppercase",
            justifySelf: "start",
          }}>
          <NodeMark />
          <span><b style={{ fontWeight: 600 }}>Taewoo Park</b></span>
        </button>

        <nav style={{ display: "flex", gap: 4, alignItems: "center" }}>
          {routes.filter(r => !r.hidden).slice(1).map(r => (
            <button
              key={r.id}
              onClick={() => go(r.id)}
              onMouseEnter={() => window.__hasHover && setHovered(r.id)}
              onMouseLeave={() => window.__hasHover && setHovered(null)}
              data-cursor="link" data-cursor-label={t(r.label)}
              style={{
                padding: "8px 14px",
                fontFamily: "var(--font-mono)", fontSize: 12,
                letterSpacing: "0.08em", textTransform: "uppercase",
                color: route === r.id ? "var(--bg)" : "var(--fg)",
                background: route === r.id ? "var(--fg)" : "transparent",
                position: "relative",
                transition: "all 280ms var(--easing-default)",
              }}>
              <span style={{
                fontSize: 9, marginRight: 8,
                opacity: hovered === r.id || route === r.id ? 1 : 0.4,
                transition: "opacity 280ms",
              }}>{r.num}</span>
              {t(r.label)}
            </button>
          ))}
        </nav>

        <div style={{ justifySelf: "end", display: "flex", alignItems: "center", gap: 14 }}>
          {false && (
          <div
            role="group" aria-label="language"
            style={{
              display: "flex", alignItems: "stretch",
              border: "1px solid var(--fg)",
              fontFamily: "var(--font-mono)", fontSize: 11,
              letterSpacing: "0.08em", textTransform: "uppercase",
              lineHeight: 1,
              overflow: "hidden",
            }}>
            {["en", "kr"].map((code) => (
              <button
                key={code}
                onClick={() => setLang(code)}
                data-cursor="link" data-cursor-label={code === "en" ? "English" : "한국어"}
                style={{
                  padding: "6px 9px",
                  background: lang === code ? "var(--fg)" : "transparent",
                  color: lang === code ? "var(--bg)" : "var(--fg)",
                  transition: "background 200ms var(--easing-default), color 200ms var(--easing-default)",
                }}>
                {code === "en" ? "EN" : "KR"}
              </button>
            ))}
          </div>
          )}
          <button
            className="mobile-menu-btn"
            onClick={() => setMobileOpen(v => !v)}
            style={{
              display: "none", alignItems: "center", gap: 6,
              padding: "6px 10px", border: "1px solid var(--fg)",
              fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.08em", textTransform: "uppercase",
            }}>
            {mobileOpen ? t("Close ×") : t("Menu ≡")}
          </button>
        </div>
      </div>
      {mobileOpen && (
        <div style={{
          position: "fixed", top: 64, left: 0, right: 0, bottom: 0,
          background: "var(--bg)", padding: "24px var(--side-pad)",
          zIndex: 99, borderTop: "1px solid var(--line)",
          display: "flex", flexDirection: "column", gap: 4,
        }}>
          {routes.filter(r => !r.hidden).map(r => (
            <button key={r.id} onClick={() => { go(r.id); setMobileOpen(false); }}
              style={{
                textAlign: "left", padding: "16px 0", borderBottom: "1px solid var(--line-soft)",
                display: "flex", alignItems: "baseline", gap: 16,
                fontFamily: "var(--font-display)", fontSize: 28,
                color: route === r.id ? "var(--fg)" : "var(--fg-muted)",
                fontStyle: route === r.id ? "italic" : "normal",
              }}>
              <span className="micro" style={{ minWidth: 30 }}>{r.num}</span>
              {t(r.label)}
            </button>
          ))}
        </div>
      )}
      <style>{`
        @keyframes pulse {
          0%, 100% { opacity: 1; }
          50% { opacity: 0.3; }
        }
        @media (max-width: 900px) {
          .mobile-hide { display: none !important; }
        }
      `}</style>
    </header>
  );
}

function NodeMark() {
  return (
    <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1">
      <circle cx="4" cy="4" r="2" fill="currentColor" />
      <circle cx="18" cy="4" r="2" />
      <circle cx="11" cy="11" r="2" fill="currentColor" />
      <circle cx="4" cy="18" r="2" />
      <circle cx="18" cy="18" r="2" fill="currentColor" />
      <line x1="4" y1="4" x2="11" y2="11" />
      <line x1="18" y1="4" x2="11" y2="11" />
      <line x1="4" y1="18" x2="11" y2="11" />
      <line x1="18" y1="18" x2="11" y2="11" />
    </svg>
  );
}

// ---------------- Footer ----------------
function Footer() {
  const { go, routes } = useRouter();
  useLang(); // subscribe to lang changes
  const taglineLines = t("Aligning physics,\ncode, and culture.").split("\n");
  return (
    <footer style={{
      borderTop: "1px solid var(--line)",
      padding: "60px var(--side-pad) 32px",
      background: "var(--bg)",
      position: "relative",
    }}>
      <div className="footer-grid" style={{ display: "grid", gridTemplateColumns: "2fr 1fr 1fr 1fr", gap: 48, marginBottom: 80 }}>
        <div>
          <div className="display-lg" style={{ marginBottom: 24 }}>
            {taglineLines.map((line, i) => (
              <React.Fragment key={i}>{line}{i < taglineLines.length - 1 && <br/>}</React.Fragment>
            ))}
          </div>
          <div className="micro">{t("Toward a civilization-scale solution.")}</div>
        </div>
        <div>
          <div className="micro" style={{ marginBottom: 16 }}>{t("Index")}</div>
          {routes.filter(r => !r.hidden).slice(0, 4).map(r => (
            <div key={r.id} style={{ marginBottom: 6 }}>
              <button onClick={() => go(r.id)} data-cursor="link" data-cursor-label={t(r.label)}
                style={{ fontFamily: "var(--font-body)", fontSize: 14 }}>
                <span style={{ color: "var(--fg-muted)", marginRight: 10, fontFamily: "var(--font-mono)", fontSize: 11 }}>{r.num}</span>
                {t(r.label)}
              </button>
            </div>
          ))}
        </div>
        <div>
          <div className="micro" style={{ marginBottom: 16 }}>&nbsp;</div>
          {routes.filter(r => !r.hidden).slice(4).map(r => (
            <div key={r.id} style={{ marginBottom: 6 }}>
              <button onClick={() => go(r.id)} data-cursor="link" data-cursor-label={t(r.label)}
                style={{ fontFamily: "var(--font-body)", fontSize: 14 }}>
                <span style={{ color: "var(--fg-muted)", marginRight: 10, fontFamily: "var(--font-mono)", fontSize: 11 }}>{r.num}</span>
                {t(r.label)}
              </button>
            </div>
          ))}
        </div>
        <div>
          <div className="micro" style={{ marginBottom: 16 }}>{t("External")}</div>
          {[
            ["GitHub", "github.com/TaewoooPark"],
            ["X / Twitter", "@theoverstrcture"],
            ["Instagram", "@t.wo0_x · @hustlyarchiv.kr"],
            ["Email", "ptw151125@kaist.ac.kr"],
          ].map(([k, v]) => (
            <div key={k} style={{ marginBottom: 6, fontSize: 14 }}>
              <span style={{ color: "var(--fg-muted)" }}>{k} </span>
              <span>{v}</span>
            </div>
          ))}
        </div>
      </div>
      <div className="hr-soft" style={{ marginBottom: 20 }} />
      <div style={{ display: "flex", justifyContent: "space-between" }} className="micro">
        <span>{t("© 2026 Taewoo Park")}</span>
        <span>{t("Last edit · 2026.04 · Built in Daejeon")}</span>
        <span>{t("v0.1 — prototype")}</span>
      </div>
    </footer>
  );
}

// ---------------- Preloader ----------------
function Preloader({ onDone }) {
  const [progress, setProgress] = useState(0);
  const [phase, setPhase] = useState("loading"); // loading | aligning | exit

  useEffect(() => {
    let p = 0;
    const id = setInterval(() => {
      p += Math.random() * 18 + 6;
      if (p >= 100) {
        p = 100;
        clearInterval(id);
        setProgress(100);
        setTimeout(() => setPhase("aligning"), 150);
        setTimeout(() => setPhase("exit"), 900);
        setTimeout(() => {
          window.__preloaderDone = true;
          window.dispatchEvent(new CustomEvent("preloader:done"));
          onDone && onDone();
        }, 1500);
      } else {
        setProgress(p);
      }
    }, 90);
    return () => clearInterval(id);
  }, [onDone]);

  // 60 dots that scatter then align into a domain-wall row
  const dots = useMemo(() => {
    return Array.from({ length: 60 }).map((_, i) => ({
      sx: Math.random() * 100,
      sy: Math.random() * 100,
      tx: 10 + (i / 59) * 80,
      ty: 50,
      delay: Math.random() * 0.3,
    }));
  }, []);

  return (
    <div style={{
      position: "fixed", inset: 0, zIndex: 10000, background: "var(--bg)",
      pointerEvents: phase === "exit" ? "none" : "auto",
      transform: phase === "exit" ? "translateY(-100%)" : "translateY(0)",
      transition: "transform 800ms var(--easing-emphasized)",
    }}>
      <div style={{
        position: "absolute", inset: 0,
        display: "flex", flexDirection: "column",
        justifyContent: "space-between",
        padding: "32px var(--side-pad)",
      }}>
        <div className="micro" style={{ display: "flex", justifyContent: "space-between" }}>
          <span>{t("TAEWOO PARK / PERSONAL SITE / v0.1")}</span>
          <span>{Math.round(progress).toString().padStart(3, "0")}</span>
        </div>

        <div style={{ position: "relative", width: "100%", height: 200 }}>
          {dots.map((d, i) => (
            <span key={i} style={{
              position: "absolute",
              width: 4, height: 4, background: "var(--fg)", borderRadius: "50%",
              left: `${phase === "loading" ? d.sx : d.tx}%`,
              top: `${phase === "loading" ? d.sy : d.ty}%`,
              transition: `left 900ms var(--easing-default) ${d.delay}s, top 900ms var(--easing-default) ${d.delay}s`,
            }} />
          ))}
          {/* domain wall line */}
          <div style={{
            position: "absolute", left: "10%", top: "calc(50% + 1px)",
            width: phase === "loading" ? "0%" : "80%", height: 1, background: "var(--fg)",
            transition: "width 1000ms var(--easing-default) 200ms",
          }} />
        </div>

        <div>
          <div style={{ width: "100%", height: 1, background: "var(--line-soft)", position: "relative" }}>
            <div style={{
              position: "absolute", top: 0, left: 0, height: "100%",
              width: `${progress}%`, background: "var(--fg)",
              transition: "width 200ms linear",
            }} />
          </div>
          <div className="micro" style={{ marginTop: 12, display: "flex", justifyContent: "space-between" }}>
            <span>{phase === "loading" ? t("Loading manifest…") : phase === "aligning" ? t("Aligning domain wall…") : t("Ready")}</span>
            <span>m × (m × H)</span>
          </div>
        </div>
      </div>
    </div>
  );
}

// ---------------- Page Curtain ----------------
function PageCurtain({ active }) {
  return (
    <div style={{
      position: "fixed", inset: 0, zIndex: 200, pointerEvents: "none",
      transform: active ? "translateY(0)" : "translateY(100%)",
      transition: "transform 540ms var(--easing-emphasized)",
      background: "var(--fg)",
      display: "flex", alignItems: "center", justifyContent: "center",
    }}>
      <span style={{
        color: "var(--bg)", fontFamily: "var(--font-mono)", fontSize: 11,
        letterSpacing: "0.16em", textTransform: "uppercase",
        opacity: active ? 1 : 0, transition: "opacity 200ms",
      }}>
        ◦◦◦ traversing ◦◦◦
      </span>
    </div>
  );
}

Object.assign(window, {
  RouterProvider, useRouter, ROUTES,
  CustomCursor, Header, Footer,
  Preloader, PageCurtain, NodeMark,
});
