// Developer Portfolio — main app
// Hyundai Card M Point Mall inspired · B&W · Massive Typography

const { useEffect, useState, useRef } = React;

// ============================================
// DATA
// ============================================
// TODO(본인): 아래 값은 아직 가짜 값입니다. 실제 정보로 바꿔주세요.
//   - resume: 이력서 링크(Notion 등). 비어 있으면 버튼이 숨겨집니다.
//   - photo : 프로필 사진 경로(예: "profile.jpg", 정사각형 권장). 비어 있으면 이니셜이 표시됩니다.
//   - telegram / instagram / linktree: 비워두면 해당 줄이 숨겨집니다.
const DEV_INFO = {
  name: "Cho Soyoung",
  nameKo: "조소영", // 프로필 카드에 크게 표시되는 한글 이름
  nameEn: "", // 한글 이름 아래 영문 표기
  role: "Backend / Embedded Engineer",
  intro: "서버와 AI를 이어 현실을 움직이는 브릿지 개발자이자, 임베디드까지 파고드는 학생.",
  // 왼쪽 intro(무엇을 만드는지)와 겹치지 않게, 일하는 방식·관심사·학교 등을 적어주세요.
  bio: "Building a better world, one code at a time",
  photo: "screenshots/Jobpic.jpg",
  tagline: "Building a better world, one code at a time.",
  location: "Seoul, KR",
  timezone: "KST (UTC+9)",
  status: "Open to work",
  available: "2026 Q3 →",
  stackFocus: "Java · Spring Boot · AWS",
  email: "josy032321@gmail.com",
  github: "https://github.com/FLOHERE",
  linkedin: "linkedin.com/in/소영-조-8b2763362",
  telegram: "SSOYAM0225",                     // 텔레그램 아이디만 (@ 제외) — t.me 링크로 연결
  instagram: "",                     // 아이디만 (@ 제외)
  linktree: "",
  resume: "https://app.notion.com/p/CHO-SOYOUNG-6ae71718a5f98305887e81b1b027c389?source=copy_link"
};

// 첫 화면 하단 뱃지 (스크린샷 기준 — 실제로 쓴 것만 남기세요)
const STACK_BADGES = ["C", "C++", "Java", "Python", "JavaScript", "Spring Boot", "MySQL", "MariaDB", "Docker", "Linux", "AWS", "GitHub"];

// TODO(본인): About 섹션 활동 슬라이드. photo 경로(예: "screenshots/act1.jpg")와 내용을 채워주세요.
//   photo가 비어 있으면 회색 자리표시 박스가 보입니다. lines는 5줄 내외 권장.
const ACTIVITIES = [
  { photo: "/screenshots/likelion.jpg", title: "멋쟁이사자처럼 교내 운영진 활동", period: "26.03~", lines: ["매주 정기 백엔드 세션을 진행하여 부원들의 개발 역량을 높이고, 학기 팀 프로젝트 매니징 및 중앙 해커톤에 참여하여 실전 협업 경험을 쌓았습니다."] },
  { photo: "/screenshots/capstone.jpg", title: "교내 캡스톤디자인 참여 및 금상 수상", period: "26.03~26.06", lines: ["청년들의 취업 문제를 해결하는 교내 LMS 연계형 포트폴리오 기반 AI면접 사이트를 개발하여 교내 캡스톤디자인 경진대회에서 금상을 수상하며 프로젝트 완성도를 입증했습니다."] },
  { photo: "/screenshots/learningmate.jpg", title: "러닝메이트", period: "25.09~26.12", lines: ["저학년 후배들을 대상으로 전공 핵심 과목(PYTHON,소프트웨어 설계) 강의를 진행하고, GitHub를 통해 학습 자료를 체계적으로 아카이빙 및 공유하며 학업·진로 멘토링을 수행했습니다."] },
  { photo: "/screenshots/club.jpeg", title: "전공동아리 회장 활동", period: "25.03~25.12", lines: ["전공동아리 회장으로 활동 하며 동아리 내 여러 팀들을 관리하고 팀원간 갈등을 해결하며, 전공동아리 경진대회에 출전하기도 했습니다."] }];

// project: 수상과 연결된 프로젝트 번호(예: "01"). 비어 있으면 표시하지 않습니다. (피드백 12)
const AWARDS = [
  { year: "2026", title: "AIDX OpenDay", prize: "대상", tier: "", project: "" },
  { year: "2026", title: "기업 연계형 캡스톤디자인 경진대회", prize: "금상", tier: "gold", project: "" },
  { year: "2026", title: "창업 경진대회", prize: "장려상", tier: "", project: "" },
  { year: "2025", title: "전공동아리 경진대회", prize: "동상", tier: "", project: "" },
  { year: "2025", title: "AI 헬스케어 해커톤", prize: "동상", tier: "", project: "" },
  { year: "2025", title: "GSAT 2025 영 스타트업 캠프 경진대회", prize: "장려상", tier: "", project: "" },
  ];

// TODO(본인): 각 프로젝트의 빈 필드를 채우면 화면에 자동으로 나타납니다.
//   github / demo : 링크 (피드백 12, 13)
//   image         : 썸네일 또는 아키텍처 다이어그램 이미지 경로 (16, 27)
//   team          : 팀 규모와 구성. 예) "4인 팀 (BE 2 · FE 1 · AI 1)" (24)
//   myPart        : 내가 직접 만든 부분 한 줄 (24)
//   metrics       : 결과 수치. 예) [{ label: "AI 피드백 응답", value: "3s → 0.8s" }] (29, 37)
//   troubleshooting: { issue, cause, fix } 겪은 문제 → 원인 → 해결 (32)
//   retro         : "다시 한다면" 한 줄 (30)
const EMPTY_EXTRAS = { github: "", demo: "", image: "", team: "", myPart: "", metrics: [], troubleshooting: null, retro: "" };

const PROJECTS = [
  {
    ...EMPTY_EXTRAS,
    num: "01",
    name: "안정적인 멀티 서버 아키텍처 및 실시간 AI 파이프라인 구축",
    year: "2026",
    role: "Backend Lead",
    stack: "Spring Boot · FastAPI · AWS",
    desc: "React, Spring Boot, FastAPI라는 서로 다른 기술 스택을 결합하여 취업 준비 통합 솔루션(DIDIM)을 개발했습니다.",
    placeholder: "Project Architecture",
    tags: ["Spring Boot", "FastAPI", "MSA", "LLM"],
    details: {
      problem: "기존 취업 서비스는 분절된 기능만을 제공했으며, 대용량 미디어 데이터 전송과 실시간 AI 분석 간의 지연 및 동기화 문제가 있었습니다.",
      solution: "Spring Boot를 메인 서버로, FastAPI를 AI 분석 전용 서버로 분리하는 멀티 서버 아키텍처를 설계했습니다. 비동기 처리 및 임시 스토리지 관리를 도입하여 병목 현상을 해결했습니다.",
      result: "중복 데이터 방지와 에러 핸들링을 통해 운영 환경 수준의 백엔드 안정성을 확보하고 실시간에 가까운 AI 피드백 루프를 구축했습니다."
    }
  },
  {
    ...EMPTY_EXTRAS,
    num: "02",
    name: "디지털 소외 계층을 위한 이벤트 기반 알림 시스템 설계",
    year: "2025",
    role: "Backend",
    stack: "Spring Boot · SMS API",
    desc: "근로 출퇴근 확인이 어려운 노인들을 위해 키오스크 연동형 자동 SMS 발송 시스템을 구축했습니다.",
    placeholder: "Notification System",
    tags: ["Spring Boot", "Event-driven", "API Integration", "Troubleshooting"],
    details: {
      problem: "고령층 사용자는 모바일 앱 확인이 어렵고, 보호자는 실시간 출퇴근 확인을 필요로 하여 디지털 접근성이 낮은 환경에서도 안전을 보장해야 했습니다.",
      solution: "키오스크 출퇴근 이벤트를 감지하면 Spring Boot 서버의 Service 계층에서 비즈니스 로직과 알림 전송 로직을 분리하여 외부 SMS API를 비동기 호출하도록 구현했습니다.",
      result: "비즈니스 로직 팽창을 방지하고 코드의 유지보수성을 극대화했으며, 실사용 환경에서 보호자에게 신속하게 출퇴근 알림을 전송하는 시스템을 완성했습니다."
    }
  },
  {
    ...EMPTY_EXTRAS,
    num: "03",
    name: "REST API 환경 최적화를 위한 Stateless 인증 및 데이터 모델링",
    year: "2025",
    role: "Full-stack",
    stack: "Spring Boot · JPA · JWT",
    desc: "번아웃 및 우울증 환자를 돕기 위한 마이크로 태스킹(투두) 서비스의 백엔드 시스템을 설계했습니다.",
    placeholder: "Todo Management",
    tags: ["JWT", "Spring Security", "JPA", "AWS EC2"],
    details: {
      problem: "초기 세션 기반 인증은 API 서버 환경에서 확장에 한계가 있었고, 복잡한 사용자-목표 연관관계 데이터를 효율적으로 분리하고 저장해야 했습니다.",
      solution: "Controller-Service-Repository 계층형 아키텍처를 도입하고 Spring Security와 JWT를 활용한 Stateless 인증을 구현했습니다. 또한 JPA를 통해 사용자 중심의 데이터 관계를 매핑했습니다.",
      result: "코드의 가독성과 확장성을 대폭 향상시켰으며, 환경별 설정 분리를 통해 인프라 제약을 극복하고 성공적으로 운영 환경에 배포했습니다."
    }
  },
  {
    ...EMPTY_EXTRAS,
    num: "04",
    name: "복잡한 도메인 조건을 해결하는 정규화 및 추천 로직 최적화",
    year: "2025",
    role: "Backend & DB Design",
    stack: "Spring Boot · MySQL",
    desc: "다양한 보장 조건과 가입 요건을 가진 보험 데이터를 활용하여 맞춤형 보험 상품 추천 서비스 백엔드를 개발했습니다.",
    placeholder: "Data Modeling",
    tags: ["Database Design", "Normalization", "Recommendation", "JPA"],
    details: {
      problem: "보험 상품의 조건(나이, 질병 등)과 사용자 건강 상태 데이터의 구조가 복잡하여, 단일 테이블로는 데이터 중복과 추천 기준 모호성 문제가 발생했습니다.",
      solution: "데이터베이스 정규화를 통해 사용자, 보험 상품, 가입 조건 테이블을 구조적으로 분리하고, 서버 측에서 가입 여부 1차 필터링 및 조건 기반 2차 추천의 단계적 로직을 구현했습니다.",
      result: "단순한 데이터 CRUD를 넘어 데이터 구조 자체를 개선함으로써 시스템의 핵심 문제를 해결하고, 확장 가능한 맞춤형 보험 추천 백엔드를 구축했습니다."
    }
  }];

const TOP_AWARD = AWARDS.find((a) => a.tier === "gold");
const pad2 = (n) => String(n).padStart(2, '0');
const toUrl = (v) => (/^https?:\/\//.test(v) ? v : `https://${v}`);

// ============================================
// REVEAL HOOK
// ============================================
function useReveal(threshold = 0.15) {
  const ref = useRef(null);
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const obs = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            setVisible(true);
            obs.unobserve(el);
          }
        });
      },
      { threshold, rootMargin: '0px 0px -10% 0px' }
    );
    obs.observe(el);
    return () => obs.disconnect();
  }, [threshold]);

  return [ref, visible];
}

// ============================================
// SCROLL PROGRESS
// ============================================
function ScrollProgress() {
  const [pct, setPct] = useState(0);
  useEffect(() => {
    const onScroll = () => {
      const h = document.documentElement;
      const max = h.scrollHeight - h.clientHeight;
      const p = max > 0 ? h.scrollTop / max * 100 : 0;
      setPct(p);
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  return <div className="scroll-progress" style={{ width: `${pct}%` }} />;
}

// ============================================
// NAV
// ============================================
const NAV_LINKS = [
  { href: "#projects", label: "Projects" },
  { href: "#about", label: "About" },
  { href: "#awards", label: "Awards" },
  { href: "#contact", label: "Contact" }];

function Nav() {
  const [time, setTime] = useState('');
  const [open, setOpen] = useState(false);
  const toggleRef = useRef(null);
  const menuRef = useRef(null);
  // 닫힌 메뉴 안의 링크에 Tab으로 들어가지 않도록
  useEffect(() => { if (menuRef.current) menuRef.current.inert = !open; }, [open]);

  useEffect(() => {
    if (!open) return;
    const onKey = (e) => {
      if (e.key === 'Escape') { setOpen(false); toggleRef.current && toggleRef.current.focus(); }
    };
    document.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    const first = document.querySelector('.mobile-menu a');
    first && first.focus();
    return () => { document.removeEventListener('keydown', onKey); document.body.style.overflow = ''; };
  }, [open]);
  useEffect(() => {
    const update = () => {
      const d = new Date();
      const opts = { hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone: 'Asia/Seoul', hour12: false };
      setTime(d.toLocaleTimeString('en-US', opts) + ' KST');
    };
    update();
    const id = setInterval(update, 1000);
    return () => clearInterval(id);
  }, []);

  return (
    <>
      <nav className={`nav ${open ? 'menu-open' : ''}`} aria-label="주 메뉴">
        <a href="#top" className="nav-logo">{DEV_INFO.name.toUpperCase()} / {DEV_INFO.role.split(' ')[0].toUpperCase()}</a>
        <div className="nav-links">
          {NAV_LINKS.map((l) => <a key={l.href} href={l.href}>{l.label}</a>)}
        </div>
        <div className="nav-time">{time}</div>
        <button
          ref={toggleRef}
          type="button"
          className={`nav-toggle ${open ? 'is-open' : ''}`}
          aria-expanded={open}
          aria-controls="mobile-menu"
          aria-label={open ? '메뉴 닫기' : '메뉴 열기'}
          onClick={() => setOpen((o) => !o)}>
          <span /><span />
        </button>
      </nav>
      <div id="mobile-menu" className={`mobile-menu ${open ? 'is-open' : ''}`} ref={menuRef} aria-hidden={!open}>
        <ul>
          {NAV_LINKS.map((l, i) =>
            <li key={l.href} style={{ transitionDelay: open ? `${0.08 + i * 0.05}s` : '0s' }}>
              <span className="mobile-menu-num">{pad2(i + 1)}</span>
              <a href={l.href} onClick={() => setOpen(false)}>{l.label}</a>
            </li>
          )}
        </ul>
        <div className="mobile-menu-foot">
          <a href={`mailto:${DEV_INFO.email}`}>{DEV_INFO.email}</a>
          <span>{time}</span>
        </div>
      </div>
    </>);

}

// ============================================
// CURSOR
// ============================================
function CursorDot() {
  const ref = useRef(null);
  const [hover, setHover] = useState(false);
  useEffect(() => {
    let raf;
    let x = 0, y = 0, tx = 0, ty = 0;
    const move = (e) => { tx = e.clientX; ty = e.clientY; };
    const loop = () => {
      x += (tx - x) * 0.18;
      y += (ty - y) * 0.18;
      if (ref.current) {
        ref.current.style.transform = `translate(${x}px, ${y}px) translate(-50%, -50%)`;
      }
      raf = requestAnimationFrame(loop);
    };
    window.addEventListener('mousemove', move);
    raf = requestAnimationFrame(loop);

    // 이벤트 위임: 나중에 렌더된 요소(모달 버튼 등)에도 동작
    const over = (e) => setHover(!!e.target.closest('a, button, .skill-row'));
    document.addEventListener('mouseover', over);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('mousemove', move);
      document.removeEventListener('mouseover', over);
    };
  }, []);
  return <div ref={ref} className={`cursor-dot ${hover ? 'dot-large' : ''}`} aria-hidden="true" />;
}

// ============================================
// HERO — 첫 화면에 본인 정보가 모두 보이도록 구성
// ============================================
function HeroActions() {
  return (
    <div className="hero-actions">
      <a href="#projects" className="btn btn-primary">프로젝트 보기 <span aria-hidden="true">→</span></a>
      {DEV_INFO.resume &&
        <a href={DEV_INFO.resume} className="btn" target="_blank" rel="noopener noreferrer">이력서 Notion <span aria-hidden="true">↗</span></a>}
    </div>);
}

function HeroProfile() {
  const links = [
    { label: "Email", value: DEV_INFO.email, href: `mailto:${DEV_INFO.email}` },
    { label: "Telegram", value: DEV_INFO.telegram && `@${DEV_INFO.telegram}`, href: `https://t.me/${DEV_INFO.telegram}`, external: true },
    { label: "Instagram", value: DEV_INFO.instagram && `@${DEV_INFO.instagram}`, href: `https://instagram.com/${DEV_INFO.instagram}`, external: true },
    { label: "Linktree", value: DEV_INFO.linktree, href: toUrl(DEV_INFO.linktree), external: true },
    { label: "GitHub", value: DEV_INFO.github, href: toUrl(DEV_INFO.github), external: true },
    { label: "LinkedIn", value: DEV_INFO.linkedin, href: toUrl(DEV_INFO.linkedin), external: true }].
  filter((l) => l.value);
  const initials = DEV_INFO.name.split(/\s+/).map((w) => w[0]).join('').slice(0, 2).toUpperCase();

  return (
    <div className="hero-card">
      <div className="hero-card-head">
        <div className="hero-photo">
          {DEV_INFO.photo ?
            <img src={DEV_INFO.photo} alt={`${DEV_INFO.name} 프로필 사진`} /> :
            <span className="hero-photo-fallback" aria-hidden="true">{initials}</span>}
        </div>
        <div className="hero-card-text">
          {DEV_INFO.nameKo && <p className="hero-card-name">{DEV_INFO.nameKo}</p>}
          {DEV_INFO.nameEn && <p className="hero-card-name hero-card-name-en">{DEV_INFO.nameEn}</p>}
          {DEV_INFO.bio && <p className="hero-card-bio">{DEV_INFO.bio}</p>}
        </div>
      </div>
      <dl className="hero-profile">
        {links.map((c) =>
          <div key={c.label} className="hero-profile-row">
            <dt>{c.label}</dt>
            <dd><a href={c.href} target={c.external ? '_blank' : undefined} rel={c.external ? 'noopener noreferrer' : undefined}>{c.value} ↗</a></dd>
          </div>
        )}
      </dl>
    </div>);
}

function HeroStats() {
  const stats = [
    { num: pad2(AWARDS.length), label: "Awards" },
    { num: pad2(PROJECTS.length), label: "Projects" },
    TOP_AWARD && { num: pad2(AWARDS.filter((a) => a.tier === "gold").length), label: `Gold · ${TOP_AWARD.year} ${TOP_AWARD.title}` }].
  filter(Boolean);
  return (
    <ul className="hero-stats">
      {stats.map((s) =>
        <li key={s.label}>
          <span className="hero-stat-num">{s.num}</span>
          <span className="hero-stat-label">{s.label}</span>
        </li>
      )}
    </ul>);
}

function Hero({ variant }) {
  const [ref, visible] = useReveal(0.05);

  if (variant === 'c') {
    return (
      <header ref={ref} id="top" className={`hero ${visible ? 'revealed' : ''}`} data-variant="c" data-screen-label="01 Hero">
        <div className="container">
          <div className="hero-meta-top">
            <div><span className="label">Name</span><span className="value">{DEV_INFO.name}</span></div>
            <div><span className="label">Role</span><span className="value">{DEV_INFO.role}</span></div>
            <div><span className="label">Location</span><span className="value">{DEV_INFO.location}</span></div>
            <div><span className="label">Status</span><span className="value">● {DEV_INFO.status}</span></div>
          </div>
        </div>
        <div style={{ overflow: 'hidden' }}>
          <div className="hero-display hero-marquee-row" aria-label={`${DEV_INFO.name} · ${DEV_INFO.role}`}>
            <span aria-hidden="true">{DEV_INFO.name.toUpperCase()}&nbsp;·&nbsp;{DEV_INFO.role.toUpperCase()}&nbsp;·&nbsp;</span>
            <span aria-hidden="true">{DEV_INFO.name.toUpperCase()}&nbsp;·&nbsp;{DEV_INFO.role.toUpperCase()}&nbsp;·&nbsp;</span>
          </div>
        </div>
        <div className="container">
          <div className="hero-bottom">
            <p className="hero-intro">{DEV_INFO.intro}</p>
            <HeroProfile />
            <HeroActions />
          </div>
        </div>
      </header>);

  }

  if (variant === 'b') {
    return (
      <header ref={ref} id="top" className={`hero ${visible ? 'revealed' : ''}`} data-variant="b" data-screen-label="01 Hero">
        <div className="container">
          <div className="hero-meta-top">
            <div><span className="label">PORTFOLIO ’26 —</span><span className="value">{DEV_INFO.role}</span></div>
          </div>
          <h1 className="hero-display">
            <span className="line"><span className="line-inner">{DEV_INFO.name}</span></span>
            <span className="line"><span className="line-inner"><em>{DEV_INFO.role}.</em></span></span>
          </h1>
          <div className="hero-bottom">
            <p className="hero-intro">{DEV_INFO.intro}</p>
            <HeroActions />
          </div>
        </div>
      </header>);

  }

  // variant A (default) — Profile-first
  return (
    <header ref={ref} id="top" className={`hero hero-profile-first ${visible ? 'revealed' : ''}`} data-variant="a" data-screen-label="01 Hero">
      <div className="container hero-inner">

        <div className="hero-main">
          <div className="hero-identity">
            <p className="hero-kicker">안녕하세요, 저는</p>
            <h1 className="hero-display hero-name">
              <span className="line"><span className="line-inner">{DEV_INFO.name}</span></span>
              <span className="line hero-role"><span className="line-inner"><em>{DEV_INFO.role}</em></span></span>
            </h1>
            {DEV_INFO.stackFocus && <p className="hero-stack">Stack focus · {DEV_INFO.stackFocus}</p>}
            <p className="hero-intro">{DEV_INFO.intro}</p>
            <HeroActions />
          </div>

          <aside className="hero-side" aria-label="Profile">
            <HeroProfile />
          </aside>
        </div>

        <div className="hero-foot">
          <HeroStats />
          <ul className="hero-badges" aria-label="Tech stack">
            {STACK_BADGES.map((b) => <li key={b}>{b}</li>)}
          </ul>
        </div>
      </div>
    </header>);

}

// ============================================
// SECTION HEADER
// ============================================
function SectionHeader({ index, title, id }) {
  const [ref, visible] = useReveal();
  return (
    <div ref={ref} id={id} className={`section-header reveal ${visible ? 'revealed' : ''}`}>
      <div className="section-index">{index}</div>
      <h2 className="section-title">{title}</h2>
    </div>);

}

// ============================================
// ABOUT
// ============================================
function About() {
  const [ref, visible] = useReveal();
  const [i, setI] = useState(0); // 0 = Bio, 1.. = ACTIVITIES
  const n = ACTIVITIES.length + 1;
  const go = (d) => setI((i + d + n) % n);
  const [auto, setAuto] = useState(true); // 화살표를 한 번이라도 누르면 자동 넘김 중지
  const click = (d) => { setAuto(false); go(d); };
  useEffect(() => {
    if (!auto || !visible || matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const t = setInterval(() => setI((k) => (k + 1) % n), 4000);
    return () => clearInterval(t);
  }, [auto, visible, n]);
  const num = (k) => String(k).padStart(2, '0');
  return (
    <section className="section" data-screen-label="03 About">
      <div className="container">
        <SectionHeader index="(02) — About" title="About me." id="about" />
        <div ref={ref} className={`activity reveal ${visible ? 'revealed' : ''}`} aria-roledescription="carousel" aria-label="About me">
          <button className="activity-arrow" onClick={() => click(-1)} aria-label="이전">&lt;</button>
          {/* 모든 장을 같은 칸에 겹쳐 두고 현재 장만 보여서, 높이가 가장 큰 장에 고정됨 */}
          <div className="activity-stage" aria-live="polite">
            <div className={`about-content ${i === 0 ? '' : 'is-hidden'}`} aria-hidden={i !== 0}>
              <div className="section-index" style={{ marginTop: 12 }}>Bio · {num(1)} / {num(n)}</div>
              <p className="about-lead">
                서로 다른 서버를,<br />
                <em style={{ fontStyle: 'normal', fontWeight: 400 }}>하나의 안정적인 서비스로</em><br />
                묶는 백엔드 엔지니어.
              </p>
              <div className="about-body">
                <p>
                  Spring Boot 메인 서버와 FastAPI AI 서버를 분리한 멀티 서버 구조, 이벤트 기반 알림,
                  JWT 기반 Stateless 인증, 정규화된 추천 데이터 모델까지 — 시스템 전체가 부드럽게 동작하도록 설계하는 것을 좋아합니다.
                </p>
                <p>
                  학부 과정 중 6개의 경진대회에서 수상하며 빠르게 만들고, 검증하고, 다듬는 사이클을 체득했습니다.
                  지금은 견고한 백엔드 시스템과 그 위에 올라가는 사용자 경험을 함께 고민하고 있습니다.
                </p>
              </div>
            </div>
            {ACTIVITIES.map((a, k) =>
            <div key={k} className={`activity-slide ${i === k + 1 ? '' : 'is-hidden'}`} aria-hidden={i !== k + 1}>
                <div className="activity-photo">
                  {a.photo ? <img src={a.photo} alt={a.title} /> : <span>PHOTO {num(k + 1)}</span>}
                </div>
                <div className="activity-text">
                  <div className="section-index">{num(k + 2)} / {num(n)} · {a.period}</div>
                  <h3 className="activity-title">{a.title}</h3>
                  {a.lines.map((l, j) => <p key={j}>{l}</p>)}
                </div>
              </div>
            )}
          </div>
          <button className="activity-arrow" onClick={() => click(1)} aria-label="다음">&gt;</button>
        </div>
      </div>
    </section>);

}

// ============================================
// TECH STACK (기존 Core Competencies 대체)
// ============================================
// ============================================
// MARQUEE
// ============================================
function Marquee() {
  const words = ["BUILD", "SHIP", "ITERATE", "SCALE", "REFACTOR", "DEPLOY"];
  return (
    <div className="marquee-strip" aria-hidden="true">
      <div className="marquee-track">
        {[0, 1].map((rep) =>
          <span key={rep}>
            {words.map((w, i) =>
              <React.Fragment key={i}>
                {w}
                <span className="dot" />
              </React.Fragment>
            )}
          </span>
        )}
      </div>
    </div>);

}

// ============================================
// AWARDS
// ============================================
function Awards() {
  const [ref, visible] = useReveal();
  return (
    <section className="section" data-screen-label="04 Awards">
      <div className="container">
        <SectionHeader index="(03) — Awards" title="Recognition🏆." id="awards" />
        <div ref={ref} className={`awards-list reveal-stagger ${visible ? 'revealed' : ''}`}>
          {AWARDS.map((a, i) => {
            const linked = a.project && PROJECTS.find((p) => p.num === a.project);
            return (
              <div key={i} className="award-row">
                <span className="award-num">{pad2(i + 1)}</span>
                <span className="award-year">{a.year}</span>
                <span className="award-title">
                  {a.title}
                  {linked && <span className="award-project">→ Project {linked.num} · {linked.name}</span>}
                </span>
                <span className="award-prize" data-tier={a.tier}>{a.prize}</span>
              </div>);
          })}
        </div>
      </div>
    </section>);

}

// ============================================
// PROJECTS
// ============================================
const Modal = ({ project, onClose }) => {
  const closeRef = useRef(null);
  useEffect(() => {
    if (!project) return;
    const prev = document.activeElement;
    closeRef.current && closeRef.current.focus();
    const onKey = (e) => {
      if (e.key === 'Escape') { onClose(); return; }
      if (e.key !== 'Tab') return;
      // 포커스를 모달 안에 가둠
      const box = document.querySelector('.project-modal-content');
      if (!box) return;
      const items = box.querySelectorAll('a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])');
      if (!items.length) return;
      const first = items[0], last = items[items.length - 1];
      if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
      else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
      else if (!box.contains(document.activeElement)) { e.preventDefault(); first.focus(); }
    };
    document.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => {
      document.removeEventListener('keydown', onKey);
      document.body.style.overflow = '';
      prev && prev.focus && prev.focus();
    };
  }, [project]);
 
  if (!project) return null;
  const ts = project.troubleshooting;
  const hasLinks = project.github || project.demo;
  return (
    <div className="project-modal-backdrop" onClick={onClose}>
      <div className="project-modal-content" role="dialog" aria-modal="true" aria-labelledby="modal-title" onClick={(e) => e.stopPropagation()}>
        <button ref={closeRef} className="project-modal-close" onClick={onClose} aria-label="닫기">×</button>
        <div className="project-modal-header">
          <h2 id="modal-title" className="project-modal-title">{project.name}</h2>
          <div className="project-modal-meta">
            <span>{project.year}</span> · <span>{project.role}</span> · <span>{project.stack}</span>
          </div>
          {(project.team || project.myPart) &&
            <p className="project-modal-team">
              {project.team && <span>{project.team}</span>}
              {project.myPart && <span>내 담당: {project.myPart}</span>}
            </p>}
          {hasLinks &&
            <div className="project-modal-links">
              {project.demo && <a className="btn btn-primary btn-sm" href={project.demo} target="_blank" rel="noopener noreferrer">Live demo ↗</a>}
              {project.github && <a className="btn btn-sm" href={toUrl(project.github)} target="_blank" rel="noopener noreferrer">GitHub ↗</a>}
            </div>}
        </div>
        <div className="project-modal-body">
          {project.image &&
            <figure className="modal-figure">
              <img src={project.image} alt={`${project.name} 아키텍처`} />
            </figure>}
          {project.metrics.length > 0 &&
            <ul className="modal-metrics">
              {project.metrics.map((m) =>
                <li key={m.label}><span className="modal-metric-value">{m.value}</span><span className="modal-metric-label">{m.label}</span></li>
              )}
            </ul>}
          <div className="modal-section">
            <h3 className="modal-section-title">Problem</h3>
            <p className="modal-section-text">{project.details.problem}</p>
          </div>
          <div className="modal-section">
            <h3 className="modal-section-title">Solution</h3>
            <p className="modal-section-text">{project.details.solution}</p>
          </div>
          <div className="modal-section">
            <h3 className="modal-section-title">Result</h3>
            <p className="modal-section-text">{project.details.result}</p>
          </div>
          {ts &&
            <div className="modal-section">
              <h3 className="modal-section-title">Troubleshooting</h3>
              <p className="modal-section-text"><b>문제</b> {ts.issue}{'\n'}<b>원인</b> {ts.cause}{'\n'}<b>해결</b> {ts.fix}</p>
            </div>}
          {project.retro &&
            <div className="modal-section">
              <h3 className="modal-section-title">다시 한다면</h3>
              <p className="modal-section-text">{project.retro}</p>
            </div>}
        </div>
      </div>
    </div>
  );
};

function Projects({ layout }) {
  const [ref, visible] = useReveal();
  const [selectedProject, setSelectedProject] = useState(null);
  const [currentSlide, setCurrentSlide] = useState(0);

  const nextSlide = () => setCurrentSlide((p) => Math.min(p + 1, PROJECTS.length - 1));
  const prevSlide = () => setCurrentSlide((p) => Math.max(p - 1, 0));

  return (
    <section className="section" data-screen-label="02 Projects">
      <div className="container">
        <div className="projects-header-wrapper">
          <SectionHeader index="(01) — Projects" title="Selected work." id="projects" />
          {layout === 'grid' &&
            <div className="slider-nav">
              <button className="slider-btn" onClick={prevSlide} disabled={currentSlide === 0} aria-label="이전 프로젝트">←</button>
              <button className="slider-btn" onClick={nextSlide} disabled={currentSlide === PROJECTS.length - 1} aria-label="다음 프로젝트">→</button>
            </div>
          }
        </div>

        {layout === 'grid' ?
          <div ref={ref} className={`projects-slider reveal-stagger ${visible ? 'revealed' : ''}`} style={{ '--current-slide': currentSlide }}>
            <div className="projects-slider-track">
              {PROJECTS.map((p) =>
                <div key={p.num} className="project-slide">
                  <button type="button" className="project-card" onClick={() => setSelectedProject(p)}>
                    <div className="project-card-image large-thumbnail" data-placeholder={p.placeholder}>
                      {p.image && <img src={p.image} alt="" className="project-card-img" />}
                    </div>
                    <div className="project-card-num">
                      <span>{p.num} / {pad2(PROJECTS.length)}</span>
                      <span>{p.year}</span>
                    </div>
                    <h3 className="project-card-name">{p.name}</h3>
                    <p className="project-card-desc">{p.desc}</p>
                    <div className="project-card-tags">
                      {p.tags.map((t) => <span key={t} className="project-card-tag">{t}</span>)}
                    </div>
                  </button>
                </div>
              )}
            </div>
          </div> :

          <div ref={ref} className={`projects-list reveal-stagger ${visible ? 'revealed' : ''}`}>
            {PROJECTS.map((p) =>
              <button type="button" key={p.num} className="project-row" onClick={() => setSelectedProject(p)}>
                <span className="project-num">{p.num}</span>
                <span className="project-main">
                  <span className="project-name">{p.name}</span>
                  <span className="project-desc">{p.desc}</span>
                  <span className="project-tags">
                    {p.tags.map((t) => <span key={t} className="project-card-tag">{t}</span>)}
                  </span>
                </span>
                <span className="project-meta"><span className="label">Year</span>{p.year} · {p.role}</span>
                <span className="project-arrow" aria-hidden="true">→</span>
              </button>
            )}
          </div>
        }
      </div>
      <Modal project={selectedProject} onClose={() => setSelectedProject(null)} />
    </section>);

}

// ============================================
// CONTACT
// ============================================
function Contact() {
  const [ref, visible] = useReveal();
  return (
    <section ref={ref} id="contact" className={`contact ${visible ? 'revealed' : ''}`} data-screen-label="05 Contact">
      <div className="container">
        <div className="contact-label">(04) — Let's work together</div>
        <h2 className="contact-email">
          <a href={`mailto:${DEV_INFO.email}`}>{DEV_INFO.email}</a>
          <span className="underline" />
        </h2>
        <div className="contact-grid">
          {DEV_INFO.telegram &&
            <div>
              <div className="label">Telegram</div>
              <a href={`https://t.me/${DEV_INFO.telegram}`} target="_blank" rel="noopener noreferrer" className="value">@{DEV_INFO.telegram} ↗</a>
            </div>}
          <div>
            <div className="label">GitHub</div>
            <a href={toUrl(DEV_INFO.github)} target="_blank" rel="noopener noreferrer" className="value">{DEV_INFO.github} ↗</a>
          </div>
          <div>
            <div className="label">LinkedIn</div>
            <a href={toUrl(DEV_INFO.linkedin)} target="_blank" rel="noopener noreferrer" className="value">{DEV_INFO.linkedin} ↗</a>
          </div>
          <div>
            <div className="label">{DEV_INFO.resume ? 'Resume' : 'Based in'}</div>
            {DEV_INFO.resume ?
              <a href={DEV_INFO.resume} target="_blank" rel="noopener noreferrer" className="value">Notion ↗</a> :
              <span className="value">{DEV_INFO.location}</span>}
          </div>
        </div>
      </div>
    </section>);

}

// ============================================
// FOOTER
// ============================================
function Footer() {
  return (
    <footer className="footer">
      <div className="container" style={{ display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: 12 }}>
        <span>© 2026 · {DEV_INFO.name.toUpperCase()} · ALL RIGHTS RESERVED</span>
        <span>DESIGNED & DEVELOPED IN SEOUL</span>
        <span>v.02 / EDITION 2026</span>
      </div>
    </footer>);

}

// ============================================
// APP
// ============================================
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "heroVariant": "a",
  "projectsLayout": "list"
} /*EDITMODE-END*/;

function App() {
  const hasTweaks = typeof window.useTweaks === 'function';
  const [tweaks, setTweakRaw] = hasTweaks ?
    window.useTweaks(TWEAK_DEFAULTS) :
    [TWEAK_DEFAULTS, () => { }];
  const setTweak = setTweakRaw;

  useEffect(() => {
    document.title = `${DEV_INFO.name} | ${DEV_INFO.role}`;
  }, []);

  return (
    <>
      <ScrollProgress />
      <CursorDot />
      <Nav />
      <Hero variant={tweaks.heroVariant} />
      <Projects layout={tweaks.projectsLayout} />
      <About />
      <Marquee />
      <Awards />
      <Contact />
      <Footer />

      {hasTweaks && (() => {
        const TP = window.TweaksPanel;
        const TS = window.TweakSection;
        const TR = window.TweakRadio;
        return (
          <TP>
            <TS title="Hero">
              <TR
                label="Layout"
                value={tweaks.heroVariant}
                onChange={(v) => setTweak('heroVariant', v)}
                options={[
                  { value: 'a', label: 'Profile' },
                  { value: 'b', label: 'Centered' },
                  { value: 'c', label: 'Marquee' }]
                } />

            </TS>
            <TS title="Projects">
              <TR
                label="Layout"
                value={tweaks.projectsLayout}
                onChange={(v) => setTweak('projectsLayout', v)}
                options={[
                  { value: 'list', label: 'List' },
                  { value: 'grid', label: 'Grid' }]
                } />

            </TS>
          </TP>);

      })()}
    </>);

}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
