// =============================================================
// SITE SECTIONS — friendly, flowing, antigravity-style
// =============================================================

const { useState, useEffect, useRef } = React;
const pick = (obj, lang) => (obj && obj[lang]) ?? (obj && obj.en) ?? obj;

// Minimal monoline icons
function Icon({ name, size = 22, stroke = 1.5 }) {
  const p = { width: size, height: size, viewBox: "0 0 24 24", fill: "none",
    stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round" };
  switch (name) {
    case "spark": return <svg {...p}><path d="M12 3v5M12 16v5M3 12h5M16 12h5M5.6 5.6l3.5 3.5M14.9 14.9l3.5 3.5M5.6 18.4l3.5-3.5M14.9 9.1l3.5-3.5"/></svg>;
    case "plane": return <svg {...p}><path d="M3 12l18-6-3 16-5-6-10-4z"/></svg>;
    case "orbit": return <svg {...p}><circle cx="12" cy="12" r="3"/><ellipse cx="12" cy="12" rx="10" ry="4" transform="rotate(30 12 12)"/></svg>;
    case "tree":  return <svg {...p}><path d="M12 3l5 7h-3l4 6h-4l3 5H7l3-5H6l4-6H7z"/><path d="M12 21v-3"/></svg>;
    case "wave":  return <svg {...p}><path d="M2 9c2.5 0 2.5-2 5-2s2.5 2 5 2 2.5-2 5-2 2.5 2 5 2"/><path d="M2 15c2.5 0 2.5-2 5-2s2.5 2 5 2 2.5-2 5-2 2.5 2 5 2"/></svg>;
    case "cloud": return <svg {...p}><path d="M7 18h10a4 4 0 0 0 .5-7.97A6 6 0 0 0 6 11a3.5 3.5 0 0 0 1 7z"/></svg>;
    case "gear":  return <svg {...p}><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 0 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 0 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 0 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 0 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>;
    case "x":     return <svg {...p}><path d="M18 6L6 18M6 6l12 12"/></svg>;
    case "external": return <svg {...p}><path d="M7 17L17 7M7 7h10v10"/></svg>;
    case "arrow": return <svg {...p}><path d="M5 12h14M13 6l6 6-6 6"/></svg>;
    case "arrowDown": return <svg {...p}><path d="M12 5v14M6 13l6 6 6-6"/></svg>;
    case "send":  return <svg {...p}><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg>;
    default: return null;
  }
}

// ---------- Custom cursor (plane shape + pastel glow + trail) ----------
function CustomCursor() {
  const glowRef  = useRef(null);
  const planeRef = useRef(null);
  useEffect(() => {
    document.body.classList.add("custom-cursor");
    let gx = window.innerWidth / 2, gy = window.innerHeight / 2;
    let px = gx, py = gy;
    let tx = gx, ty = gy;
    let angle = 0; // current plane rotation (degrees), 0 = pointing up-right
    let lastTrail = 0;
    const palette = [
      "radial-gradient(circle, rgba(245,198,184,0.85), rgba(245,198,184,0))",
      "radial-gradient(circle, rgba(201,184,234,0.85), rgba(201,184,234,0))",
      "radial-gradient(circle, rgba(158,217,180,0.85), rgba(158,217,180,0))",
      "radial-gradient(circle, rgba(191,217,240,0.85), rgba(191,217,240,0))",
    ];
    const onMove = (e) => {
      tx = e.clientX; ty = e.clientY;
      const now = performance.now();
      if (now - lastTrail > 60) {
        lastTrail = now;
        const p = document.createElement("div");
        p.className = "cursor-trail";
        p.style.background = palette[Math.floor(Math.random() * palette.length)];
        p.style.transform = `translate3d(${tx + (Math.random()-0.5)*14}px, ${ty + (Math.random()-0.5)*14}px, 0) translate(-50%,-50%)`;
        document.body.appendChild(p);
        setTimeout(() => p.remove(), 950);
      }
    };
    const onOver = (e) => {
      const t = e.target;
      const hover = t.closest("a, button, .help__card, .app-card, .topic-chip, input, textarea, .about__card");
      if (glowRef.current)  glowRef.current.classList.toggle("is-hover",  !!hover);
      if (planeRef.current) planeRef.current.classList.toggle("is-hover", !!hover);
    };
    const onLeave = () => {
      if (glowRef.current)  glowRef.current.style.opacity  = "0";
      if (planeRef.current) planeRef.current.style.opacity = "0";
    };
    const onEnter = () => {
      if (glowRef.current)  glowRef.current.style.opacity  = "1";
      if (planeRef.current) planeRef.current.style.opacity = "1";
    };
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseover", onOver);
    document.addEventListener("mouseleave", onLeave);
    document.addEventListener("mouseenter", onEnter);
    let raf;
    const tick = () => {
      const prevPx = px, prevPy = py;
      px += (tx - px) * 0.55;
      py += (ty - py) * 0.55;
      gx += (tx - gx) * 0.14;
      gy += (ty - gy) * 0.14;
      // Rotation: plane points in direction of motion. SVG plane default points UP (angle 0 = up).
      const dx = px - prevPx, dy = py - prevPy;
      const speed = Math.hypot(dx, dy);
      if (speed > 0.3) {
        // atan2(dx, -dy) gives angle where 0 = up, clockwise positive
        const target = Math.atan2(dx, -dy) * 180 / Math.PI;
        // Shortest-path interpolation
        let diff = target - angle;
        while (diff >  180) diff -= 360;
        while (diff < -180) diff += 360;
        angle += diff * 0.2;
      }
      if (glowRef.current)  glowRef.current.style.transform  = `translate3d(${gx}px, ${gy}px, 0) translate(-50%,-50%)`;
      if (planeRef.current) planeRef.current.style.transform = `translate3d(${px}px, ${py}px, 0) translate(-50%,-50%) rotate(${angle}deg)`;
      raf = requestAnimationFrame(tick);
    };
    tick();
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseover", onOver);
      document.removeEventListener("mouseleave", onLeave);
      document.removeEventListener("mouseenter", onEnter);
      document.body.classList.remove("custom-cursor");
    };
  }, []);
  return (
    <>
      <div ref={glowRef} className="cursor-glow"/>
      <div ref={planeRef} className="cursor-plane">
        <svg viewBox="0 0 100 100" width="42" height="42">
          <defs>
            <linearGradient id="cur-body" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" stopColor="#FFFFFF"/>
              <stop offset="0.55" stopColor="#E9EFF6"/>
              <stop offset="1" stopColor="#B9CDE2"/>
            </linearGradient>
            <linearGradient id="cur-wing" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" stopColor="#D9E3EF"/>
              <stop offset="1" stopColor="#8FA3BC"/>
            </linearGradient>
          </defs>
          {/* plane pointing UP (north). Centered on (50,50). */}
          {/* wings */}
          <path d="M50 48 L14 62 L10 64 L8 60 L48 44 Z" fill="url(#cur-wing)"/>
          <path d="M50 48 L86 62 L90 64 L92 60 L52 44 Z" fill="url(#cur-wing)"/>
          {/* fuselage */}
          <path d="M48 16 Q55 16 56 24 L58 66 Q58 72 52 74 L48 74 Q42 72 42 66 L44 24 Q45 16 48 16 Z"
                fill="url(#cur-body)" stroke="#8FA3BC" strokeWidth="0.5"/>
          {/* nose */}
          <ellipse cx="50" cy="17" rx="5" ry="3" fill="#B8C7D8"/>
          {/* cockpit windows */}
          <path d="M47 20 Q50 19 53 20 L52 24 Q50 23 48 24 Z" fill="#4A6B8C" opacity="0.8"/>
          {/* cabin windows */}
          <rect x="47.5" y="30" width="5" height="28" rx="1" fill="#3D5573" opacity="0.3"/>
          {/* tail wings */}
          <path d="M50 64 L38 74 L38 77 L50 71 Z" fill="url(#cur-wing)"/>
          <path d="M50 64 L62 74 L62 77 L50 71 Z" fill="url(#cur-wing)"/>
          {/* vertical stabilizer */}
          <path d="M50 59 L49 78 L51 78 Z" fill="#B8C7D8"/>
          {/* engines */}
          <ellipse cx="28" cy="56" rx="3" ry="5" fill="#6B8099"/>
          <ellipse cx="72" cy="56" rx="3" ry="5" fill="#6B8099"/>
        </svg>
      </div>
    </>
  );
}

// ---------- Nav ----------
function Nav({ lang, setLang, t }) {
  const [scrolled, setScrolled] = useState(false);
  useEffect(() => {
    const on = () => setScrolled(window.scrollY > 40);
    window.addEventListener("scroll", on, { passive: true });
    return () => window.removeEventListener("scroll", on);
  }, []);
  return (
    <nav className={"nav" + (scrolled ? " is-scrolled" : "")}>
      <a className="nav__brand" href="#top">
        <span className="nav__mark"/>
        <span>Pilot Top Operations</span>
      </a>
      <div className="nav__links">
        <a href="#about">{t.nav_about}</a>
        <a href="#help">{t.nav_help}</a>
        <a href="#apps">{t.nav_apps}</a>
        <a href="#contact">{t.nav_contact}</a>
      </div>
      <button className="lang" onClick={() => setLang(lang === "ko" ? "en" : "ko")}>
        <span className={lang === "ko" ? "on" : ""}>KO</span>
        <span className={lang === "en" ? "on" : ""}>EN</span>
      </button>
    </nav>
  );
}

// ---------- Hero ----------
function Hero({ lang, t }) {
  const c = window.SITE_CONTENT;
  const lines = pick(c.profile.hero_lines, lang);
  return (
    <header id="top" className="hero">
      <canvas id="hero-canvas" className="hero__canvas"/>
      <div className="hero__grain"/>
      <div className="hero__inner">
        <div className="hero__greet">
          <span className="hero__greet-dot"/>
          {pick(c.profile.greeting, lang)}
        </div>
        <h1 className="hero__title">
          {lines.map((ln, i) => (
            <span key={i} className="hero__title-line" style={{ animationDelay: `${0.2 + i * 0.15}s` }}>
              {ln}
            </span>
          ))}
        </h1>
        <p className="hero__sub">{pick(c.profile.hero_sub, lang)}</p>
        <div className="hero__cta">
          <a href="#contact" className="btn btn--send">
            <span className="btn__label">{pick(c.profile.cta_primary, lang)}</span>
            <Icon name="arrow" size={16}/>
          </a>
          <a href="#apps" className="btn btn--ghost">
            {pick(c.profile.cta_secondary, lang)}
          </a>
        </div>
      </div>
      <div className="hero__scroll">
        <span>{t.scroll}</span>
        <Icon name="arrowDown" size={14}/>
      </div>
    </header>
  );
}

// ---------- About — 3D gradient ribbon ----------
function AboutBackdrop() {
  // Pure CSS/SVG soft gradient blobs — white/blue/green pastel, glassmorphic
  return (
    <div className="about__bg" aria-hidden>
      <div className="about__blob about__blob--1"/>
      <div className="about__blob about__blob--2"/>
      <div className="about__blob about__blob--3"/>
      <div className="about__blob about__blob--4"/>
    </div>
  );
}

function About({ lang }) {
  const c = window.SITE_CONTENT;
  const entries = pick(c.about, lang);
  return (
    <section id="about" className="sec sec--about">
      <AboutBackdrop/>
      <div className="sec__kicker">01 — {lang === "ko" ? "지나온 길" : "Where I've been"}</div>
      <h2 className="sec__h">
        {lang === "ko"
          ? <>한 곳에 머물지 않았어요. <em>그 과정 속에서 많은 것들을 경험하고 배워왔어요.</em></>
          : <>I've never stayed in one place — <em>along the way I've lived through a lot and learned even more.</em></>}
      </h2>

      <div className="about__cards">
        {entries.map((e, i) => (
          <AboutCard key={i} entry={e} i={i}/>
        ))}
      </div>
    </section>
  );
}

function AboutCard({ entry, i }) {
  const ref = useRef(null);
  const [tilt, setTilt] = useState({ x: 0, y: 0 });
  const onMove = (ev) => {
    const r = ref.current.getBoundingClientRect();
    setTilt({
      x: -((ev.clientY - r.top) / r.height - 0.5) * 4,
      y:  ((ev.clientX - r.left) / r.width - 0.5) * 6,
    });
  };
  return (
    <div ref={ref} className="about__card float-hover"
      onMouseMove={onMove}
      onMouseLeave={() => setTilt({ x: 0, y: 0 })}
      style={{
        transform: `perspective(1000px) rotateX(${tilt.x}deg) rotateY(${tilt.y}deg)`,
        animationDelay: `${i * 0.1}s`,
      }}>
      <div className="about__card-num">0{i+1}</div>
      <div className="about__card-body">
        <div className="about__k">{entry.k}</div>
        <div className="about__v">{entry.v}</div>
      </div>
    </div>
  );
}

// ---------- Help 3D gradient orbs ----------
function HelpOrb({ kind }) {
  // Three variations of a soft 3D glass-orb — different gradient rotations
  const variants = {
    toolbox: { rot: 135, colors: ["#FFFFFF", "#E8F2FB", "#BFD9F0", "#A8DCC4"] },
    compass: { rot: 200, colors: ["#FFFFFF", "#DCEAF8", "#9CCAEA", "#C6ECDA"] },
    bridge:  { rot: 60,  colors: ["#FFFFFF", "#EAF4EE", "#A8DCC4", "#BFD9F0"] },
  };
  const v = variants[kind] || variants.toolbox;
  const gradId = `orb-${kind}`;
  const glowId = `orb-glow-${kind}`;
  return (
    <svg viewBox="0 0 320 220" preserveAspectRatio="xMidYMid slice"
         style={{ width: "100%", height: "100%", display: "block" }}>
      <defs>
        <linearGradient id={`${gradId}-bg`} x1="0" y1="0" x2="1" y2="1">
          <stop offset="0" stopColor="#F8FBFD"/>
          <stop offset="0.5" stopColor="#EEF5FA"/>
          <stop offset="1" stopColor="#E8F4EE"/>
        </linearGradient>
        <radialGradient id={gradId} cx="0.35" cy="0.3" r="0.8">
          <stop offset="0" stopColor={v.colors[0]} stopOpacity="1"/>
          <stop offset="0.35" stopColor={v.colors[1]} stopOpacity="0.95"/>
          <stop offset="0.7" stopColor={v.colors[2]} stopOpacity="0.92"/>
          <stop offset="1" stopColor={v.colors[3]} stopOpacity="0.85"/>
        </radialGradient>
        <radialGradient id={glowId} cx="0.5" cy="0.5" r="0.5">
          <stop offset="0" stopColor={v.colors[2]} stopOpacity="0.5"/>
          <stop offset="1" stopColor={v.colors[2]} stopOpacity="0"/>
        </radialGradient>
        <radialGradient id={`${gradId}-hi`} cx="0.3" cy="0.25" r="0.35">
          <stop offset="0" stopColor="#ffffff" stopOpacity="0.95"/>
          <stop offset="1" stopColor="#ffffff" stopOpacity="0"/>
        </radialGradient>
        <filter id={`${gradId}-soft`}>
          <feGaussianBlur stdDeviation="0.4"/>
        </filter>
      </defs>

      {/* backdrop */}
      <rect width="320" height="220" fill={`url(#${gradId}-bg)`}/>

      {/* ambient glow */}
      <ellipse cx="160" cy="120" rx="140" ry="90" fill={`url(#${glowId})`}/>

      {/* soft background orbs */}
      <circle cx="50" cy="50" r="40" fill={v.colors[1]} opacity="0.35"/>
      <circle cx="280" cy="180" r="55" fill={v.colors[3]} opacity="0.3"/>
      <circle cx="260" cy="40" r="22" fill="#fff" opacity="0.45"/>

      {/* main orb — rotates gradient via transform */}
      <g transform={`rotate(${v.rot} 160 110)`}>
        <circle cx="160" cy="110" r="72" fill={`url(#${gradId})`}
                filter={`url(#${gradId}-soft)`}/>
        {/* rim highlight */}
        <circle cx="160" cy="110" r="72" fill="none"
                stroke="#ffffff" strokeWidth="1.2" opacity="0.55"/>
        {/* inner ring */}
        <circle cx="160" cy="110" r="64" fill="none"
                stroke="#ffffff" strokeWidth="0.4" opacity="0.35"/>
      </g>

      {/* specular highlight — always top-left */}
      <ellipse cx="135" cy="82" rx="30" ry="20" fill={`url(#${gradId}-hi)`}/>
      <ellipse cx="142" cy="78" rx="8" ry="5" fill="#fff" opacity="0.85"/>

      {/* drop shadow beneath orb */}
      <ellipse cx="160" cy="195" rx="58" ry="6" fill="#7FA3C4" opacity="0.18"/>
    </svg>
  );
}

// Back-compat alias; HelpCard will call this
function HelpIllust({ kind }) { return <HelpOrb kind={kind}/>; }

// ---------- Help card + section ----------
function HelpCard({ item, lang, i }) {
  const ref = useRef(null);
  const [tilt, setTilt] = useState({ x: 0, y: 0 });
  const onMove = (e) => {
    const r = ref.current.getBoundingClientRect();
    setTilt({
      x: -((e.clientY - r.top) / r.height - 0.5) * 6,
      y:  ((e.clientX - r.left) / r.width - 0.5) * 8,
    });
  };
  return (
    <article ref={ref} className="help__card float-hover"
      onMouseMove={onMove}
      onMouseLeave={() => setTilt({ x: 0, y: 0 })}
      style={{
        transform: `perspective(1000px) rotateX(${tilt.x}deg) rotateY(${tilt.y}deg)`,
        animationDelay: `${i * 0.1}s`,
      }}>
      <div className="help__illust">
        <HelpIllust kind={item.illust || (item.id === "small-biz" ? "toolbox" : item.id === "flight" ? "compass" : "bridge")}/>
      </div>
      <div className="help__body">
        <div className="help__icon"><Icon name={item.icon} size={24}/></div>
        <h3>{pick(item.title, lang)}</h3>
        <p>{pick(item.body, lang)}</p>
        <a href="#contact" className="help__cta">
          {lang === "ko" ? "이야기 나눠요" : "Let's talk"} <Icon name="arrow" size={14}/>
        </a>
      </div>
    </article>
  );
}

function Help({ lang }) {
  const c = window.SITE_CONTENT;
  const head = pick(c.help.headline, lang);
  return (
    <section id="help" className="sec sec--help">
      <div className="sec__kicker">02 — {lang === "ko" ? "제가 도울 수 있는 일" : "How I can help"}</div>
      <h2 className="sec__h">
        {head.map((l, i) => <span key={i} className="sec__h-line">{l}</span>)}
      </h2>
      <p className="sec__lede">{pick(c.help.sub, lang)}</p>
      <div className="help__grid">
        {c.help.items.map((item, i) => (
          <HelpCard key={item.id} item={item} lang={lang} i={i}/>
        ))}
      </div>
    </section>
  );
}

// ---------- Apps backdrop ----------
function AppsBackdrop() {
  return (
    <div className="apps__bg" aria-hidden>
      <div className="apps__blob apps__blob--1"/>
      <div className="apps__blob apps__blob--2"/>
      <div className="apps__blob apps__blob--3"/>
    </div>
  );
}

// Decide what kind of preview to render
function PreviewPane({ app, lang, t }) {
  if (!app) {
    return (
      <div className="preview preview--empty">
        <div className="preview__hint">
          {lang === "ko" ? "← 왼쪽에서 앱을 골라보세요" : "← Pick an app on the left"}
        </div>
      </div>
    );
  }
  const url = app.preview || app.url;
  const isImg = url && /\.(png|jpe?g|webp|gif|svg)(\?|$)/i.test(url);
  const isVid = url && /\.(mp4|webm|mov)(\?|$)/i.test(url);

  return (
    <div className="preview" style={{ "--app-accent": app.accent }}>
      <div className="preview__bar">
        <span className="preview__dots"><i/><i/><i/></span>
        <span className="preview__url">{url || t.no_url}</span>
        {app.url ? (
          <a className="preview__open" href={app.url} target="_blank" rel="noopener">
            {t.open_app} <Icon name="external" size={12}/>
          </a>
        ) : <span className="preview__open preview__open--disabled">{t.no_url}</span>}
      </div>
      <div className="preview__body">
        {!url && (
          <div className="preview__placeholder">
            <Icon name={app.icon || "spark"} size={48}/>
            <div>{pick(app.tag, lang)}</div>
          </div>
        )}
        {url && isImg && <img src={url} alt={app.name} className="preview__media"/>}
        {url && isVid && (
          <video src={url} className="preview__media" autoPlay loop muted playsInline/>
        )}
        {url && !isImg && !isVid && (
          <iframe
            key={url}
            src={url}
            className="preview__frame"
            sandbox="allow-scripts allow-same-origin"
            referrerPolicy="no-referrer"
            title={app.name}
          />
        )}
      </div>
      <div className="preview__caption">
        <h3>{app.name}</h3>
        <p>{pick(app.blurb, lang)}</p>
      </div>
    </div>
  );
}

function AppRow({ app, lang, active, onSelect }) {
  // Click-only: clicking a row only swaps the preview on the right. The only
  // way to navigate to the live site is the "Open" button in the preview
  // header. (No hover, no second-click-opens — confirmed by the user.)
  // Focus still selects so keyboard Tab navigation works.
  let host = "";
  try { if (app.url) host = new URL(app.url).hostname; } catch {}
  const favicon = host ? `https://www.google.com/s2/favicons?sz=64&domain=${host}` : "";

  return (
    <button
      type="button"
      className={"app-row" + (active ? " is-active" : "")}
      onFocus={onSelect}
      onClick={onSelect}
      style={{ "--app-accent": app.accent }}
    >
      <span className={"app-row__chip" + (favicon ? " app-row__chip--fav" : "")}
        style={favicon ? undefined : { background: app.accent }}>
        {favicon ? (
          <img src={favicon} alt="" className="app-row__fav" loading="lazy"
            onError={(e) => {
              // Favicon service failed → revert to the colored chip with no icon.
              const chip = e.currentTarget.parentElement;
              chip.classList.remove("app-row__chip--fav");
              chip.style.background = app.accent;
              e.currentTarget.style.display = "none";
            }}/>
        ) : (
          <Icon name={app.icon || "spark"} size={16}/>
        )}
      </span>
      <span className="app-row__main">
        <span className="app-row__name">{app.name}</span>
        <span className="app-row__tag">{pick(app.tag, lang)}</span>
      </span>
      <span className={"app-row__status app-row__status--" + app.status}/>
      <Icon name="external" size={14}/>
    </button>
  );
}

function Apps({ lang, t, apps }) {
  const c = window.SITE_CONTENT;
  const head = pick(c.apps_intro.headline, lang);
  const [activeId, setActiveId] = useState(apps[0]?.id);
  const active = apps.find(a => a.id === activeId);
  return (
    <section id="apps" className="sec sec--apps">
      <AppsBackdrop/>
      <div className="sec__kicker">03 — {lang === "ko" ? "만들고 있는 것들" : "What I'm building"}</div>
      <h2 className="sec__h">
        {head.map((l, i) => <span key={i} className="sec__h-line">{l}</span>)}
      </h2>
      <p className="sec__lede">{pick(c.apps_intro.sub, lang)}</p>

      <div className="apps__split">
        <div className="apps__list" role="listbox">
          {apps.map(a => (
            <AppRow
              key={a.id}
              app={a}
              lang={lang}
              active={a.id === activeId}
              onSelect={() => setActiveId(a.id)}
            />
          ))}
        </div>
        <div className="apps__preview">
          <PreviewPane app={active} lang={lang} t={t}/>
        </div>
      </div>
    </section>
  );
}

// ---------- Hobbies ----------
function Hobbies({ lang, photo }) {
  const c = window.SITE_CONTENT;
  const h = pick(c.hobbies, lang);
  // photo prop wins over content.js default. Empty -> decorative blobs.
  const photoUrl = (photo ?? c.hobbies.photo ?? "").trim();
  return (
    <section className="sec sec--hob">
      <div className="hob">
        <div className="hob__copy">
          <div className="sec__kicker">04 — {lang === "ko" ? "취미" : "Off-duty"}</div>
          <h3>{h.title}</h3>
          <p>{h.body}</p>
        </div>
        <div className="hob__viz" aria-hidden>
          {photoUrl ? (
            <img src={photoUrl} alt="" className="hob__photo"
                 referrerPolicy="no-referrer"/>
          ) : (
            <div className="hob__stage">
              <div className="hob__blob hob__blob--1"/>
              <div className="hob__blob hob__blob--2"/>
              <div className="hob__blob hob__blob--3"/>
              <div className="hob__orb">
                <div className="hob__orb-hi"/>
              </div>
              <div className="hob__arc" aria-hidden>
                <svg viewBox="0 0 500 300" preserveAspectRatio="xMidYMid meet"
                     style={{ width: "100%", height: "100%" }}>
                  <defs>
                    <linearGradient id="hob-arc" x1="0" y1="0" x2="1" y2="0">
                      <stop offset="0" stopColor="#BFD9F0" stopOpacity="0"/>
                      <stop offset="0.5" stopColor="#A8DCC4" stopOpacity="0.8"/>
                      <stop offset="1" stopColor="#BFD9F0" stopOpacity="0"/>
                    </linearGradient>
                  </defs>
                  <path d="M 30 240 Q 250 30 470 240"
                        fill="none" stroke="url(#hob-arc)" strokeWidth="2"
                        strokeDasharray="2 6" strokeLinecap="round"/>
                </svg>
              </div>
            </div>
          )}
        </div>
      </div>
    </section>
  );
}

// ---------- Contact — single funnel to email ----------
function Contact({ lang }) {
  const c = window.SITE_CONTENT;
  const head = pick(c.contact.headline, lang);
  const topics = pick(c.contact.form_topic_opts, lang);
  const prefs  = pick(c.contact.form_pref_opts,  lang);
  const [form, setForm] = useState({
    name: "", contact: "", pref: 0, kakao: "", topic: 0, msg: "",
  });
  const [sending, setSending] = useState(false);
  const [launch, setLaunch] = useState(false);

  const submit = (e) => {
    e.preventDefault();
    setSending(true);
    setLaunch(true);
    const topicLabel = topics[form.topic];
    const prefLabel  = prefs[form.pref];
    const L = (ko, en) => (lang === "ko" ? ko : en);
    const subject = encodeURIComponent(
      `[${topicLabel}] ${form.name || L("안녕하세요", "Hello")}`
    );
    const lines = [
      form.msg || L("안녕하세요, TH!", "Hi TH,"),
      "",
      "— " + L("이름",            "Name")             + ": " + (form.name    || "-"),
      "— " + L("연락처",          "Contact")          + ": " + (form.contact || "-"),
      "— " + L("선호 연락 방법", "Preferred method") + ": " + prefLabel,
      "— " + L("카카오톡 ID",   "KakaoTalk ID")     + ": " + (form.kakao   || "-"),
    ];
    const body = encodeURIComponent(lines.join("\n"));
    setTimeout(() => {
      window.location.href = `mailto:${c.contact.email}?subject=${subject}&body=${body}`;
    }, 700);
    setTimeout(() => { setSending(false); setLaunch(false); }, 1500);
  };

  return (
    <section id="contact" className="sec sec--contact">
      <div className="sec__kicker">05 — {lang === "ko" ? "연락" : "Get in touch"}</div>
      <h2 className="sec__h sec__h--center">
        {head.map((l, i) => <span key={i} className="sec__h-line">{l}</span>)}
      </h2>
      <p className="sec__lede sec__lede--center">{pick(c.contact.sub, lang)}</p>

      <form className="contact-form" onSubmit={submit}>
        <PaperPlane launching={launch}/>

        <div className="field-row">
          <div className="field">
            <label>{pick(c.contact.form_name, lang)}</label>
            <input type="text"
              value={form.name}
              onChange={(e) => setForm({ ...form, name: e.target.value })}
              placeholder={lang === "ko" ? "홍길동" : "Your name"}/>
          </div>
          <div className="field">
            <label>{pick(c.contact.form_contact, lang)}</label>
            <input type="text"
              value={form.contact}
              onChange={(e) => setForm({ ...form, contact: e.target.value })}
              placeholder={lang === "ko"
                ? "010-0000-0000 또는 you@email.com"
                : "+1 555 000 0000 or you@email.com"}/>
          </div>
        </div>

        <div className="field-row">
          <div className="field">
            <label>{pick(c.contact.form_pref, lang)}</label>
            <div className="topic-row topic-row--compact">
              {prefs.map((p, i) => (
                <button type="button" key={i}
                  className={"topic-chip" + (form.pref === i ? " on" : "")}
                  onClick={() => setForm({ ...form, pref: i })}>{p}</button>
              ))}
            </div>
          </div>
          <div className="field">
            <label>{pick(c.contact.form_kakao, lang)}</label>
            <input type="text"
              value={form.kakao}
              onChange={(e) => setForm({ ...form, kakao: e.target.value })}
              placeholder="kakao_id"/>
          </div>
        </div>

        <div className="field">
          <label>{pick(c.contact.form_topic, lang)}</label>
          <div className="topic-row">
            {topics.map((topic, i) => (
              <button type="button" key={i}
                className={"topic-chip" + (form.topic === i ? " on" : "")}
                onClick={() => setForm({ ...form, topic: i })}>{topic}</button>
            ))}
          </div>
        </div>

        <div className="field">
          <label>{pick(c.contact.form_msg, lang)}</label>
          <textarea rows="5"
            value={form.msg}
            onChange={(e) => setForm({ ...form, msg: e.target.value })}
            placeholder={lang === "ko"
              ? "편하게 적어주세요. 자세하지 않아도 괜찮아요."
              : "Whatever comes to mind. Doesn't need to be polished."}
          />
        </div>

        <button type="submit" className="btn btn--send btn--lg" disabled={sending}>
          <span className="btn__label">{pick(c.contact.form_send, lang)}</span>
          <Icon name="send" size={16}/>
        </button>
        <p className="contact-hint">{pick(c.contact.form_hint, lang)}</p>
      </form>
    </section>
  );
}

// ---------- Footer ----------
function Footer({ t }) {
  return (
    <footer className="footer">
      <span>© {new Date().getFullYear()} Pilot Top Operations</span>
      <span>·</span>
      <span>{t.footer}</span>
    </footer>
  );
}

// ---------- Admin ----------
const ADMIN_KEY = "th.admin";
const ADMIN_UNLOCK_KEY = "th.admin.unlocked";

// Browser locale → ko if Korean, else en. Korea-based users almost always
// have ko-* in navigator.language; everywhere else falls through to en.
function detectLang() {
  const langs = (navigator.languages && navigator.languages.length)
    ? navigator.languages
    : [navigator.language || ""];
  return langs.some(l => /^ko\b/i.test(l)) ? "ko" : "en";
}

// Admin is hidden by default. Unlock by visiting `?admin` (sets a localStorage
// flag so subsequent visits on the same browser keep it unlocked). Re-lock by
// visiting `?admin=lock`. The flag is per-browser; nothing about this is a
// real auth boundary — it's just a "don't show the gear to random visitors".
function checkAdminUnlock() {
  try {
    const params = new URLSearchParams(window.location.search);
    if (params.has("admin")) {
      if (params.get("admin") === "lock") {
        localStorage.removeItem(ADMIN_UNLOCK_KEY);
      } else {
        localStorage.setItem(ADMIN_UNLOCK_KEY, "1");
      }
    }
    return localStorage.getItem(ADMIN_UNLOCK_KEY) === "1";
  } catch { return false; }
}

function loadAdmin() {
  try {
    const saved = JSON.parse(localStorage.getItem(ADMIN_KEY) || "{}");
    const defaults = { ...window.SITE_CONTENT.admin_defaults, lang: detectLang() };
    return {
      ...defaults,
      ...saved,
      appsOverrides: saved.appsOverrides || {},
      appsExtra:     saved.appsExtra     || [],   // user-added apps
      appsOrder:     saved.appsOrder     || [],   // explicit ordering by id
      hobbyPhoto:    saved.hobbyPhoto    || "",   // overrides content.js hobbies.photo
    };
  } catch {
    return {
      ...window.SITE_CONTENT.admin_defaults,
      lang: detectLang(),
      appsOverrides: {}, appsExtra: [], appsOrder: [], hobbyPhoto: "",
    };
  }
}

function applyAdminToBody(a) {
  const b = document.body;
  b.dataset.accent  = a.accent;
  b.dataset.hero    = a.heroGradient;
  b.dataset.density = a.density;
  b.dataset.fontKo  = a.fontKo;
  b.dataset.fontEn  = a.fontEn;
  b.classList.toggle("no-cursor", !a.cursor);
  b.classList.toggle("reduced-motion", a.motion === "reduced");
  b.classList.toggle("no-hero3d", !a.showHero3D);
  b.classList.toggle("no-hobbies", !a.showHobbies);
}

function mergeAppsWithOverrides(admin) {
  const base   = window.SITE_CONTENT.apps || [];
  const ov     = admin?.appsOverrides || {};
  const extra  = admin?.appsExtra     || [];
  const order  = admin?.appsOrder     || [];

  // 1) base + user-added, with field overrides applied
  const merged = [...base, ...extra].map(a => ({ ...a, ...(ov[a.id] || {}) }));

  // 2) Apply explicit ordering (unknown ids fall through to original order at end)
  if (order.length === 0) return merged;
  const byId = new Map(merged.map(a => [a.id, a]));
  const out = [];
  for (const id of order) {
    if (byId.has(id)) { out.push(byId.get(id)); byId.delete(id); }
  }
  for (const a of merged) if (byId.has(a.id)) out.push(a);
  return out;
}

function AdminPanel({ admin, setAdmin, lang, t, onClose }) {
  const update = (k, v) => setAdmin({ ...admin, [k]: v });
  const reset = () => setAdmin({ ...window.SITE_CONTENT.admin_defaults, appsOverrides: {} });
  const opts = {
    fontKo:  ["gungseo", "pretendard", "inter"],
    fontEn:  ["times", "inter", "fraunces"],
    accent:  ["ocean", "sky", "forest", "sunset", "lavender"],
    heroGradient: ["sunrise", "ocean", "forest", "dawn"],
    motion:  ["full", "reduced"],
    density: ["comfy", "tight"],
    lang:    ["ko", "en"],
  };
  const labels = lang === "ko" ? {
    lang: "언어", fontKo: "한글 폰트", fontEn: "영어 폰트",
    accent: "메인 색상", heroGradient: "히어로 그라데이션",
    cursor: "커스텀 커서", motion: "모션", density: "여백",
    showHero3D: "Hero 3D 효과", showHobbies: "취미 섹션",
  } : {
    lang: "Language", fontKo: "Korean font", fontEn: "English font",
    accent: "Accent color", heroGradient: "Hero gradient",
    cursor: "Custom cursor", motion: "Motion", density: "Density",
    showHero3D: "Hero 3D scene", showHobbies: "Hobbies section",
  };

  const Row = ({ k, children }) => (
    <div className="admin__row"><label>{labels[k]}</label>{children}</div>
  );

  const baseApps  = window.SITE_CONTENT.apps || [];
  const extraApps = admin.appsExtra || [];
  const allApps   = [...baseApps, ...extraApps];
  // Effective ordering for admin's "App links" section — same logic as the
  // public list, so what you reorder here is what visitors see.
  const order     = (admin.appsOrder || []).filter(id => allApps.find(a => a.id === id));
  const orderedIds = [...order, ...allApps.map(a => a.id).filter(id => !order.includes(id))];
  const orderedApps = orderedIds.map(id => allApps.find(a => a.id === id)).filter(Boolean);

  const setAppOverride = (id, patch) => {
    const next = { ...(admin.appsOverrides || {}) };
    next[id] = { ...(next[id] || {}), ...patch };
    if (!next[id].url) delete next[id].url;
    if (!next[id].preview) delete next[id].preview;
    if (Object.keys(next[id]).length === 0) delete next[id];
    setAdmin({ ...admin, appsOverrides: next });
  };

  const moveApp = (id, dir) => {
    const ids = orderedApps.map(a => a.id);
    const i = ids.indexOf(id);
    const j = i + dir;
    if (i < 0 || j < 0 || j >= ids.length) return;
    [ids[i], ids[j]] = [ids[j], ids[i]];
    setAdmin({ ...admin, appsOrder: ids });
  };

  const [newApp, setNewApp] = useState({ name: "", url: "" });
  const addApp = () => {
    const name = newApp.name.trim();
    if (!name) return;
    const id = "custom-" + Date.now().toString(36);
    const app = {
      id, name,
      tag: { ko: name, en: name },
      status: "shipping",
      accent: "#A7C7F0",
      url: newApp.url.trim(),
      preview: "",
      blurb: { ko: "", en: "" },
    };
    setAdmin({
      ...admin,
      appsExtra: [...extraApps, app],
      appsOrder: [...orderedIds, id],
    });
    setNewApp({ name: "", url: "" });
  };

  const removeApp = (id) => {
    if (!extraApps.find(a => a.id === id)) return;  // base apps can't be removed
    const nextExtra = extraApps.filter(a => a.id !== id);
    const nextOrder = (admin.appsOrder || []).filter(x => x !== id);
    const nextOv    = { ...(admin.appsOverrides || {}) };
    delete nextOv[id];
    setAdmin({ ...admin, appsExtra: nextExtra, appsOrder: nextOrder, appsOverrides: nextOv });
  };

  return (
    <div className="admin" role="dialog" aria-modal="true">
      <div className="admin__head">
        <h3>{t.admin_title}</h3>
        <button className="admin__close" onClick={onClose} aria-label="Close">
          <Icon name="x" size={18}/>
        </button>
      </div>
      <div className="admin__body">
        <Row k="lang">
          <select value={admin.lang} onChange={e => update("lang", e.target.value)}>
            {opts.lang.map(o => <option key={o} value={o}>{o.toUpperCase()}</option>)}
          </select>
        </Row>
        <Row k="fontKo">
          <select value={admin.fontKo} onChange={e => update("fontKo", e.target.value)}>
            {opts.fontKo.map(o => <option key={o} value={o}>{o}</option>)}
          </select>
        </Row>
        <Row k="fontEn">
          <select value={admin.fontEn} onChange={e => update("fontEn", e.target.value)}>
            {opts.fontEn.map(o => <option key={o} value={o}>{o}</option>)}
          </select>
        </Row>
        <Row k="accent">
          <div className="admin__swatches">
            {opts.accent.map(o => (
              <button key={o} type="button"
                className={"admin__sw admin__sw--" + o + (admin.accent === o ? " on" : "")}
                onClick={() => update("accent", o)}
                aria-label={o}/>
            ))}
          </div>
        </Row>
        <Row k="heroGradient">
          <select value={admin.heroGradient} onChange={e => update("heroGradient", e.target.value)}>
            {opts.heroGradient.map(o => <option key={o} value={o}>{o}</option>)}
          </select>
        </Row>
        <Row k="cursor">
          <input type="checkbox" checked={!!admin.cursor}
            onChange={e => update("cursor", e.target.checked)}/>
        </Row>
        <Row k="motion">
          <select value={admin.motion} onChange={e => update("motion", e.target.value)}>
            {opts.motion.map(o => <option key={o} value={o}>{o}</option>)}
          </select>
        </Row>
        <Row k="density">
          <select value={admin.density} onChange={e => update("density", e.target.value)}>
            {opts.density.map(o => <option key={o} value={o}>{o}</option>)}
          </select>
        </Row>
        <Row k="showHero3D">
          <input type="checkbox" checked={!!admin.showHero3D}
            onChange={e => update("showHero3D", e.target.checked)}/>
        </Row>
        <Row k="showHobbies">
          <input type="checkbox" checked={!!admin.showHobbies}
            onChange={e => update("showHobbies", e.target.checked)}/>
        </Row>

        <div className="admin__photo">
          <div className="admin__apps-head">
            <span>{lang === "ko" ? "취미 섹션 사진" : "Off-duty photo"}</span>
            <span className="admin__apps-sub">
              {lang === "ko"
                ? "이미지 URL을 넣으면 04 — 취미 자리에 사진이 들어갑니다. 비우면 기본 그래픽."
                : "Image URL replaces the decorative graphic in section 04. Empty = default."}
            </span>
          </div>
          <input
            className="admin__app-input"
            value={admin.hobbyPhoto || ""}
            onChange={e => setAdmin({ ...admin, hobbyPhoto: e.target.value.trim() })}
            placeholder="https://…"
          />
        </div>

        <div className="admin__apps">
          <div className="admin__apps-head">
            <span>{lang === "ko" ? "앱 링크" : "App links"}</span>
            <span className="admin__apps-sub">
              {lang === "ko"
                ? "↑↓ 순서 · url=새 탭 · preview=미리보기 override"
                : "↑↓ reorder · url=new tab · preview=override iframe"}
            </span>
          </div>

          <div className="admin__app admin__app--add">
            <input
              className="admin__app-input"
              value={newApp.name}
              onChange={e => setNewApp({ ...newApp, name: e.target.value })}
              placeholder={lang === "ko" ? "새 앱 이름" : "New app name"}
            />
            <input
              className="admin__app-input"
              value={newApp.url}
              onChange={e => setNewApp({ ...newApp, url: e.target.value })}
              placeholder="https://…"
            />
            <button type="button" className="admin__app-add" onClick={addApp}
              disabled={!newApp.name.trim()}>
              {lang === "ko" ? "추가" : "Add"}
            </button>
          </div>

          <div className="admin__app-list">
            {orderedApps.map((app, idx) => {
              const ov = (admin.appsOverrides || {})[app.id] || {};
              const urlVal = ov.url ?? app.url ?? "";
              const prevVal = ov.preview ?? app.preview ?? "";
              const isCustom = !!extraApps.find(a => a.id === app.id);
              return (
                <div className="admin__app" key={app.id}>
                  <div className="admin__app-row">
                    <div className="admin__app-reorder">
                      <button type="button" onClick={() => moveApp(app.id, -1)}
                        disabled={idx === 0} aria-label="Move up">↑</button>
                      <button type="button" onClick={() => moveApp(app.id, +1)}
                        disabled={idx === orderedApps.length - 1} aria-label="Move down">↓</button>
                    </div>
                    <div className="admin__app-name">{app.name}{isCustom && " ·"}{isCustom && <em style={{opacity:.6,fontStyle:"normal"}}> custom</em>}</div>
                    {isCustom && (
                      <button type="button" className="admin__app-del"
                        onClick={() => removeApp(app.id)}
                        aria-label="Remove">×</button>
                    )}
                  </div>
                  <input
                    className="admin__app-input"
                    value={urlVal}
                    onChange={e => setAppOverride(app.id, { url: e.target.value.trim() })}
                    placeholder={lang === "ko"
                      ? "url (예: https://… 또는 http://localhost:…)"
                      : "url (e.g. https://… or http://localhost:…)"}
                  />
                  <input
                    className="admin__app-input"
                    value={prevVal}
                    onChange={e => setAppOverride(app.id, { preview: e.target.value.trim() })}
                    placeholder={lang === "ko"
                      ? "preview (스크린샷/영상 URL 또는 임베드 가능한 URL)"
                      : "preview (img/video url or embeddable url)"}
                  />
                </div>
              );
            })}
          </div>
        </div>

        {/* Server picker pings local LAN/Tailscale IPs over HTTP. On a real
            HTTPS host (Vercel etc.) browsers block mixed content and prompt
            for "allow insecure content". So only show it on local hosts. */}
        {/^(localhost$|127\.|192\.168\.|10\.|100\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/
          .test(window.location.hostname) && <ServerPicker lang={lang}/>}
      </div>
      <div className="admin__foot">
        <button className="btn btn--pastel-blue" onClick={onClose}>{t.admin_save}</button>
        <button className="btn btn--pastel-white" onClick={reset}>{t.admin_reset}</button>
      </div>
    </div>
  );
}

// ---------- Server picker (inside Admin) ----------
function ServerPicker({ lang }) {
  const cfg = window.SITE_CONTENT.servers;
  const [statuses, setStatuses] = useState({}); // id -> { ok, ms } | undefined
  const [busy, setBusy] = useState(false);

  const urlFor = (s) => `http://${s.ip}:${cfg.port}/`;

  async function ping(url, timeoutMs = 1800) {
    const ctrl = new AbortController();
    const timer = setTimeout(() => ctrl.abort(), timeoutMs);
    const t0 = performance.now();
    try {
      await fetch(url, { mode: "no-cors", signal: ctrl.signal, cache: "no-store" });
      return { ok: true, ms: Math.round(performance.now() - t0) };
    } catch { return { ok: false, ms: Math.round(performance.now() - t0) }; }
    finally { clearTimeout(timer); }
  }

  async function checkAll() {
    setBusy(true);
    const next = {};
    await Promise.all(cfg.list.map(async s => {
      next[s.id] = await ping(urlFor(s));
    }));
    setStatuses(next);
    setBusy(false);
  }

  function autoConnect() {
    const alive = cfg.list
      .map(s => ({ ...s, ...statuses[s.id] }))
      .filter(s => s.ok)
      .sort((a, b) => a.ms - b.ms);
    if (alive[0]) window.location.href = urlFor(alive[0]);
  }

  useEffect(() => { checkAll(); }, []);

  return (
    <div className="admin__server">
      <div className="admin__server-head">
        <span>{lang === "ko" ? "서버 (Tailscale / LAN)" : "Servers (Tailscale / LAN)"}</span>
        <button className="admin__server-refresh" onClick={checkAll} disabled={busy}>
          {busy ? "…" : "↻"}
        </button>
      </div>
      <div className="admin__server-list">
        {cfg.list.map(s => {
          const st = statuses[s.id];
          const cls = !st ? "is-check" : st.ok ? "is-up" : "is-down";
          return (
            <div className="admin__server-row" key={s.id}>
              <span className={"admin__server-dot " + cls}/>
              <div className="admin__server-main">
                <div className="admin__server-name">{s.name}</div>
                <div className="admin__server-ip">{s.ip}:{cfg.port}</div>
              </div>
              <span className="admin__server-ms">{st && st.ok ? st.ms + "ms" : ""}</span>
              <button className="admin__server-go"
                onClick={() => window.open(urlFor(s), "_blank", "noopener")}>→</button>
            </div>
          );
        })}
      </div>
      <button className="btn btn--pastel-green admin__server-auto" onClick={autoConnect}>
        ⚡ {lang === "ko" ? "가장 빠른 서버로 이동" : "Jump to fastest live server"}
      </button>
    </div>
  );
}

// ---------- Root ----------
function App() {
  const [admin, setAdmin] = useState(loadAdmin);
  const [lang, setLang] = useState(admin.lang || "ko");
  const [adminOpen, setAdminOpen] = useState(false);
  const [adminUnlocked, setAdminUnlocked] = useState(checkAdminUnlock);

  useEffect(() => {
    localStorage.setItem(ADMIN_KEY, JSON.stringify(admin));
    applyAdminToBody(admin);
    if (admin.lang !== lang) setLang(admin.lang);
  }, [admin]);

  useEffect(() => {
    setAdmin(a => a.lang === lang ? a : { ...a, lang });
    localStorage.setItem("th.lang", lang);
  }, [lang]);

  const t = window.SITE_CONTENT.ui[lang];
  const apps = mergeAppsWithOverrides(admin);

  useEffect(() => {
    const id = requestAnimationFrame(() => {
      if (admin.showHero3D && window.initHero3D) window.initHero3D();
    });
    return () => cancelAnimationFrame(id);
  }, [admin.showHero3D]);

  return (
    <>
      {admin.cursor && <CustomCursor/>}
      <Nav lang={lang} setLang={setLang} t={t}/>
      <Hero lang={lang} t={t}/>
      <About lang={lang}/>
      <Help lang={lang}/>
      <Apps lang={lang} t={t} apps={apps}/>
      {admin.showHobbies && <Hobbies lang={lang} photo={admin.hobbyPhoto}/>}
      <Contact lang={lang}/>
      <Footer t={t}/>

      {adminUnlocked && (
        <button className="admin-fab" onClick={() => setAdminOpen(true)} aria-label="Admin">
          <Icon name="gear" size={20}/>
        </button>
      )}
      {adminOpen && adminUnlocked && (
        <>
          <div className="admin-backdrop" onClick={() => setAdminOpen(false)}/>
          <AdminPanel admin={admin} setAdmin={setAdmin} lang={lang} t={t}
            onClose={() => setAdminOpen(false)}/>
        </>
      )}
    </>
  );
}

Object.assign(window, { App, Icon });

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