/* =============================================================================
   랩스휘트니스 석촌점 — 화면 본체  ·  디자인 컨셉: "볼드 스포트" (시안5)
   -----------------------------------------------------------------------------
   글자는 전부 src/content.js 에 있다. 이 파일은 구조와 디자인만 담당한다.

   볼드 스포트의 뼈대:
   - 다크 차콜(near-black #0B0B0D) 전면. 섹션은 ink ↔ ink2 로 리듬을 준다.
   - 일렉트릭 블루(#1F4FFF)가 메인, 밝은 블루(#4E6BFF)는 강조어·아이콘,
     애시드 그린(#C8FF3D)은 아주 작은 포인트 라벨에만.
   - Anton 초대형 타이포(영문 라벨·숫자 전용), 대각 클립 도형, 블루 스탯 스트립,
     마무리 CTA 의 거대한 고스트 글자가 시그니처.
   - 히어로는 실제 시설 사진(네온 조명)을 우측 대각 패널로 깔고 왼쪽에 카피.
   ========================================================================== */

const C = window.CONTENT;
const { useState, useEffect, useRef } = React;

/* 촬영 모드 — 주소 뒤에 ?shot=1 을 붙였을 때만 켜진다. */
const SHOT = window.location.search.indexOf("shot=1") >= 0;
const LAZY = SHOT ? "eager" : "lazy";

/* ── 공통 유틸 ─────────────────────────────────────────────────────────── */

// 스크롤해서 화면에 들어오면 한 번만 떠오르는 효과
function useReveal() {
  useEffect(() => {
    const els = document.querySelectorAll(".reveal:not(.in)");
    if (SHOT) {
      els.forEach((el) => el.classList.add("in"));
      return;
    }
    if (!("IntersectionObserver" in window)) {
      els.forEach((el) => el.classList.add("in"));
      return;
    }
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            e.target.classList.add("in");
            io.unobserve(e.target);
          }
        });
      },
      { threshold: 0.12, rootMargin: "0px 0px -8% 0px" }
    );
    els.forEach((el) => io.observe(el));
    return () => io.disconnect();
  });
}

const tel = () => "tel:" + C.footer.phone.replace(/[^0-9+]/g, "");

// 섹션 상단 라벨 — 영문 eyebrow 는 Anton(볼드 스포트 "sk" 라벨), 한글 제목은 Pretendard 볼드
function SectionHead({ eyebrow, title, desc, align = "center" }) {
  const lines = Array.isArray(title) ? title : [title];
  const a = align === "left" ? "text-left items-start" : "text-center items-center";
  return (
    <div className={"flex flex-col " + a + " reveal"}>
      {eyebrow ? (
        <p className="font-display text-[15px] sm:text-[16px] tracking-[0.14em] text-blue2 mb-3">
          {eyebrow}
        </p>
      ) : null}
      <h2 className="text-[30px] sm:text-[42px] lg:text-[48px] leading-[1.08] font-black tracking-[-0.025em] break-keep text-tx">
        {lines.map((l, i) => (
          <span key={i} className="block">{l}</span>
        ))}
      </h2>
      {desc ? (
        <p className={"mt-5 max-w-2xl text-[16.5px] sm:text-[18px] leading-[1.8] text-mut " + (align === "center" ? "mx-auto" : "")}>
          {desc}
        </p>
      ) : null}
      <div className={"mt-6 h-[3px] w-12 bg-blue " + (align === "center" ? "" : "")} />
    </div>
  );
}

/* 로고 — 헤더/푸터에서만 노출(어두운 배경이라 항상 dark 로고 사용) */
function Logo() {
  return (
    <span className="inline-flex items-center gap-2.5 leading-none">
      <img src={C.brand.logoDark} alt={C.brand.name} style={{ height: 26 }} className="w-auto" />
      <span className="font-display text-[11px] tracking-[0.18em] text-blue2 mt-0.5">석촌점</span>
    </span>
  );
}

/* ── 헤더 ──────────────────────────────────────────────────────────────── */

const NAV = [
  { id: "about", label: "소개" },
  { id: "programs", label: "프로그램" },
  { id: "facilities", label: "시설" },
  { id: "trainers", label: "트레이너" },
  { id: "pricing", label: "이용안내" },
  { id: "faq", label: "FAQ" },
  { hash: "#/column", label: "저널" },
];

function Header({ alwaysSolid }) {
  const [scrolled, setScrolled] = useState(false);
  const [open, setOpen] = useState(false);

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

  const solid = alwaysSolid || scrolled || open;

  // 칼럼 페이지에 있을 때 홈 섹션을 누르면, 홈으로 먼저 돌아간 뒤 해당 섹션으로 내려간다
  const go = (item) => {
    setOpen(false);
    if (item.hash) {
      window.location.hash = item.hash;
      return;
    }
    const id = item.id || item;
    const jump = () => {
      const el = document.getElementById(id);
      if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
    };
    if (window.location.hash && window.location.hash !== "#/") {
      window.location.hash = "";
      setTimeout(jump, 80);
    } else {
      jump();
    }
  };

  return (
    <header
      className={
        "fixed inset-x-0 top-0 z-50 transition-all duration-500 " +
        (solid ? "bg-ink/85 backdrop-blur-md border-b border-line py-3" : "py-5")
      }
    >
      <div className="mx-auto max-w-8xl px-5 sm:px-8 flex items-center justify-between gap-6">
        <button
          onClick={() => {
            if (window.location.hash) window.location.hash = "";
            window.scrollTo({ top: 0, behavior: "smooth" });
          }}
          className="text-left"
        >
          <Logo />
        </button>

        <nav className="hidden lg:flex items-center gap-8">
          {NAV.map((n) => (
            <button
              key={n.hash || n.id}
              onClick={() => go(n)}
              className="font-label text-[13px] tracking-[0.12em] uppercase text-tx/70 hover:text-white transition-colors"
            >
              {n.label}
            </button>
          ))}
          <a
            href={tel()}
            className="clip-pill bg-blue px-5 py-2.5 text-[14px] font-bold text-white hover:bg-blue2 transition-colors"
          >
            전화 {C.footer.phone}
          </a>
        </nav>

        <button
          onClick={() => setOpen((v) => !v)}
          aria-label="메뉴"
          className="lg:hidden flex flex-col gap-[5px] p-2"
        >
          <span className={"block h-px w-6 bg-white transition-transform " + (open ? "translate-y-[6px] rotate-45" : "")} />
          <span className={"block h-px w-6 bg-white transition-opacity " + (open ? "opacity-0" : "")} />
          <span className={"block h-px w-6 bg-white transition-transform " + (open ? "-translate-y-[6px] -rotate-45" : "")} />
        </button>
      </div>

      {open ? (
        <div className="lg:hidden mt-4 mx-5 bg-ink2 border border-line p-4 shadow-xl">
          {NAV.map((n) => (
            <button
              key={n.hash || n.id}
              onClick={() => go(n)}
              className="block w-full text-left px-3 py-3 text-[17px] text-tx/80 border-b border-line last:border-0"
            >
              {n.label}
            </button>
          ))}
          <a
            href={tel()}
            className="clip-pill mt-3 block w-full text-center bg-blue px-5 py-3 text-[16px] font-bold text-white"
          >
            전화 문의 {C.footer.phone}
          </a>
        </div>
      ) : null}
    </header>
  );
}

/* ── 1. 첫 화면 (실사진 대각 패널 히어로) ───────────────────────────────── */

/* 히어로 전용 모션그래픽: 캔버스로 그리는 "떠오르는 빛 입자"(EmberCanvas) + 대각선 라이트 스윕.
   실내 네온 조명 속 빛 입자가 천천히 떠오르는 느낌. 볼드 스포트에 맞춰 입자 색을
   일렉트릭 블루 계열로 맞췄다. (외부 라이브러리·유료 에셋 없음, 직접 작성) */
function EmberCanvas({ className }) {
  const canvasRef = useRef(null);
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    const colors = ["31,79,255", "78,107,255", "255,255,255"];
    let raf, w, h, dpr, particles, sparkles = [];
    let windPhase = Math.random() * Math.PI * 2;
    const mouse = { x: 0, y: 0, active: false };

    function resize() {
      dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = canvas.clientWidth;
      h = canvas.clientHeight;
      canvas.width = w * dpr;
      canvas.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    }
    // layer 0 = 멀리(작고 느리고 흐림), layer 1 = 가까이(크고 빠르고 또렷함) — 원근감
    function spawn(fromBottom) {
      const layer = Math.random() < 0.55 ? 0 : 1;
      return {
        x: Math.random() * w,
        y: fromBottom ? h + Math.random() * 40 : Math.random() * h,
        layer,
        r: (layer ? Math.random() * 2.6 + 1.2 : Math.random() * 1.3 + 0.5),
        vy: -(Math.random() * (layer ? 0.5 : 0.22) + (layer ? 0.22 : 0.1)),
        sway: Math.random() * 0.02 + 0.006,
        phase: Math.random() * Math.PI * 2,
        c: colors[Math.floor(Math.random() * colors.length)],
        life: 0,
        maxLife: Math.random() * 420 + 340,
      };
    }
    function init() {
      const count = Math.max(26, Math.min(64, Math.floor((w * h) / 20000)));
      // life를 무작위로 흩어놔야 첫 프레임부터 바로 입자가 보인다.
      particles = Array.from({ length: count }, () => {
        const p = spawn(false);
        p.life = Math.random() * p.maxLife;
        return p;
      });
      sparkles = [];
    }
    function maybeSpawnSparkle() {
      if (Math.random() > 0.02) return;
      sparkles.push({
        x: Math.random() * w,
        y: Math.random() * h * 0.85,
        r: Math.random() * 1.4 + 1.6,
        life: 0,
        maxLife: Math.random() * 40 + 34,
        c: colors[Math.floor(Math.random() * colors.length)],
      });
    }
    function drawFrame() {
      ctx.clearRect(0, 0, w, h);
      const px = mouse.active ? (mouse.x - w / 2) / w : 0;
      const py = mouse.active ? (mouse.y - h / 2) / h : 0;

      particles.forEach((p) => {
        const t = p.life / p.maxLife;
        const alpha = Math.sin(Math.min(t, 1) * Math.PI) * (p.layer ? 0.95 : 0.55);
        const parallax = p.layer ? 10 : 3;
        const dx = p.x - px * parallax;
        const dy = p.y - py * parallax;
        ctx.beginPath();
        ctx.fillStyle = "rgba(" + p.c + "," + alpha.toFixed(3) + ")";
        ctx.shadowBlur = p.layer ? 11 : 6;
        ctx.shadowColor = "rgba(" + p.c + ",0.9)";
        ctx.arc(dx, dy, p.r, 0, Math.PI * 2);
        ctx.fill();
      });

      sparkles.forEach((s) => {
        const t = s.life / s.maxLife;
        const alpha = Math.sin(Math.min(t, 1) * Math.PI);
        ctx.beginPath();
        ctx.fillStyle = "rgba(" + s.c + "," + (alpha * 0.9).toFixed(3) + ")";
        ctx.shadowBlur = 14;
        ctx.shadowColor = "rgba(" + s.c + ",1)";
        ctx.arc(s.x, s.y, s.r * (0.6 + alpha * 0.6), 0, Math.PI * 2);
        ctx.fill();
      });
      ctx.shadowBlur = 0;
    }
    function step() {
      windPhase += 0.004;
      const wind = Math.sin(windPhase) * 0.12;
      particles.forEach((p, i) => {
        p.life++;
        p.y += p.vy;
        p.x += Math.sin(p.life * p.sway + p.phase) * 0.2 + wind * (p.layer ? 1 : 0.5);
        if (p.life > p.maxLife || p.y < -10) particles[i] = spawn(true);
      });
      sparkles.forEach((s) => (s.life += 1));
      sparkles = sparkles.filter((s) => s.life < s.maxLife);
      maybeSpawnSparkle();
      drawFrame();
      raf = requestAnimationFrame(step);
    }
    function onMove(e) {
      const rect = canvas.getBoundingClientRect();
      mouse.x = e.clientX - rect.left;
      mouse.y = e.clientY - rect.top;
      mouse.active = true;
    }
    function onLeave() { mouse.active = false; }

    resize();
    init();
    if (reduceMotion) drawFrame(); else step();
    const onResize = () => { resize(); init(); };
    window.addEventListener("resize", onResize);
    canvas.addEventListener("mousemove", onMove);
    canvas.addEventListener("mouseleave", onLeave);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", onResize);
      canvas.removeEventListener("mousemove", onMove);
      canvas.removeEventListener("mouseleave", onLeave);
    };
  }, []);
  return <canvas ref={canvasRef} className={className + " pointer-events-auto"} aria-hidden="true" />;
}
function HeroParticles() {
  return (
    <div className="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
      <EmberCanvas className="absolute inset-0 h-full w-full" />
      <div className="light-sweep" />
    </div>
  );
}

/* 숫자가 화면에 들어오면 0에서 실제 값까지 세어 올라간다.
   "8·9호선"처럼 숫자가 아닌 문자가 섞여 있어도 숫자 부분만 각각 센다. */
function CountUpStat({ value, duration = 1600, startDelay = 400 }) {
  const str = String(value);
  const [display, setDisplay] = useState(() => str.replace(/\d/g, "0"));
  useEffect(() => {
    const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    const nums = [];
    str.replace(/\d+/g, (m, offset) => { nums.push({ offset, len: m.length, target: parseInt(m, 10) }); return m; });
    if (reduceMotion || nums.length === 0) { setDisplay(str); return; }
    let raf;
    const timeoutId = setTimeout(() => {
      const start = performance.now();
      const tick = (now) => {
        const t = Math.min((now - start) / duration, 1);
        const eased = 1 - Math.pow(1 - t, 3);
        let out = str;
        nums.forEach((n) => {
          const cur = Math.round(n.target * eased);
          out = out.slice(0, n.offset) + String(cur).padStart(n.len, "0") + out.slice(n.offset + n.len);
        });
        setDisplay(out);
        if (t < 1) raf = requestAnimationFrame(tick);
      };
      raf = requestAnimationFrame(tick);
    }, startDelay);
    return () => { clearTimeout(timeoutId); cancelAnimationFrame(raf); };
  }, [value]);
  return <span>{display}</span>;
}

function Hero() {
  const h = C.hero;
  const b = C.branches[0];
  const go = (id) => {
    const el = document.getElementById(id);
    if (el) el.scrollIntoView({ behavior: "smooth" });
  };
  const step = (n) => ({ animationDelay: n * 140 + "ms" });

  return (
    <section className="relative min-h-[92svh] flex items-center overflow-hidden bg-ink">
      {/* 메인 히어로 — RAPS 팀 캠페인 사진(풀블리드 배경). 왼쪽은 어둡게 눌러 카피 가독성 확보. */}
      <div className="absolute inset-0">
        <img
          src={h.image}
          alt="랩스휘트니스 석촌점 — RAPS 트레이닝 팀"
          fetchpriority="high"
          decoding="async"
          className="hero-ken h-full w-full object-cover object-center"
        />
        <div
          className="absolute inset-0"
          style={{
            background:
              "linear-gradient(90deg, rgba(11,11,13,.93) 0%, rgba(11,11,13,.5) 42%, rgba(11,11,13,0) 72%), " +
              "linear-gradient(180deg, rgba(11,11,13,.5) 0%, rgba(11,11,13,.2) 42%, rgba(11,11,13,.95) 100%)",
          }}
        />
      </div>
      <HeroParticles />
      <div className="orb float-orb bg-blue2/20" style={{ width: 420, height: 420, top: "-8%", right: "-6%" }} />
      <div className="orb float-orb bg-blue/25" style={{ width: 360, height: 360, bottom: "-10%", left: "-6%", animationDelay: "-8s" }} />

      <div className="relative z-[3] mx-auto w-full max-w-7xl px-5 sm:px-8 pt-32 pb-20">
        <div className="max-w-2xl">
          <p className="hero-in font-display text-[14px] sm:text-[15px] tracking-[0.2em] text-blue2" style={step(0.6)}>
            {h.eyebrow}
          </p>

          <h1
            className="hero-in mt-5 font-black text-white text-[34px] sm:text-[52px] lg:text-[62px] leading-[1.04] tracking-[-0.03em] break-keep whitespace-pre-line"
            style={step(1)}
          >
            {h.title}
          </h1>

          <p className="hero-in mt-6 font-thin text-[12.5px] sm:text-[14px] tracking-[0.22em] uppercase text-white/55" style={step(2)}>
            {h.subEn}
          </p>

          <p className="hero-in mt-6 text-[18px] sm:text-[21px] leading-[1.55] font-bold text-white/90 break-keep whitespace-pre-line" style={step(3)}>
            {h.desc}
          </p>

          <div className="hero-in mt-9 flex flex-wrap items-stretch gap-2.5" style={step(4.2)}>
            {b.naverBooking ? (
              <a href={b.naverBooking} target="_blank" rel="noreferrer noopener"
                className="clip-pill bg-white px-7 py-4 text-[15px] font-bold text-ink hover:bg-white/90 transition whitespace-nowrap">
                네이버 예약
              </a>
            ) : null}
            {b.kakao ? (
              <a href={b.kakao} target="_blank" rel="noreferrer noopener"
                className="clip-pill bg-white px-7 py-4 text-[15px] font-bold text-ink hover:bg-white/90 transition whitespace-nowrap">
                카카오톡 상담
              </a>
            ) : null}
            <a href={tel()}
              className="clip-pill bg-white px-7 py-4 text-[15px] font-bold text-ink hover:bg-white/90 transition-colors whitespace-nowrap">
              <svg width="17" height="17" className="inline-block -mt-0.5 mr-1.5"><use href="#icon-phone" /></svg>
              전화 문의
            </a>
            <button onClick={() => go("about")}
              className="clip-pill bg-white/10 border border-white/70 px-7 py-4 text-[15px] font-semibold text-white hover:bg-white hover:text-ink transition-colors">
              둘러보기 ↓
            </button>
          </div>
        </div>
      </div>

    </section>
  );
}

/* ── 블루 스탯 스트립 (볼드 스포트 시그니처 밴드) ───────────────────────── */

function StatStrip() {
  const stats = C.hero.stats;
  return (
    <div className="bg-blue text-white overflow-hidden">
      <div className="mx-auto max-w-8xl grid grid-cols-2 sm:grid-cols-4">
        {stats.map((s, i) => {
          const cls =
            "px-6 py-8 sm:py-10 border-white/15 " +
            (i % 2 === 0 ? "border-r " : "") +   /* 모바일: 왼쪽 열에 세로 구분선 */
            (i < 2 ? "border-b " : "") +         /* 모바일: 윗줄에 가로 구분선 */
            "sm:border-b-0 " +                   /* 데스크톱: 가로 구분선 제거 */
            (i < 3 ? "sm:border-r" : "sm:border-r-0"); /* 데스크톱: 마지막 빼고 세로 구분선 */
          return (
          <div key={i} className={cls}>
            <p className="font-display text-[44px] sm:text-[54px] leading-[0.9]">
              <CountUpStat value={s.num} /><span className="text-[18px] sm:text-[20px] font-label ml-1">{s.unit}</span>
            </p>
            <p className="mt-2.5 text-[12.5px] sm:text-[13.5px] text-white/85 leading-snug break-keep">{s.label}</p>
          </div>
          );
        })}
      </div>
    </div>
  );
}

/* ── 2. 브랜드 소개 (8년 헤리티지) ─────────────────────────────────────── */

function About() {
  const a = C.about;
  return (
    <section id="about" className="relative overflow-hidden bg-ink py-20 sm:py-28">
      <div className="orb float-orb bg-blue/[0.10]" style={{ width: 460, height: 460, top: "-14%", left: "-10%" }} />
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <div className="grid lg:grid-cols-2 gap-12 lg:gap-16 items-center">
          <div className="reveal order-2 lg:order-1">
            <p className="font-display text-[15px] tracking-[0.14em] text-blue2 mb-3">{a.eyebrow}</p>
            <h2 className="text-[30px] sm:text-[42px] leading-[1.1] font-black tracking-[-0.025em] break-keep text-tx">
              {a.title.map((l, i) => <span key={i} className="block">{l}</span>)}
            </h2>
            <p className="mt-6 text-[18px] leading-[1.8] text-tx/90 font-medium">{a.lead}</p>
            <p className="mt-4 text-[16px] leading-[1.9] text-mut">{a.body}</p>

            <div className="mt-9 grid grid-cols-2 gap-3">
              {a.points.map((p, i) => (
                <div key={i} className="relative bg-ink2 border-l-2 border-blue p-5">
                  <p className="font-bold text-tx text-[15.5px]">{p.title}</p>
                  <p className="mt-1.5 text-[13px] text-mut leading-snug">{p.desc}</p>
                </div>
              ))}
            </div>

            <p className="mt-6 text-[12.5px] text-tx/40 leading-snug">{C.footer.notice}</p>
          </div>
          <div className="reveal order-1 lg:order-2 relative aspect-[4/5] overflow-hidden clip-card">
            <img src={a.image} alt="어두운 조명의 트레이닝 공간" loading={LAZY} className="h-full w-full object-cover" />
            <div className="absolute inset-0" style={{ background: "linear-gradient(0deg, rgba(11,11,13,.5) 0%, rgba(11,11,13,0) 45%)" }} />
          </div>
        </div>
      </div>
    </section>
  );
}

/* ── 2.5. 프로그램 (실사진 풀블리드 카드) ──────────────────────────────── */

function Programs() {
  const p = C.programs;
  // RAPS 팀 캠페인 사진(업체 제공)을 좌/중/우 3등분해 카드 3개에 나눠 넣는다 —
  //   한 장의 팀 사진이 카드별로 쪼개져 이어지는 효과. (원본 3840x2160 → prog_split_1~3)
  const photos = ["img/prog_split2_1.jpg", "img/prog_split2_2.jpg", "img/prog_split2_3.jpg"];
  return (
    <section id="programs" className="bg-ink2 py-20 sm:py-28">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <SectionHead eyebrow={p.eyebrow} title={p.title} desc={p.desc} />

        <div className="mt-14 grid lg:grid-cols-3 gap-5">
          {p.list.map((item, i) => (
            <div
              key={i}
              className="reveal overflow-hidden bg-ink border border-line flex flex-col"
              style={{ animationDelay: i * 100 + "ms" }}
            >
              {/* 5인 단체사진 1/3 조각 — 전체가 잘리지 않게 사진 비율(16:27) 그대로 노출 */}
              <div className="relative aspect-[16/27] bg-ink">
                <img src={photos[i % photos.length]} alt="" loading={LAZY} className="absolute inset-0 h-full w-full object-cover" />
                <span className="absolute top-4 left-5 font-display text-[40px] text-white leading-none drop-shadow-[0_2px_8px_rgba(0,0,0,.6)]">{"0" + (i + 1)}</span>
              </div>
              <div className="p-7">
                <p className="font-display text-[12px] tracking-[0.16em] text-acid">{item.tag}</p>
                <h3 className="mt-2.5 text-[23px] font-black text-white tracking-[-0.02em]">{item.title}</h3>
                <p className="mt-3 text-[14px] leading-[1.7] text-tx/75">{item.desc}</p>
                <ul className="mt-5 space-y-2">
                  {item.points.map((pt, j) => (
                    <li key={j} className="flex items-start gap-2 text-[13px] text-tx/70">
                      <svg width="14" height="14" className="text-blue2 shrink-0 mt-[3px]"><use href="#icon-check" /></svg>
                      {pt}
                    </li>
                  ))}
                </ul>
              </div>
            </div>
          ))}
        </div>

        <div className="reveal mt-5 bg-ink border border-line clip-br p-7 sm:p-8 grid sm:grid-cols-3 gap-5 text-[14.5px] text-mut">
          {p.meta.map((m, i) => (
            <p key={i}><span className="font-semibold text-tx">{m.label}</span> — {m.value}</p>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ── 시설 갤러리 (마퀴) ─────────────────────────────────────────────── */

function Gallery() {
  const g = C.gallery;
  const row1 = g.images.filter((_, i) => i % 2 === 0);
  const row2 = g.images.filter((_, i) => i % 2 === 1);
  const loop1 = row1.concat(row1);
  const loop2 = row2.concat(row2);
  return (
    <section id="facilities" className="bg-ink2 py-20 sm:py-28 overflow-hidden">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <SectionHead eyebrow={g.eyebrow} title={g.title} desc={g.desc} />
      </div>
      <div className="mt-12 space-y-4 gallery-fade">
        <div className="relative">
          <div className="flex gap-4 w-max" style={{ animation: "marquee 48s linear infinite" }}>
            {loop1.map((src, i) => (
              <div key={i} className="w-[230px] sm:w-[300px] aspect-[4/3] overflow-hidden shrink-0 -rotate-1 shadow-[0_0_0_1px_rgba(31,79,255,.3)]">
                <img src={src} alt="" loading={LAZY} className="h-full w-full object-cover" />
              </div>
            ))}
          </div>
        </div>
        <div className="relative">
          <div className="flex gap-4 w-max" style={{ animation: "marqueeReverse 38s linear infinite" }}>
            {loop2.map((src, i) => (
              <div key={i} className="w-[150px] sm:w-[200px] aspect-[4/3] overflow-hidden shrink-0 rotate-1 shadow-[0_0_0_1px_rgba(200,255,61,.25)]">
                <img src={src} alt="" loading={LAZY} className="h-full w-full object-cover" />
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

/* ── 5. 트레이너 (3인, 실사진 + 자격·대회·경력 상세) ──────────────────── */

function Trainers() {
  const t = C.trainers;
  return (
    <section id="trainers" className="relative overflow-hidden bg-ink py-20 sm:py-28">
      <div className="orb float-orb bg-blue/[0.10]" style={{ width: 420, height: 420, bottom: "-16%", left: "-8%", animationDelay: "-6s" }} />
      <div className="mx-auto max-w-6xl px-5 sm:px-8">
        <SectionHead eyebrow={t.eyebrow} title={t.title} desc={t.desc} />
        <div className="mt-14 grid lg:grid-cols-3 gap-5">
          {t.list.map((tr, i) => (
            <div key={tr.id} className="reveal bg-ink2 border border-line p-7 flex flex-col" style={{ animationDelay: i * 120 + "ms" }}>
              {tr.image ? (
                <div className="aspect-[2/3] overflow-hidden mb-6 bg-ink3 clip-card">
                  <img src={tr.image} alt={tr.name + " 트레이너"} loading={LAZY} className="h-full w-full object-cover object-top" />
                </div>
              ) : (
                <div className="aspect-[2/3] bg-ink3 clip-card flex items-center justify-center mb-6 border border-line">
                  <p className="text-[13px] text-blue2/70 font-label tracking-wide text-center px-6 break-keep">{t.photoPlaceholder}</p>
                </div>
              )}

              <p className="font-display text-[12px] tracking-[0.16em] text-acid">{tr.role}</p>
              <p className="mt-1.5 text-[24px] font-black text-tx tracking-[-0.02em]">{tr.name}</p>

              <p className="mt-5 text-[12px] font-bold text-tx/45 tracking-wide">자격 · 이수</p>
              <ul className="mt-2 space-y-1.5">
                {tr.certs.slice(0, 5).map((c, ci) => (
                  <li key={ci} className="flex items-start gap-2 text-[14px] text-tx/75 leading-snug break-keep">
                    <svg width="14" height="14" className="text-blue2 shrink-0 mt-[3px]"><use href="#icon-check" /></svg>
                    <span>{c}</span>
                  </li>
                ))}
              </ul>
              {tr.certs.length > 5 ? (
                <p className="mt-2 pl-[22px] text-[12.5px] text-tx/40">외 {tr.certs.length - 5}개 자격 보유</p>
              ) : null}

              {tr.awards ? (
                <div className="mt-5 pt-5 border-t border-line">
                  <p className="text-[12px] font-bold text-tx/45 tracking-wide">대회 이력</p>
                  <p className="mt-2 flex items-start gap-2 text-[13.5px] text-mut leading-relaxed break-keep">
                    <svg width="14" height="14" className="text-acid shrink-0 mt-[3px]"><use href="#icon-award" /></svg>
                    <span>{tr.awards}</span>
                  </p>
                </div>
              ) : null}

              {tr.career ? (
                <div className="mt-5 pt-5 border-t border-line mt-auto">
                  <p className="text-[12px] font-bold text-tx/45 tracking-wide">경력</p>
                  <p className="mt-2 text-[13px] text-tx/55 leading-relaxed break-keep">{tr.career}</p>
                </div>
              ) : null}
            </div>
          ))}
        </div>

        <p className="reveal mt-8 text-center text-[12.5px] text-tx/40">{C.footer.notice}</p>
      </div>
    </section>
  );
}

/* ── 6. 이용 절차 ─────────────────────────────────────────────────────── */

function Process() {
  const p = C.process;
  return (
    <section className="bg-ink2 py-20 sm:py-28">
      <div className="mx-auto max-w-6xl px-5 sm:px-8">
        <SectionHead eyebrow={p.eyebrow} title={p.title} />
        <div className="mt-12 grid sm:grid-cols-4 gap-6">
          {p.steps.map((s, i) => (
            <div key={i} className="reveal border-t-2 border-blue/30 pt-5" style={{ animationDelay: i * 110 + "ms" }}>
              <p className="font-display text-[40px] text-blue2/40 leading-none">{s.no}</p>
              <p className="mt-3 text-[18px] font-bold text-tx">{s.title}</p>
              <p className="mt-2 text-[14px] text-mut leading-snug">{s.desc}</p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ── 7. 요금 안내 ─────────────────────────────────────────────────────── */

function Pricing() {
  const p = C.pricing;
  return (
    <section id="pricing" className="relative bg-ink py-20 sm:py-28 overflow-hidden">
      <div className="pointer-events-none absolute inset-0 flex items-center justify-center" aria-hidden="true">
        <span className="font-display text-[140px] sm:text-[220px] leading-none text-white/[0.03] whitespace-nowrap">MEMBERSHIP</span>
      </div>
      <div className="relative mx-auto max-w-3xl px-5 sm:px-8 text-center">
        <p className="reveal font-display text-[15px] tracking-[0.14em] text-blue2 mb-3">{p.eyebrow}</p>
        <h2 className="reveal text-[30px] sm:text-[44px] leading-[1.1] font-black tracking-[-0.025em] break-keep text-tx">
          {p.title}
        </h2>
        <p className="reveal mt-5 text-[17px] leading-[1.8] text-mut">{p.desc}</p>
        <div className="reveal mt-9 bg-ink2 border border-line clip-br p-8">
          <p className="text-[16.5px] leading-[1.9] text-tx/85">{p.note}</p>
          <a href={tel()} className="clip-pill mt-6 inline-flex items-center gap-2 bg-blue px-8 py-3.5 text-[15.5px] font-bold text-white hover:bg-blue2 transition-colors">
            <svg width="17" height="17"><use href="#icon-phone" /></svg>
            {p.cta}
          </a>
        </div>
      </div>
    </section>
  );
}

/* ── 8. FAQ ───────────────────────────────────────────────────────────── */

function Faq() {
  const f = C.faq;
  const [open, setOpen] = useState(0);
  return (
    <section id="faq" className="bg-ink2 py-20 sm:py-28 overflow-hidden">
      <div className="mx-auto max-w-8xl px-5 sm:px-8 grid lg:grid-cols-[1.4fr_1fr] gap-10 lg:gap-14 items-stretch">
        <div>
          <SectionHead eyebrow={f.eyebrow} title={f.title} align="left" />
          <div className="mt-10 divide-y divide-line border-y border-line">
            {f.items.map((it, i) => {
              const isOpen = open === i;
              return (
                <div key={i} className="reveal">
                  <button
                    onClick={() => setOpen(isOpen ? -1 : i)}
                    className="flex w-full items-start justify-between gap-6 py-6 text-left"
                  >
                    <span className="text-[18px] sm:text-[19.5px] text-tx leading-snug">{it.q}</span>
                    <svg width="22" height="22" className={"shrink-0 mt-1 text-blue2 transition-transform duration-300 " + (isOpen ? "rotate-45" : "")}>
                      <use href="#icon-plus" />
                    </svg>
                  </button>
                  {isOpen ? (
                    <p className="pb-7 pr-6 text-[16.5px] leading-[1.9] text-mut">{it.a}</p>
                  ) : null}
                </div>
              );
            })}
          </div>
        </div>
        {/* 오른쪽 RAPS 사진 — 채도↓·밝기↓로 톤을 맞추고 상하좌우를 검정으로 페이드해
            섹션 배경(#141418)과 하나로 녹아들게 한다(데스크톱 전용). */}
        <div className="relative hidden lg:block overflow-hidden min-h-[520px]">
          <img
            src="img/faq_raps.jpg"
            alt="RAPS 트레이닝웨어"
            loading={LAZY}
            className="absolute inset-0 h-full w-full object-cover object-center"
            style={{ filter: "saturate(.8) brightness(.58) contrast(1.03)" }}
          />
          <div className="absolute inset-0" style={{ background: "linear-gradient(90deg, #141418 0%, rgba(20,20,24,.92) 10%, rgba(20,20,24,.32) 46%, rgba(20,20,24,.58) 100%), linear-gradient(180deg, #141418 0%, rgba(20,20,24,.22) 15%, rgba(20,20,24,.22) 85%, #141418 100%)" }} />
        </div>
      </div>
    </section>
  );
}

/* ── 9. 마무리 CTA (거대 고스트 글자) ──────────────────────────────────── */

function CtaBand() {
  const c = C.cta;
  const b = C.branches[0];
  return (
    <section className="relative brand-gradient py-20 sm:py-28 overflow-hidden">
      <div className="pointer-events-none absolute inset-0 flex items-center justify-center" aria-hidden="true">
        <span className="font-display text-[110px] sm:text-[200px] leading-none text-white/[0.08] whitespace-nowrap">RAPS FITNESS</span>
      </div>
      <div className="relative mx-auto max-w-3xl px-5 sm:px-8 text-center">
        <h2 className="reveal text-[32px] sm:text-[48px] leading-[1.1] font-black tracking-[-0.025em] break-keep text-white">
          {c.title.map((l, i) => <span key={i} className="block">{l}</span>)}
        </h2>
        <p className="reveal mt-6 text-[17px] sm:text-[18.5px] leading-[1.8] text-white/85">{c.desc}</p>
        <div className="reveal mt-10 flex flex-wrap items-center justify-center gap-2.5">
          {b.naverBooking ? (
            <a href={b.naverBooking} target="_blank" rel="noreferrer noopener"
              className="clip-pill bg-[#03C75A] px-9 py-4 text-[16px] font-bold text-white hover:brightness-95 transition whitespace-nowrap">
              네이버 예약
            </a>
          ) : null}
          {b.kakao ? (
            <a href={b.kakao} target="_blank" rel="noreferrer noopener"
              className="clip-pill bg-[#FEE500] px-9 py-4 text-[16px] font-bold text-ink hover:brightness-95 transition whitespace-nowrap">
              카카오톡 상담
            </a>
          ) : null}
          <a href={tel()} className="clip-pill bg-white px-9 py-4 text-[16px] font-bold text-blue hover:bg-acid hover:text-ink transition-colors">
            {c.button}
          </a>
        </div>
      </div>
    </section>
  );
}

/* ── 10. 상담 · 문의 ──────────────────────────────────────────────────── */

function Booking() {
  const b = C.booking;
  const br = C.branches[0];
  return (
    <section id="booking" className="relative overflow-hidden py-20 sm:py-28">
      {/* CONTACT 배경 — RAPS 팀 사진 위에 살짝 그라데이션(텍스트 가독성 확보) */}
      <div className="absolute inset-0" aria-hidden="true">
        <img src="img/contact_bg.jpg" alt="" loading={LAZY} className="h-full w-full object-cover object-center" />
        <div className="absolute inset-0" style={{ background: "linear-gradient(180deg, rgba(11,11,13,.8) 0%, rgba(11,11,13,.7) 45%, rgba(11,11,13,.68) 62%, rgba(11,11,13,.95) 100%)" }} />
      </div>
      <div className="relative z-10 mx-auto max-w-xl px-5 sm:px-8">
        <SectionHead eyebrow={b.eyebrow} title={b.title} desc={b.desc} />

        <div className="mt-8 grid gap-2.5 sm:grid-cols-3">
          {br.naverBooking ? (
            <a href={br.naverBooking} target="_blank" rel="noreferrer noopener"
              className="reveal clip-pill flex items-center justify-center gap-2 bg-[#03C75A] px-6 py-4 text-[15.5px] font-bold text-white hover:brightness-95 transition">
              네이버 예약 →
            </a>
          ) : null}
          {br.kakao ? (
            <a href={br.kakao} target="_blank" rel="noreferrer noopener"
              className="reveal clip-pill flex items-center justify-center gap-2 bg-[#FEE500] px-6 py-4 text-[15.5px] font-bold text-ink hover:brightness-95 transition">
              카카오톡 상담 →
            </a>
          ) : null}
          <a href={tel()}
            className="reveal clip-pill flex items-center justify-center gap-2 bg-blue px-6 py-4 text-[15.5px] font-bold text-white hover:bg-blue2 transition-colors">
            <svg width="16" height="16"><use href="#icon-phone" /></svg>
            전화 문의 →
          </a>
        </div>

        {b.trust ? (
          <div className="reveal flex flex-wrap justify-center gap-2 mt-6">
            {b.trust.map((t, i) => (
              <span key={i} className="text-[12.5px] font-semibold text-tx/60 bg-ink2 border border-line rounded-full px-3 py-1.5">✓ {t}</span>
            ))}
          </div>
        ) : null}

        <div className="reveal mt-6 bg-ink2 border border-line clip-br p-8">
          <p className="font-display text-[15px] tracking-[0.14em] text-blue2">LOCATION</p>
          <p className="mt-4 text-[18px] font-semibold text-tx">{C.footer.addr}</p>
          <p className="mt-2 text-[15.5px] text-mut">{C.footer.hours}</p>
          <div className="mt-6 flex flex-wrap gap-3">
            <a
              href={"https://map.naver.com/p/search/" + encodeURIComponent(br.mapQuery)}
              target="_blank" rel="noreferrer noopener"
              className="clip-pill border border-white/25 px-6 py-3 text-[15px] text-tx/85 hover:border-blue2 hover:text-blue2 transition-colors"
            >
              네이버 지도에서 보기 →
            </a>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ── 11. 푸터 ──────────────────────────────────────────────────────────── */

function Footer() {
  const f = C.footer;
  const lines = [
    { label: "주소", value: f.addr },
    { label: "전화", value: f.phone },
    { label: "운영시간", value: f.hours },
    { label: "주차", value: f.parking },
  ];

  return (
    <footer className="bg-[#060608] text-tx/80 py-16 sm:py-20 border-t border-line">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <div className="grid md:grid-cols-2 gap-10">
          <div>
            <img src={C.brand.logoDark} alt={C.brand.name} style={{ height: 30 }} className="w-auto" />
            <p className="mt-3 font-display text-[12px] tracking-[0.18em] text-blue2/70">{f.company}</p>
            <p className="mt-6 text-[15px] leading-[1.8] text-tx/55 max-w-md">{f.notice}</p>
          </div>

          <div className="md:justify-self-end">
            <dl className="space-y-2.5">
              {lines.map((l, i) => (
                <div key={i} className="flex gap-4 text-[15.5px]">
                  <dt className="w-20 shrink-0 text-tx/45">{l.label}</dt>
                  <dd className="text-tx/85">{l.value}</dd>
                </div>
              ))}
            </dl>
          </div>
        </div>

        <div className="mt-14 pt-7 border-t border-line flex flex-col sm:flex-row justify-between gap-3">
          <p className="font-label text-[13px] tracking-wider2 text-tx/45">© 2026 RAPS FITNESS 석촌점</p>
          <button
            onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
            className="text-left font-label text-[13px] tracking-wider2 text-tx/45 hover:text-tx/80 transition-colors"
          >
            맨 위로 ↑
          </button>
        </div>
      </div>
    </footer>
  );
}

/* ── 12. 칼럼(저널) ───────────────────────────────────────────────────────
   데이터는 src/columns.js 에 있다. 칼럼봇(~/automation/raps-seokchon-column)이
   평일 아침마다 갱신한다. 이 섹션 자체는 손으로 고치지 않는다 — columns.js 만 고친다.
   ────────────────────────────────────────────────────────────────────────── */

const J = window.COLUMNS || { meta: {}, categories: [], photoPools: {}, posts: [] };

function dateNum(str) {
  const m = String(str || "").match(/(\d{4})\D(\d{1,2})\D(\d{1,2})/);
  if (!m) return 0;
  return Number(m[1]) * 10000 + Number(m[2]) * 100 + Number(m[3]);
}
function todayNum() {
  const d = new Date();
  return d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate();
}

function publishedPosts() {
  const t = todayNum();
  return (J.posts || [])
    .filter((p) => !p.draft && dateNum(p.date) <= t)
    .sort((a, b) => dateNum(b.date) - dateNum(a.date));
}

function findPost(id) {
  return (J.posts || []).find((p) => p.id === id);
}

// 저널 사진 — 글마다 겹치지 않게 전체 사진 목록에서 서로 다른 사진을 돌아가며 쓴다.
// (카테고리 풀이 3장뿐이라 같은 분류 글끼리 사진이 반복되던 문제 해결 — 전역 유니크 배정)
const JOURNAL_PHOTOS = [
  "img/헬스장전경.jpg", "img/프리웨이트존1.jpg", "img/머신존1.jpg", "img/스트레칭존1.jpg",
  "img/덤벨존.jpg", "img/프리웨이트존2.jpg", "img/머신존2.jpg", "img/스트레칭존2.jpg",
  "img/프리웨이트존3.jpg", "img/머신존3.jpg", "img/리셉션.jpg", "img/프리웨이트존4.jpg",
  "img/머신존4.jpg", "img/프리웨이트존5.jpg", "img/락커룸입구.jpg", "img/머신존5.jpg",
  "img/프리웨이트존6.jpg", "img/개인락커.jpg", "img/프리웨이트존7.jpg", "img/프리웨이트존8.jpg",
];

// 전체 글 중 몇 번째 글인지(소스 순서) — 글마다 고정된 사진을 주기 위한 기준
function postOrder(p) {
  const i = (J.posts || []).findIndex((x) => x.id === p.id);
  return i < 0 ? 0 : i;
}

// image가 "auto"면 전체 글 순서(index)로 사진을 고른다 → 글마다 서로 다른 사진이 나온다.
function postImage(p) {
  if (p.image && p.image !== "auto") return p.image;
  return JOURNAL_PHOTOS[postOrder(p) % JOURNAL_PHOTOS.length];
}

// 본문 중간 사진도 대표 사진·다른 글과 되도록 겹치지 않게 목록에서 떨어진 위치를 고른다.
function bodyPhotos(p, count = 2) {
  const hero = postImage(p);
  const base = postOrder(p);
  const out = [];
  for (let k = 1; k <= count; k++) {
    let src = JOURNAL_PHOTOS[(base + k * 7) % JOURNAL_PHOTOS.length];
    if (src === hero) src = JOURNAL_PHOTOS[(base + k * 7 + 1) % JOURNAL_PHOTOS.length];
    out.push(src);
  }
  return out;
}

function photoSlots(body, count) {
  const n = body.length;
  const heads = [];
  body.forEach((b, i) => {
    if (i > 0 && b && typeof b === "object" && b.h2) heads.push(i);
  });
  const slots = [];
  for (let k = 1; k <= count; k++) {
    const target = Math.round((n * k) / (count + 1));
    let at = heads.find((h) => h >= target && slots.indexOf(h) < 0);
    if (at === undefined) at = heads.filter((h) => slots.indexOf(h) < 0).pop();
    if (at === undefined) at = slots.indexOf(target) < 0 ? target : target + 1;
    if (at > 0 && at < n) slots.push(at);
  }
  return slots;
}

function BodyPhoto({ src }) {
  return (
    <figure className="mt-14 mb-2 -mx-5 sm:mx-0">
      <img src={src} alt="랩스휘트니스 석촌점 내부" loading={LAZY} className="w-full h-[240px] sm:h-[420px] object-cover" />
    </figure>
  );
}

function ColumnCard({ p, wide = false, noPhoto = false }) {
  return (
    <a
      href={"#/column/" + p.id}
      className="reveal group block overflow-hidden bg-ink2 border border-line transition-transform duration-500 hover:-translate-y-1"
    >
      {noPhoto ? null : (
        <div className={"overflow-hidden " + (wide ? "h-[260px]" : "h-[200px]")}>
          <img src={postImage(p)} alt={p.title} loading={LAZY} className="h-full w-full object-cover transition-transform duration-[1200ms] group-hover:scale-[1.06]" />
        </div>
      )}
      <div className="p-7">
        <p className="font-display text-[12px] tracking-[0.14em] text-acid">{p.cat}</p>
        <h3 className="mt-3 text-[19px] sm:text-[20px] font-bold text-tx leading-snug">{p.title}</h3>
        <p className="mt-3 text-[14.5px] leading-[1.8] text-mut">{p.excerpt}</p>
        <p className="mt-5 text-[13px] text-tx/40">{p.date}</p>
      </div>
    </a>
  );
}

function JournalPreview() {
  const list = publishedPosts().slice(0, 3);
  if (!list.length) return null;
  return (
    <section className="bg-ink py-20 sm:py-28">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <SectionHead eyebrow={J.meta.eyebrow} title={J.meta.title} desc={J.meta.subtitle} />
        <div className="mt-12 grid sm:grid-cols-3 gap-5">
          {list.map((p) => <ColumnCard key={p.id} p={p} />)}
        </div>
        <div className="mt-10 text-center">
          <a href="#/column" className="clip-pill inline-block border border-white/25 px-8 py-4 text-[15px] text-tx/85 hover:border-blue2 hover:text-blue2 transition-colors">
            저널 전체 보기 →
          </a>
        </div>
      </div>
    </section>
  );
}

function ColumnList() {
  const [cat, setCat] = useState("전체");
  const all = publishedPosts();
  const cats = ["전체"].concat((J.categories || []).filter((c) => all.some((p) => p.cat === c)));
  const list = cat === "전체" ? all : all.filter((p) => p.cat === cat);

  return (
    <main className="bg-ink pt-32 sm:pt-40 pb-24 sm:pb-32 min-h-[70svh]">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <SectionHead eyebrow={J.meta.eyebrow} title={J.meta.title} desc={J.meta.subtitle} />

        {all.length === 0 ? (
          <p className="mt-16 text-center text-[16px] leading-[1.9] text-mut">
            아직 발행된 글이 없습니다.
            <br />첫 글이 올라오면 이곳에 표시됩니다.
          </p>
        ) : (
          <React.Fragment>
            {cats.length > 2 ? (
              <div className="mt-12 flex flex-wrap justify-center gap-2.5">
                {cats.map((c) => (
                  <button
                    key={c}
                    onClick={() => setCat(c)}
                    className={"clip-pill px-5 py-2.5 text-[14px] transition-colors " + (c === cat ? "bg-blue text-white" : "border border-white/20 text-tx/70 hover:border-blue2 hover:text-blue2")}
                  >
                    {c}
                  </button>
                ))}
              </div>
            ) : null}

            <div className="mt-12 sm:mt-16 grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
              {list.map((p) => <ColumnCard key={p.id} p={p} />)}
            </div>
          </React.Fragment>
        )}
      </div>
    </main>
  );
}

function ColumnPost({ id }) {
  const p = findPost(id);

  if (!p || p.draft) {
    return (
      <main className="bg-ink pt-36 sm:pt-44 pb-32 min-h-[70svh]">
        <div className="mx-auto max-w-2xl px-5 text-center">
          <h1 className="text-[32px] font-black text-tx">글을 찾을 수 없습니다</h1>
          <p className="mt-5 text-[15px] text-mut">주소가 바뀌었거나 아직 공개되지 않은 글입니다.</p>
          <a href="#/column" className="clip-pill mt-9 inline-block bg-blue px-7 py-3.5 text-[15px] font-bold text-white hover:bg-blue2 transition-colors">
            저널 목록으로
          </a>
        </div>
      </main>
    );
  }

  const related = (p.related || []).map(findPost).filter((x) => x && !x.draft);
  const photos = bodyPhotos(p);
  const slots = photoSlots(p.body || [], photos.length);

  return (
    <main className="bg-ink pb-24 sm:pb-32 pt-[68px] sm:pt-[76px]">
      <div className="relative h-[46svh] min-h-[300px] overflow-hidden bg-ink">
        <img src={postImage(p)} alt={p.title} className="h-full w-full object-cover opacity-80" />
        <div className="absolute inset-0" style={{ background: "linear-gradient(to top, #0B0B0D 0%, rgba(11,11,13,.55) 45%, rgba(31,79,255,.25) 100%)" }} />
        <div className="absolute inset-x-0 bottom-0">
          <div className="mx-auto max-w-3xl px-5 sm:px-8 pb-10 sm:pb-14">
            <p className="font-display text-[12px] tracking-[0.14em] text-acid">{p.cat}</p>
            <h1 className="mt-4 text-[27px] sm:text-[38px] font-black text-white leading-[1.15] break-keep tracking-[-0.02em]">{p.title}</h1>
            <p className="mt-4 text-[13.5px] text-white/70">{p.date}{p.updated ? " · " + p.updated + " 수정" : ""}</p>
          </div>
        </div>
      </div>

      <article className="mx-auto max-w-3xl px-5 sm:px-8">
        <p className="mt-12 text-[17px] sm:text-[18.5px] leading-[1.9] text-tx/85 break-keep">{p.excerpt}</p>
        <div className="mt-8 h-[3px] w-12 bg-blue" />

        <div className="mt-10">
          {(p.body || []).map((b, i) => {
            const slot = slots.indexOf(i);
            return (
              <React.Fragment key={i}>
                {slot >= 0 && photos[slot] ? <BodyPhoto src={photos[slot]} /> : null}
                {typeof b === "string" ? (
                  <p className="mt-6 text-[16px] sm:text-[17px] leading-[2.0] text-tx/80 break-keep">{b}</p>
                ) : (
                  <h2 className="mt-14 mb-1 text-[21px] sm:text-[24px] font-bold text-tx leading-snug break-keep">{b.h2}</h2>
                )}
              </React.Fragment>
            );
          })}
        </div>

        <div className="mt-20 brand-gradient clip-br px-8 py-10 sm:px-12 sm:py-12 text-center">
          <p className="font-display text-[12px] tracking-[0.14em] text-white/80">RAPS FITNESS 석촌점</p>
          <p className="mt-5 text-[19px] sm:text-[22px] leading-[1.6] text-white break-keep font-bold">
            석촌역 앞, 8년째 이어온 랩스휘트니스.
            <br />상담만 받으셔도 됩니다.
          </p>
          <a
            href="#booking"
            onClick={() => { window.location.hash = ""; setTimeout(() => { const el = document.getElementById("booking"); if (el) el.scrollIntoView({ behavior: "smooth" }); }, 80); }}
            className="clip-pill mt-8 inline-block bg-white px-8 py-4 text-[15px] font-bold text-blue hover:bg-acid hover:text-ink transition-colors"
          >
            상담 · 문의하기
          </a>
        </div>

        {related.length ? (
          <div className="mt-20">
            <p className="font-display text-[13px] tracking-[0.14em] text-blue2">RELATED</p>
            <h2 className="mt-3 text-[22px] font-black text-tx">함께 읽으면 좋은 글</h2>
            <div className="mt-7 grid sm:grid-cols-2 gap-5">
              {related.map((r) => <ColumnCard key={r.id} p={r} noPhoto />)}
            </div>
          </div>
        ) : null}

        <div className="mt-16 text-center">
          <a href="#/column" className="clip-pill inline-block border border-white/25 px-8 py-4 text-[15px] text-tx/85 hover:border-blue2 hover:text-blue2 transition-colors">
            ← 저널 목록으로
          </a>
        </div>
      </article>
    </main>
  );
}

/* ── 화면 전환 ─────────────────────────────────────────────────────────── */

function parseHash() {
  const h = (window.location.hash || "").replace(/^#\/?/, "");
  if (h === "column") return { name: "list" };
  if (h.indexOf("column/") === 0) return { name: "post", id: h.slice("column/".length) };
  return { name: "home" };
}

/* ── 떠 있는 전화 버튼 (모바일) ────────────────────────────────────────── */

function FloatingCta() {
  return (
    <a
      href={tel()}
      className="lg:hidden fixed bottom-5 right-5 z-40 flex items-center gap-2 clip-pill bg-blue text-white px-5 py-3.5 shadow-xl shadow-blue/30"
    >
      <svg width="18" height="18"><use href="#icon-phone" /></svg>
      <span className="text-[14px] font-bold">전화 문의</span>
    </a>
  );
}

/* ── 앱 루트 ───────────────────────────────────────────────────────────── */

function App() {
  const [route, setRoute] = useState(parseHash());
  useReveal();

  useEffect(() => {
    const onHash = () => {
      setRoute(parseHash());
      window.scrollTo({ top: 0, behavior: "auto" });
    };
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, []);

  if (route.name === "list") {
    return (
      <React.Fragment>
        <Header alwaysSolid />
        <ColumnList />
        <Footer />
      </React.Fragment>
    );
  }

  if (route.name === "post") {
    return (
      <React.Fragment>
        <Header alwaysSolid />
        <ColumnPost id={route.id} />
        <Footer />
      </React.Fragment>
    );
  }

  return (
    <React.Fragment>
      <Header />
      <Hero />
      <StatStrip />
      <About />
      <Programs />
      <Gallery />
      <Trainers />
      <Process />
      <Pricing />
      <Faq />
      <CtaBand />
      <JournalPreview />
      <Booking />
      <Footer />
      <FloatingCta />
    </React.Fragment>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
