/* =============================================================================
   발란스온 필라테스 — 화면 본체
   -----------------------------------------------------------------------------
   글자는 전부 src/content.js 에 있다. 이 파일은 구조와 디자인만 담당한다.
   ========================================================================== */

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

/* 촬영 모드 — 주소 뒤에 ?shot=1 을 붙였을 때만 켜진다. (tools/shoot.py 가 쓴다)
   스크린샷 도구는 화면을 스크롤하지 않는다. 그래서 이 모드에서는
   ① 떠오르는 효과(.reveal)를 처음부터 다 보이게 하고
   ② 사진을 미루지 않고(lazy 해제) 한꺼번에 불러온다.
   둘 다 안 하면 화면 아래쪽이 비어 있는 채로 찍힌다. */
const SHOT = window.location.search.indexOf("shot=1") >= 0;
const LAZY = SHOT ? "eager" : "lazy";

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

// 스크롤해서 화면에 들어오면 한 번만 떠오르는 효과
//
// 주소 뒤에 ?shot=1 을 붙이면 스크롤 없이 전부 바로 보이게 한다.
// 화면을 이미지로 뜰 때(tools/shoot.py) 쓰는 촬영 모드다 — 스크린샷 도구는 스크롤을 하지
// 않아서, 이게 없으면 아래쪽 카드가 안 떠오른 채(투명한 채)로 찍힌다.
function useReveal() {
  const shotMode = window.location.search.indexOf("shot=1") >= 0;
  useEffect(() => {
    const els = document.querySelectorAll(".reveal:not(.in)");
    if (shotMode) {
      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();
  });
}

/* content.js 의 **별표 두 개** 를 굵은 글씨로 바꿔준다.
   원고를 쓰는 쪽에서 강조를 직접 정할 수 있게 하려고 둔 아주 작은 문법이다.
   줄바꿈(\n)도 그대로 살린다. */
function rich(text, strongClass) {
  const out = [];
  String(text == null ? "" : text)
    .split("\n")
    .forEach((line, li) => {
      if (li > 0) out.push(<br key={"br" + li} />);
      line.split("**").forEach((part, i) => {
        if (!part) return;
        out.push(
          i % 2
            ? <strong key={li + "-" + i} className={"font-bold " + (strongClass || "")}>{part}</strong>
            : <React.Fragment key={li + "-" + i}>{part}</React.Fragment>
        );
      });
    });
  return out;
}

// 섹션 상단 라벨 (영문 소문자 스페이싱 + 한글 제목)
function SectionHead({ eyebrow, title, desc, align = "center", light = false }) {
  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-label text-[13px] tracking-mega uppercase mb-3.5 " + (light ? "text-mist" : "text-sage")}>
          {eyebrow}
        </p>
      ) : null}
      <h2
        className={
          "text-[32px] sm:text-[43px] lg:text-[50px] leading-[1.24] font-bold tracking-[-0.015em] break-keep " +
          (light ? "text-cream" : "text-ink")
        }
      >
        {lines.map((l, i) => (
          <span key={i} className="block">{l}</span>
        ))}
      </h2>
      {desc ? (
        <p
          className={
            "mt-4 max-w-2xl text-[17.5px] sm:text-[18.5px] leading-[1.8] " +
            (light ? "text-cream/85" : "text-ink/85") +
            (align === "center" ? " mx-auto" : "")
          }
        >
          {desc}
        </p>
      ) : null}
      <div className={"mt-6 h-px w-14 " + (light ? "bg-mist/50" : "bg-sage/50")} />
    </div>
  );
}

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

const NAV = [
  { id: "about", label: "소개" },
  { id: "strengths", label: "10가지 강점" },
  { id: "programs", label: "수업" },
  { id: "space", label: "공간·기구" },
  { id: "teachers", label: "강사진" },
  { id: "guide", label: "이용안내" },
  { 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 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 " +
        (alwaysSolid || scrolled ? "bg-cream/90 backdrop-blur-md border-b border-ink/10 py-3" : "py-6")
      }
    >
      <div className="mx-auto max-w-8xl px-5 sm:px-8 flex items-center justify-between gap-6">
        <button onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })} className="text-left leading-none">
          <span className="block font-display text-[19px] sm:text-[21px] tracking-wider2 text-ink">
            BALANCE ON PILATES
          </span>
          <span className="block font-label text-[11px] tracking-mega text-sage mt-1">발란스온 필라테스</span>
        </button>

        <nav className="hidden lg:flex items-center gap-8">
          {NAV.map((n) => (
            <button
              key={n.label}
              onClick={() => go(n)}
              className="text-[15.5px] text-ink/80 hover:text-sageDk transition-colors"
            >
              {n.label}
            </button>
          ))}
          <button
            onClick={() => go({ id: "booking" })}
            className="rounded-full bg-sageDk px-5 py-2.5 text-[15px] text-cream hover:bg-ink transition-colors"
          >
            체험 신청
          </button>
        </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-ink transition-transform " + (open ? "translate-y-[6px] rotate-45" : "")} />
          <span className={"block h-px w-6 bg-ink transition-opacity " + (open ? "opacity-0" : "")} />
          <span className={"block h-px w-6 bg-ink transition-transform " + (open ? "-translate-y-[6px] -rotate-45" : "")} />
        </button>
      </div>

      {open ? (
        <div className="lg:hidden mt-4 mx-5 rounded-2xl bg-ivory border border-ink/10 p-4 shadow-lg">
          {NAV.map((n) => (
            <button
              key={n.label}
              onClick={() => go(n)}
              className="block w-full text-left px-3 py-3 text-[17px] text-ink/80 border-b border-ink/5 last:border-0"
            >
              {n.label}
            </button>
          ))}
          <button
            onClick={() => go({ id: "booking" })}
            className="mt-3 w-full rounded-full bg-sageDk px-5 py-3 text-[16px] text-cream"
          >
            체험 신청
          </button>
        </div>
      ) : null}
    </header>
  );
}

/* ── 1. 첫 화면 + 겹쳐 올라오는 세로 카드 ─────────────────────────────── */

function Hero() {
  const h = C.hero;
  const imgRef = useRef(null);
  const go = (id) => {
    const el = document.getElementById(id);
    if (el) el.scrollIntoView({ behavior: "smooth" });
  };

  // 스크롤에 따라 배경 사진만 조금 느리게 따라온다 (얕은 패럴랙스).
  // rAF 로 묶어서 스크롤 이벤트마다 레이아웃을 건드리지 않게 한다.
  useEffect(() => {
    const el = imgRef.current;
    if (!el) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    let ticking = false;
    const apply = () => {
      const y = Math.min(window.scrollY, window.innerHeight);
      el.style.transform = "translate3d(0," + y * 0.16 + "px,0)";
      ticking = false;
    };
    const onScroll = () => {
      if (!ticking) { ticking = true; requestAnimationFrame(apply); }
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  const step = (n) => ({ animationDelay: n * 140 + "ms" });

  return (
    <section className="relative min-h-[88svh] flex items-center overflow-hidden bg-cream">
      {/* 배경 사진 — 천천히 확대/축소를 반복하고, 스크롤하면 살짝 뒤따라온다 */}
      <div ref={imgRef} className="absolute inset-0 will-change-transform">
        <img src={h.image} alt="" aria-hidden="true" fetchpriority="high" decoding="async" className="h-full w-full object-cover kenburns scale-105" />
      </div>
      <div className="absolute inset-0 hero-tint" />
      <div className="absolute inset-0 hero-wash" />
      <div className="orb float-orb bg-mist/45" style={{ width: 460, height: 460, top: "6%", left: "12%" }} />
      <div
        className="orb float-orb bg-sageLt/20"
        style={{ width: 340, height: 340, bottom: "14%", right: "10%", animationDelay: "-8s" }}
      />

      {/* 가운데 정렬 — 로랑처럼 라벨 → 큰 제목 → 두 줄 → 한 줄 설명 순서 */}
      <div className="relative mx-auto w-full max-w-5xl px-5 sm:px-8 pt-28 pb-48 sm:pb-56 text-center">
        <p className="hero-in font-label text-[12.5px] sm:text-[13.5px] tracking-mega uppercase text-sageDk" style={step(0)}>
          {h.eyebrow}
        </p>

        <h1
          className="hero-in mt-7 font-semibold text-ink text-[30px] sm:text-[44px] lg:text-[52px] leading-[1.3] tracking-[-0.01em] break-keep"
          style={step(1)}
        >
          {h.headline}
        </h1>

        {/* 설명 두 줄 — 사진 위에서도 읽히도록 크기와 두께를 한 단계씩 올렸다 */}
        <div className="mt-7">
          {h.sub.map((line, i) => (
            <p
              key={i}
              className="hero-in text-[21px] sm:text-[26px] leading-[1.6] font-medium text-ink/95 break-keep"
              style={step(2 + i * 0.4)}
            >
              {line}
            </p>
          ))}
        </div>

        <p
          className="hero-in mt-5 text-[17.5px] sm:text-[19.5px] leading-[1.75] font-medium text-ink/80 break-keep"
          style={step(3.2)}
        >
          {h.desc}
        </p>

        <div
          className="hero-in mt-9 flex flex-col sm:flex-row items-stretch sm:items-center justify-center gap-3 max-w-[380px] sm:max-w-none mx-auto"
          style={step(3.9)}
        >
          <button
            onClick={() => go("booking")}
            className="rounded-full bg-sageDk px-8 py-4 text-[16px] text-cream hover:bg-ink transition-colors"
          >
            {h.ctaPrimary}
          </button>
          <button
            onClick={() => go("about")}
            className="rounded-full border border-ink/25 bg-cream/40 px-8 py-4 text-[16px] text-ink/85 hover:border-sageDk hover:text-sageDk transition-colors"
          >
            {h.ctaSecondary} →
          </button>
        </div>
      </div>
    </section>
  );
}

/* 첫 화면 사진 위로 절반쯤 걸쳐 올라오는 세로 카드 3장 */
function Pillars() {
  const go = (id) => {
    const el = document.getElementById(id);
    if (el) el.scrollIntoView({ behavior: "smooth" });
  };
  return (
    <section className="relative z-10 -mt-40 sm:-mt-48 pb-16 sm:pb-24 bg-transparent">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <div className="grid sm:grid-cols-3 gap-5 sm:gap-6">
          {C.pillars.map((p, i) => (
            <button
              key={i}
              onClick={() => go(p.to)}
              className="reveal group relative overflow-hidden rounded-[26px] aspect-[1/1] sm:aspect-[3/4] text-left shadow-[0_24px_60px_-24px_rgba(51,55,46,.45)] transition-transform duration-500 hover:-translate-y-2"
              style={{ animationDelay: i * 110 + "ms" }}
            >
              {/* 사진은 원본보다 살짝 밝게 — 센터가 실제보다 어두워 보이지 않게 한다 */}
              <img
                src={p.image}
                alt={p.title}
                loading={LAZY}
                className="absolute inset-0 h-full w-full object-cover brightness-[1.04] transition-transform duration-[1200ms] group-hover:scale-[1.07]"
              />
              {/* 어둡게 까는 층은 글자가 앉는 아래쪽에만. 사진 윗부분은 손대지 않는다. */}
              <div
                className="absolute inset-x-0 bottom-0 h-[74%]"
                style={{
                  background:
                    "linear-gradient(to top, rgba(24,27,21,.94) 0%, rgba(24,27,21,.88) 26%, rgba(24,27,21,.66) 52%, rgba(24,27,21,.26) 78%, rgba(24,27,21,0) 100%)",
                }}
              />
              <div className="absolute inset-x-0 bottom-0 p-6 sm:p-8">
                <p className="font-label text-[12.5px] font-medium tracking-mega uppercase text-cream/95">{p.eyebrow}</p>
                <h3 className="mt-2.5 text-[29px] sm:text-[32px] font-bold text-cream leading-tight">{p.title}</h3>
                <p className="mt-2.5 text-[16px] leading-[1.65] text-cream/90">{p.desc}</p>
                <div className="mt-4 flex flex-wrap gap-2">
                  {p.tags.map((t, j) => (
                    <span
                      key={j}
                      className="rounded-full border border-cream/40 px-3 py-1.5 text-[12.5px] text-cream/90"
                    >
                      {t}
                    </span>
                  ))}
                </div>
              </div>
            </button>
          ))}
        </div>

        {/* 숫자 네 개 — 카드 아래로 내려 첫 화면을 비워둔다 */}
        <div className="reveal mt-12 sm:mt-16 grid grid-cols-2 lg:grid-cols-4 gap-x-6 gap-y-8 border-t border-ink/10 pt-9">
          {C.hero.stats.map((s, i) => (
            <div key={i} className="text-center sm:text-left">
              <p className="font-display text-[36px] sm:text-[44px] leading-none text-sageDk">
                {s.num}
                <span className="text-[17px] sm:text-[19.5px] ml-1 text-ink/60">{s.unit}</span>
              </p>
              <p className="mt-2.5 text-[14px] sm:text-[15px] text-ink/70">{s.label}</p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ── 2. 핵심 3블록 ─────────────────────────────────────────────────────── */

function KeyPoints() {
  const k = C.keyPoints;
  return (
    <section id="about" className="relative bg-ivory bg-grain py-16 sm:py-24">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <SectionHead title={k.title} desc={k.sub} />
        {/* 숫자를 제목 옆에 붙여 카드 안의 빈 공간을 없앴다 (예전엔 위아래로 벌려 놓아 허전했다) */}
        <div className="mt-10 sm:mt-14 grid md:grid-cols-3 gap-4 sm:gap-5">
          {k.items.map((it, i) => (
            <div
              key={i}
              className={
                "reveal rounded-[24px] p-7 sm:p-8 transition-transform duration-500 hover:-translate-y-1 " +
                (it.accent ? "bg-sage text-cream" : "bg-cream border border-ink/10 text-ink")
              }
              style={{ animationDelay: i * 90 + "ms" }}
            >
              <div className="flex items-baseline gap-3.5">
                <span
                  className={
                    "font-display text-[30px] leading-none shrink-0 " + (it.accent ? "text-cream/60" : "text-sage/55")
                  }
                >
                  {it.mark}
                </span>
                <h3 className="text-[22px] sm:text-[24px] font-bold leading-snug break-keep">{it.title}</h3>
              </div>
              <p className={"mt-3.5 text-[16.5px] leading-[1.75] " + (it.accent ? "text-cream/90" : "text-ink/75")}>
                {rich(it.desc, it.accent ? "text-cream" : "text-ink")}
              </p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ── 3. 브랜드 선언문 ──────────────────────────────────────────────────── */

function Manifesto() {
  const m = C.manifesto;
  return (
    <section className="relative bg-cream py-16 sm:py-24 overflow-hidden">
      <div className="orb bg-mist/60" style={{ width: 460, height: 460, top: -120, right: -140 }} />
      <div className="relative mx-auto max-w-8xl px-5 sm:px-8 grid lg:grid-cols-12 gap-12 lg:gap-16 items-center">
        <div className="lg:col-span-6 reveal">
          <p className="font-label text-[13px] tracking-mega uppercase text-sage mb-6">{m.eyebrow}</p>
          <h2 className="text-[33px] sm:text-[44px] lg:text-[50px] leading-[1.24] font-bold tracking-[-0.015em] break-keep text-ink">
            {m.title.map((l, i) => (
              <span key={i} className="block">{l}</span>
            ))}
          </h2>
          <div className="mt-8 h-px w-14 bg-sage/50" />
          <p className="mt-8 text-[18px] sm:text-[19.5px] leading-[1.9] text-ink/85">{m.lead}</p>
          <p className="mt-5 text-[17px] leading-[1.9] text-ink/70">{m.body}</p>
        </div>
        <div className="lg:col-span-6 reveal">
          <div className="relative">
            <div className="absolute -inset-3 sm:-inset-5 rounded-[30px] border border-sage/25" />
            <img
              src={m.image}
              alt="발란스온 프라이빗 개인레슨실"
              loading={LAZY}
              className="relative rounded-[24px] w-full h-[320px] sm:h-[460px] object-cover"
            />
          </div>
        </div>
      </div>
    </section>
  );
}

/* ── 4. 10가지 강점 ────────────────────────────────────────────────────── */

/* 예전엔 짙은 초록 바탕에 열 개를 똑같이 늘어놓아, 작은 흰 글씨가 모바일에서 잘 안 읽혔다.
   지금은 밝은 바탕으로 바꾸고 핵심(key:true)을 위쪽 큰 카드로, 나머지는 아래 목록으로 나눈다. */
function Strengths() {
  const s = C.strengths;
  const keyItems = s.items.filter((it) => it.key);
  const restItems = s.items.filter((it) => !it.key);

  return (
    <section id="strengths" className="relative bg-cream bg-grain py-16 sm:py-24">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <SectionHead eyebrow={s.eyebrow} title={s.title} desc={s.sub} />

        {/* 핵심 — 먼저 눈에 들어와야 하는 것들 */}
        <div className="mt-10 sm:mt-14 grid md:grid-cols-2 gap-4 sm:gap-5">
          {keyItems.map((it, i) => (
            <article
              key={it.no}
              className="reveal rounded-[22px] bg-ivory border border-ink/10 p-6 sm:p-8 transition-transform duration-500 hover:-translate-y-1"
              style={{ animationDelay: (i % 2) * 80 + "ms" }}
            >
              <div className="flex items-baseline gap-3.5">
                <span className="font-display text-[27px] sm:text-[30px] leading-none text-wood shrink-0">
                  {it.no}
                </span>
                <h3 className="text-[21px] sm:text-[24px] font-bold text-ink leading-snug break-keep">{it.title}</h3>
              </div>
              <p className="mt-3.5 text-[17px] sm:text-[17.5px] leading-[1.75] text-ink/75 break-keep">
                {rich(it.desc, "text-ink")}
              </p>
            </article>
          ))}
        </div>

        {/* 나머지 — 한 줄씩 짧게 */}
        {restItems.length ? (
          <div className="reveal mt-4 sm:mt-5 rounded-[22px] bg-sand/70 border border-wood/20 px-6 sm:px-8 py-2 divide-y divide-wood/20">
            {restItems.map((it) => (
              <div key={it.no} className="flex items-baseline gap-3.5 py-5">
                <span className="font-display text-[19px] leading-none text-wood/80 shrink-0 pt-[3px]">{it.no}</span>
                <div>
                  <h3 className="text-[18px] sm:text-[19px] font-bold text-ink leading-snug break-keep">{it.title}</h3>
                  <p className="mt-1.5 text-[16px] leading-[1.7] text-ink/70 break-keep">{it.desc}</p>
                </div>
              </div>
            ))}
          </div>
        ) : null}
      </div>
    </section>
  );
}

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

function Gallery() {
  const g = C.gallery;
  const row = g.images.concat(g.images); // 끊김 없이 흐르도록 2배 복제
  return (
    <section className="bg-cream py-16 sm:py-24 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-10 sm:mt-14 relative">
        <div className="flex gap-4 w-max" style={{ animation: "marquee 52s linear infinite" }}>
          {row.map((src, i) => (
            <div key={i} className="w-[260px] sm:w-[360px] shrink-0">
              <img
                src={src}
                alt=""
                aria-hidden="true"
                loading={LAZY}
                className="h-[190px] sm:h-[250px] w-full object-cover rounded-2xl"
              />
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ── 6. 공감 체크리스트 ────────────────────────────────────────────────── */

function PainPoints() {
  const p = C.painPoints;
  return (
    <section className="bg-ivory py-16 sm:py-24">
      <div className="mx-auto max-w-4xl px-5 sm:px-8 text-center">
        <SectionHead title={p.title} />
        <ul className="mt-9 space-y-3 text-left">
          {p.items.map((t, i) => (
            <li
              key={i}
              className="reveal flex items-start gap-4 rounded-2xl bg-cream border border-ink/10 px-6 py-5"
              style={{ animationDelay: i * 70 + "ms" }}
            >
              <span className="mt-[7px] h-1.5 w-1.5 rounded-full bg-sage shrink-0" />
              <span className="text-[17px] sm:text-[18px] leading-[1.7] text-ink/80">{t}</span>
            </li>
          ))}
        </ul>
        <p className="reveal mt-10 text-[17px] sm:text-[19.5px] leading-[1.85] text-sageDk">{p.outro}</p>
      </div>
    </section>
  );
}

/* ── 7. 프로그램 ───────────────────────────────────────────────────────── */

function Programs() {
  const p = C.programs;
  return (
    <section id="programs" className="bg-cream py-16 sm:py-24">
      <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-10 sm:mt-14 grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
          {p.list.map((it, i) => (
            <article
              key={i}
              className="reveal group rounded-[24px] overflow-hidden bg-ivory border border-ink/10 flex flex-col"
              style={{ animationDelay: (i % 3) * 80 + "ms" }}
            >
              <div className="relative h-[210px] overflow-hidden">
                <img
                  src={it.image}
                  alt={it.name}
                  loading={LAZY}
                  className="h-full w-full object-cover transition-transform duration-700 group-hover:scale-[1.06]"
                />
                <span className="absolute left-4 top-4 rounded-full bg-cream/90 px-3 py-1 font-label text-[12px] tracking-wider2 text-sageDk">
                  {it.tag}
                </span>
              </div>
              <div className="p-7 flex-1 flex flex-col">
                <h3 className="text-[21.5px] font-semibold text-ink">{it.name}</h3>
                <p className="mt-3 text-[16px] leading-[1.8] text-ink/70 flex-1">{it.desc}</p>
                <ul className="mt-6 space-y-2 border-t border-ink/10 pt-5">
                  {it.points.map((pt, j) => (
                    <li key={j} className="flex items-start gap-2.5 text-[15px] text-ink/80">
                      <span className="mt-[7px] h-1 w-1 rounded-full bg-sage shrink-0" />
                      {pt}
                    </li>
                  ))}
                </ul>
              </div>
            </article>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ── 8. 공간 & 기구 ────────────────────────────────────────────────────── */

function Equipment() {
  const e = C.equipment;
  return (
    <section id="space" className="bg-ivory bg-grain py-16 sm:py-24">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <SectionHead eyebrow={e.eyebrow} title={e.title} desc={e.desc} />

        {/* 대형 기구 */}
        <div className="mt-10 sm:mt-14 grid sm:grid-cols-2 lg:grid-cols-4 gap-4">
          {e.machines.map((m, i) => (
            <div
              key={i}
              className="reveal rounded-2xl bg-cream border border-ink/10 px-7 py-8"
              style={{ animationDelay: i * 70 + "ms" }}
            >
              <p className="font-thin text-[23px] font-medium leading-tight text-ink">{m.name}</p>
              <p className="mt-2.5 font-label text-[13px] tracking-wider2 text-sage">{m.brand}</p>
            </div>
          ))}
        </div>

        {/* 소도구 */}
        <div className="reveal mt-6 rounded-2xl bg-sageDk px-7 py-7 sm:px-9">
          <p className="font-label text-[13px] tracking-mega uppercase text-mist/70">PROPS</p>
          <div className="mt-4 flex flex-wrap gap-2.5">
            {e.props.map((p, i) => (
              <span key={i} className="rounded-full border border-cream/25 px-4 py-2 text-[15px] text-cream/90">
                {p}
              </span>
            ))}
          </div>
        </div>

        {/* 공간 */}
        <div className="mt-6 grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
          {e.spaces.map((s, i) => (
            <figure
              key={i}
              className="reveal group relative overflow-hidden rounded-[22px] h-[240px]"
              style={{ animationDelay: (i % 3) * 80 + "ms" }}
            >
              <img
                src={s.image}
                alt={s.name}
                loading={LAZY}
                className="h-full w-full object-cover transition-transform duration-700 group-hover:scale-[1.06]"
              />
              <div
                className="absolute inset-0"
                style={{
                  background:
                    "linear-gradient(to top, rgba(28,31,25,.92) 0%, rgba(28,31,25,.62) 34%, rgba(28,31,25,.22) 68%, rgba(28,31,25,.04) 100%)",
                }}
              />
              <figcaption className="absolute inset-x-0 bottom-0 p-6">
                <p className="text-[19.5px] font-semibold text-cream">{s.name}</p>
                <p className="mt-1.5 text-[15px] text-cream/80">{s.desc}</p>
              </figcaption>
            </figure>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ── 9. 강사진 ─────────────────────────────────────────────────────────── */

/* 강사 카드 사진.
   원본(tr-*.jpg, 1080×1350)은 이름·자격이 통째로 인쇄된 카드라 글씨가 읽히지 않는다.
   그래서 인물 사진이 들어있는 액자 안쪽(가로 553~1016 / 세로 73~692)만 잘라서 쓴다.
   액자 테두리(갈색 선)까지 들어오지 않도록 원본 좌표에서 몇 px 안쪽을 잡았다.
   숫자는 그 좌표를 3:4 상자에 맞춘 값이다 — 사진 원본이 바뀌면 여기도 다시 맞춰야 한다. */
function TrainerPhoto({ src, alt }) {
  return (
    <div className="relative aspect-[3/4] overflow-hidden rounded-[16px] bg-ivory">
      <img
        src={src}
        alt={alt}
        loading={LAZY}
        className="absolute max-w-none"
        style={{ width: "233.3%", left: "-119.5%", top: "-11.8%" }}
      />
    </div>
  );
}

function Teachers() {
  const t = C.trainers;
  const [openId, setOpenId] = useState(null);

  return (
    <section id="teachers" className="bg-cream py-16 sm:py-24">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <SectionHead eyebrow={t.eyebrow} title={t.title} desc={t.desc} />

        {/* 첫 화면엔 사진 + 이름 + 대표 자격 3~4개만. 전체 경력은 「약력 +」로 펼친다. */}
        <div className="mt-10 sm:mt-14 grid md:grid-cols-2 gap-4 sm:gap-5">
          {t.list.map((p, i) => {
            const isOpen = openId === p.id;
            const hasCerts = p.certs && p.certs.length > 0;
            const top = p.top && p.top.length ? p.top : (p.certs || []).slice(0, 4);
            return (
              <article
                key={p.id}
                className="reveal rounded-[20px] bg-ivory border border-ink/10 p-4 sm:p-5"
                style={{ animationDelay: (i % 2) * 70 + "ms" }}
              >
                <div className="flex gap-4 sm:gap-5">
                  <div className="w-[116px] sm:w-[136px] shrink-0">
                    <TrainerPhoto src={p.image} alt={p.name + " " + p.role} />
                  </div>

                  <div className="min-w-0 flex-1">
                    <p className="flex items-baseline gap-2">
                      <span className="text-[21px] sm:text-[23px] font-bold text-ink">{p.name}</span>
                      <span className="text-[15px] text-sage">{p.role}</span>
                    </p>

                    <ul className="mt-3 space-y-[7px]">
                      {top.map((c, j) => (
                        <li key={j} className="flex items-start gap-2.5 text-[15.5px] leading-[1.55] text-ink/80 break-keep">
                          <span className="mt-[9px] h-1 w-1 rounded-full bg-sage shrink-0" />
                          <span>{c}</span>
                        </li>
                      ))}
                    </ul>

                    {hasCerts ? (
                      <button
                        onClick={() => setOpenId(isOpen ? null : p.id)}
                        className="mt-4 rounded-full border border-ink/20 px-4 py-2 text-[14px] text-ink/75 hover:border-sageDk hover:text-sageDk transition-colors"
                      >
                        {isOpen ? "약력 접기 −" : "전체 약력 보기 +"}
                      </button>
                    ) : null}
                  </div>
                </div>

                {isOpen ? (
                  <ul className="mt-4 rounded-2xl bg-cream border border-ink/10 p-5 space-y-2">
                    {p.certs.map((c, j) => (
                      <li key={j} className="flex items-start gap-2.5 text-[15px] leading-[1.65] text-ink/80 break-keep">
                        <span className="mt-[8px] h-1 w-1 rounded-full bg-sage shrink-0" />
                        <span>{c}</span>
                      </li>
                    ))}
                  </ul>
                ) : null}
              </article>
            );
          })}
        </div>
      </div>
    </section>
  );
}

/* ── 10. 이용안내 (시작 4단계 + 안내표) ────────────────────────────────── */

function Guide() {
  const p = C.process;
  const rows = p.guide.filter((g) => g.value); // 아직 안 정한 항목은 자동으로 빠진다
  return (
    <section id="guide" className="bg-ivory py-16 sm:py-24">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <SectionHead eyebrow={p.eyebrow} title={p.title} />

        <div className="mt-10 sm:mt-14 grid sm:grid-cols-2 lg:grid-cols-4 gap-5">
          {p.steps.map((s, i) => (
            <div
              key={s.no}
              className="reveal relative rounded-[22px] bg-cream border border-ink/10 p-8"
              style={{ animationDelay: i * 80 + "ms" }}
            >
              <p className="font-display text-[37px] leading-none text-sage/55">{s.no}</p>
              <h3 className="mt-5 text-[19.5px] font-semibold text-ink">{s.title}</h3>
              <p className="mt-2.5 text-[15.5px] leading-[1.8] text-ink/70">{s.desc}</p>
            </div>
          ))}
        </div>

        {rows.length ? (
          <div className="reveal mt-6 rounded-[22px] bg-cream border border-ink/10 divide-y divide-ink/10">
            {rows.map((g, i) => (
              <div key={i} className="flex flex-col sm:flex-row sm:items-center gap-1.5 sm:gap-8 px-7 py-6">
                <p className="font-label text-[13px] tracking-wider2 uppercase text-sage sm:w-32 shrink-0">{g.label}</p>
                <p className="text-[17px] text-ink/80">{g.value}</p>
              </div>
            ))}
          </div>
        ) : null}
      </div>
    </section>
  );
}

/* ── 11. 후기 (없으면 통째로 숨김) ─────────────────────────────────────── */

function Reviews() {
  const r = C.reviews;
  if (!r.list || !r.list.length) return null;
  return (
    <section className="bg-cream py-16 sm:py-24">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <SectionHead eyebrow={r.eyebrow} title={r.title} />
        <div className="mt-10 grid md:grid-cols-3 gap-5">
          {r.list.map((v, i) => (
            <blockquote key={i} className="reveal rounded-[22px] bg-ivory border border-ink/10 p-8">
              <p className="text-sage text-[15px] tracking-wider2">{"★".repeat(v.stars || 5)}</p>
              <p className="mt-4 text-[17px] leading-[1.85] text-ink/85">{v.text}</p>
              <footer className="mt-6 text-[15px] text-ink/70">{v.author}</footer>
            </blockquote>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ── 12. FAQ ───────────────────────────────────────────────────────────── */

function Faq() {
  const f = C.faq;
  const [open, setOpen] = useState(0);
  return (
    <section className="bg-ivory bg-grain py-16 sm:py-24">
      <div className="mx-auto max-w-3xl px-5 sm:px-8">
        <SectionHead eyebrow={f.eyebrow} title={f.title} />
        <div className="mt-10 divide-y divide-ink/10 border-y border-ink/10">
          {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-ink leading-snug">{it.q}</span>
                  <span
                    className={
                      "mt-1 shrink-0 font-thin text-[26px] leading-none text-sage transition-transform duration-300 " +
                      (isOpen ? "rotate-45" : "")
                    }
                  >
                    +
                  </span>
                </button>
                {isOpen ? (
                  <p className="pb-7 pr-10 text-[16.5px] leading-[1.9] text-ink/85">{it.a}</p>
                ) : null}
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

/* ── 13. 마무리 CTA ────────────────────────────────────────────────────── */

function CtaBand() {
  const c = C.cta;
  return (
    <section className="relative bg-sageDk py-16 sm:py-24 overflow-hidden">
      <div className="orb bg-sageLt/25" style={{ width: 520, height: 520, bottom: -220, left: -140 }} />
      <div className="relative mx-auto max-w-3xl px-5 sm:px-8 text-center">
        <h2 className="reveal text-[34px] sm:text-[48px] leading-[1.24] font-bold tracking-[-0.015em] break-keep text-cream">
          {c.title.map((l, i) => (
            <span key={i} className="block">{l}</span>
          ))}
        </h2>
        <p className="reveal mt-7 text-[17px] sm:text-[18.5px] leading-[1.85] text-cream/80">{c.desc}</p>
        <button
          onClick={() => document.getElementById("booking").scrollIntoView({ behavior: "smooth" })}
          className="reveal mt-10 rounded-full bg-cream px-9 py-4 text-[17px] text-sageDk hover:bg-mist transition-colors"
        >
          {c.button}
        </button>
      </div>
    </section>
  );
}

/* ── 14. 문의 (카카오톡 채널) ──────────────────────────────────────────────
   원래 이름·연락처를 받는 폼이었는데, 신청을 카카오톡 채널로만 받기로 해서
   폼을 걷어냈다. 개인정보를 아예 수집하지 않으므로 동의 절차도 필요 없다.
   ────────────────────────────────────────────────────────────────────────── */

function Booking() {
  const b = C.booking;
  const s = C.site;

  return (
    <section id="booking" className="bg-cream py-16 sm:py-24">
      <div className="mx-auto max-w-3xl px-5 sm:px-8">
        <SectionHead eyebrow={b.eyebrow} title={b.title} desc={b.desc} />

        <div className="mt-9 rounded-[24px] bg-ivory border border-ink/10 p-8 sm:p-12 text-center">
          {s.kakao ? (
            <a
              href={s.kakao}
              target="_blank"
              rel="noreferrer noopener"
              className="inline-block rounded-full bg-[#FEE500] px-9 py-4 text-[17px] font-semibold text-[#3C1E1E] hover:brightness-95 transition"
            >
              카카오톡으로 문의하기
            </a>
          ) : (
            /* 채널 주소가 아직 없으면 버튼을 만들지 않는다.
               눌러도 아무 데도 안 가는 버튼을 두는 것보다 낫다. */
            <p className="text-[16px] leading-[1.85] text-ink/60">
              카카오톡 상담 채널을 준비하고 있습니다.
            </p>
          )}

          {s.phone ? (
            <p className="mt-6 text-[17px] text-ink/80">
              전화 문의{" "}
              <a href={"tel:" + s.phone.replace(/[^0-9+]/g, "")} className="font-semibold text-sageDk hover:underline">
                {s.phone}
              </a>
            </p>
          ) : null}

          <div className="mt-8 flex flex-wrap justify-center gap-x-6 gap-y-2">
            {b.trust.map((t, i) => (
              <span key={i} className="text-[14px] text-ink/60">· {t}</span>
            ))}
          </div>
        </div>

        {/* 찾아오는 길 — 주소는 확정되어 있다 */}
        {s.address ? (
          <div className="mt-6 rounded-[24px] bg-ivory border border-ink/10 p-8 sm:p-10">
            <p className="font-label text-[13px] tracking-mega uppercase text-sage">LOCATION</p>
            <p className="mt-4 text-[19px] font-semibold text-ink">
              {s.address} {s.addressDetail}
            </p>
            {s.transit ? <p className="mt-2 text-[16px] text-ink/70">{s.transit}</p> : null}
            <div className="mt-6 flex flex-wrap gap-3">
              <a
                href={"https://map.naver.com/p/search/" + encodeURIComponent(s.mapQuery || s.name)}
                target="_blank"
                rel="noreferrer noopener"
                className="rounded-full border border-ink/25 px-6 py-3 text-[15px] text-ink/85 hover:border-sageDk hover:text-sageDk transition-colors"
              >
                네이버 지도에서 보기 →
              </a>
              <a
                href={"https://map.kakao.com/?q=" + encodeURIComponent(s.address)}
                target="_blank"
                rel="noreferrer noopener"
                className="rounded-full border border-ink/25 px-6 py-3 text-[15px] text-ink/85 hover:border-sageDk hover:text-sageDk transition-colors"
              >
                카카오맵에서 보기 →
              </a>
            </div>
          </div>
        ) : null}
      </div>
    </section>
  );
}

/* ── 15. 푸터 ──────────────────────────────────────────────────────────── */

function Footer() {
  const s = C.site;
  const f = C.footer;
  const lines = [
    s.address ? { label: "주소", value: s.address + (s.addressDetail ? " " + s.addressDetail : "") } : null,
    s.phone ? { label: "전화", value: s.phone } : null,
    s.hoursWeekday ? { label: "운영시간", value: s.hoursWeekday } : null,
    s.parking ? { label: "주차", value: s.parking } : null,
    s.bizNumber ? { label: "사업자등록번호", value: s.bizNumber } : null,
  ].filter(Boolean);

  return (
    <footer className="bg-ink text-cream/80 py-16 sm:py-20">
      <div className="mx-auto max-w-8xl px-5 sm:px-8">
        <div className="grid md:grid-cols-2 gap-10">
          <div>
            <p className="font-display text-[26px] tracking-wider2 text-cream leading-none">
              BALANCE ON PILATES
            </p>
            <p className="mt-3 font-label text-[12px] tracking-mega text-mist/60">{f.company}</p>
            <p className="mt-6 text-[16px] leading-[1.8] text-cream/70 max-w-md">{s.tagline}</p>
          </div>

          <div className="md:justify-self-end">
            {lines.length ? (
              <dl className="space-y-2.5">
                {lines.map((l, i) => (
                  <div key={i} className="flex gap-4 text-[15.5px]">
                    <dt className="w-24 shrink-0 text-cream/55">{l.label}</dt>
                    <dd className="text-cream/85">{l.value}</dd>
                  </div>
                ))}
              </dl>
            ) : (
              <p className="text-[15.5px] text-cream/55 leading-[1.9]">
                주소 · 전화번호 · 운영시간은 확정되는 대로 이곳에 표시됩니다.
              </p>
            )}

            {s.instagram || s.kakao || s.naverMap ? (
              <div className="mt-6 flex flex-wrap gap-2.5">
                {s.instagram ? <FooterLink href={s.instagram} label="Instagram" /> : null}
                {s.kakao ? <FooterLink href={s.kakao} label="카카오톡 상담" /> : null}
                {s.naverMap ? <FooterLink href={s.naverMap} label="네이버 지도" /> : null}
              </div>
            ) : null}
          </div>
        </div>

        <div className="mt-14 pt-7 border-t border-cream/10 flex flex-col sm:flex-row justify-between gap-3">
          <p className="font-label text-[13px] tracking-wider2 text-cream/55">{f.copyright}</p>
          <button
            onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
            className="text-left font-label text-[13px] tracking-wider2 text-cream/55 hover:text-cream/80 transition-colors"
          >
            맨 위로 ↑
          </button>
        </div>
      </div>
    </footer>
  );
}

function FooterLink({ href, label }) {
  return (
    <a
      href={href}
      target="_blank"
      rel="noreferrer noopener"
      className="rounded-full border border-cream/25 px-4 py-2 text-[14.5px] text-cream/80 hover:border-cream/60 transition-colors"
    >
      {label}
    </a>
  );
}

/* ── 16. 떠 있는 CTA ───────────────────────────────────────────────────── */

function FloatingCta() {
  const [show, setShow] = useState(false);
  useEffect(() => {
    const onScroll = () => {
      const booking = document.getElementById("booking");
      const nearForm = booking && booking.getBoundingClientRect().top < window.innerHeight * 1.1;
      setShow(window.scrollY > window.innerHeight * 0.9 && !nearForm);
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  return (
    <div
      className={
        "fixed bottom-5 right-5 z-40 transition-all duration-500 " +
        (show ? "opacity-100 translate-y-0" : "opacity-0 translate-y-6 pointer-events-none")
      }
    >
      <button
        onClick={() => document.getElementById("booking").scrollIntoView({ behavior: "smooth" })}
        className="rounded-full bg-sageDk px-6 py-4 text-[16px] text-cream shadow-xl shadow-ink/20 hover:bg-ink transition-colors"
      >
        체험 · 상담 문의
      </button>
    </div>
  );
}

/* ── 앱 ────────────────────────────────────────────────────────────────── */

/* ── 17. 칼럼(저널) ───────────────────────────────────────────────────────
   데이터는 src/columns.js 에 있다. write.html 에서 쓰고 내려받아 덮어쓴다.
   ────────────────────────────────────────────────────────────────────────── */

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

// "2026.08.12" → 오늘 날짜와 비교할 수 있는 숫자
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);
}

// image 가 "auto" 면 카테고리 사진 중에서 고른다. id 를 기준으로 고르므로 항상 같은 사진이 나온다.
function postImage(p) {
  if (p.image && p.image !== "auto") return p.image;
  const pool = (J.photoPools || {})[p.cat] || (J.photoPools || {})._default || [];
  if (!pool.length) return "img/studio-wide.jpg";
  let h = 0;
  for (let i = 0; i < String(p.id).length; i++) h = (h * 31 + String(p.id).charCodeAt(i)) >>> 0;
  return pool[h % pool.length];
}

/* 본문 중간에 자동으로 들어가는 사진.
   카테고리 사진 묶음이 세 장씩이라, 대표 사진 1장 + 본문 2장 = 한 편에 3장이 된다.
   대표 사진과 겹치지 않게 나머지를 순서대로 쓴다. 원고에는 사진을 적지 않는다. */
function bodyPhotos(p, count = 2) {
  const pool = (J.photoPools || {})[p.cat] || (J.photoPools || {})._default || [];
  const hero = postImage(p);
  const rest = pool.filter((src) => src !== hero);
  const out = [];
  for (let i = 0; i < count && rest.length; i++) out.push(rest[i % rest.length]);
  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 sm:rounded-[22px]"
      />
    </figure>
  );
}

/* noPhoto: 사진 없이 글자만.
   글 한 편 안에서는 사진을 딱 3장(대표 1 + 본문 2)만 쓰기로 했다.
   글 끝의 「함께 읽으면 좋은 글」에까지 사진을 붙이면 한 화면에 다섯 장이 되어
   글보다 사진이 많아 보인다. 그래서 그 자리에서는 이 옵션을 켠다. */
function ColumnCard({ p, wide = false, noPhoto = false }) {
  return (
    <a
      href={"#/column/" + p.id}
      className={
        "reveal group block overflow-hidden rounded-[22px] bg-ivory border border-ink/10 " +
        "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-label text-[11.5px] tracking-wider2 uppercase text-sage">{p.cat}</p>
        <h3 className="mt-3 text-[19px] sm:text-[20px] font-semibold text-ink leading-snug">{p.title}</h3>
        <p className="mt-3 text-[14.5px] leading-[1.8] text-ink/70">{p.excerpt}</p>
        <p className="mt-5 text-[13px] text-ink/45">{p.date}</p>
      </div>
    </a>
  );
}

/* 홈에 붙는 최신 3개 미리보기 — 글이 없으면 통째로 숨긴다 */
function JournalPreview() {
  const list = publishedPosts().slice(0, 3);
  if (!list.length) return null;
  return (
    <section className="bg-ivory bg-grain py-16 sm:py-24">
      <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-10 sm:mt-14 grid sm:grid-cols-3 gap-6">
          {list.map((p) => <ColumnCard key={p.id} p={p} />)}
        </div>
        <div className="mt-10 text-center">
          <a
            href="#/column"
            className="inline-block rounded-full border border-ink/25 px-8 py-4 text-[15px] text-ink/85 hover:border-sageDk hover:text-sageDk 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-cream 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-ink/60">
            아직 발행된 글이 없습니다.
            <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={
                      "rounded-full px-5 py-2.5 text-[14px] transition-colors " +
                      (c === cat
                        ? "bg-sageDk text-cream"
                        : "border border-ink/20 text-ink/70 hover:border-sageDk hover:text-sageDk")
                    }
                  >
                    {c}
                  </button>
                ))}
              </div>
            ) : null}

            <div className="mt-12 sm:mt-16 grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
              {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-cream 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-bold text-ink">글을 찾을 수 없습니다</h1>
          <p className="mt-5 text-[15px] text-ink/65">주소가 바뀌었거나 아직 공개되지 않은 글입니다.</p>
          <a
            href="#/column"
            className="mt-9 inline-block rounded-full bg-sageDk px-7 py-3.5 text-[15px] text-cream hover:bg-ink transition-colors"
          >
            저널 목록으로
          </a>
        </div>
      </main>
    );
  }

  const related = (p.related || []).map(findPost).filter((x) => x && !x.draft);
  const photos = bodyPhotos(p);                       // 본문 중간 사진 2장
  const slots = photoSlots(p.body || [], photos.length);

  return (
    <main className="bg-cream pb-24 sm:pb-32 pt-[68px] sm:pt-[76px]">
      {/* 대표 사진 */}
      <div className="relative h-[46svh] min-h-[300px] overflow-hidden">
        <img src={postImage(p)} alt={p.title} className="h-full w-full object-cover" />
        <div
          className="absolute inset-0"
          style={{
            background:
              "linear-gradient(to top, rgba(28,31,25,.92) 0%, rgba(28,31,25,.55) 40%, rgba(28,31,25,.22) 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-label text-[11.5px] tracking-mega uppercase text-mist">{p.cat}</p>
            <h1 className="mt-4 text-[27px] sm:text-[38px] font-semibold text-cream leading-[1.3] break-keep">
              {p.title}
            </h1>
            <p className="mt-4 text-[13.5px] text-cream/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-ink/85 break-keep">{p.excerpt}</p>
        <div className="mt-8 h-px w-14 bg-sage/50" />

        <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-ink/80 break-keep">
                    {b}
                  </p>
                ) : (
                  <h2 className="mt-14 mb-1 text-[21px] sm:text-[24px] font-semibold text-ink leading-snug break-keep">
                    {b.h2}
                  </h2>
                )}
              </React.Fragment>
            );
          })}
        </div>

        {/* 글 끝 상담 유도 */}
        <div className="mt-20 rounded-[24px] bg-sageDk px-8 py-10 sm:px-12 sm:py-12 text-center">
          <p className="font-label text-[11px] tracking-mega uppercase text-mist/70">BALANCE ON PILATES</p>
          <p className="mt-5 text-[19px] sm:text-[22px] leading-[1.6] text-cream break-keep">
            내 몸에 맞는 방향이 궁금하다면,
            <br />원장 상담부터 시작해보세요.
          </p>
          <a
            href="#/"
            onClick={() => setTimeout(() => {
              const el = document.getElementById("booking");
              if (el) el.scrollIntoView({ behavior: "smooth" });
            }, 90)}
            className="mt-8 inline-block rounded-full bg-cream px-8 py-4 text-[15px] text-sageDk hover:bg-mist transition-colors"
          >
            체험 · 상담 문의
          </a>
        </div>

        {related.length ? (
          <div className="mt-20">
            <p className="font-label text-[11.5px] tracking-mega uppercase text-sage">RELATED</p>
            <h2 className="mt-3 text-[22px] font-semibold text-ink">함께 읽으면 좋은 글</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="inline-block rounded-full border border-ink/25 px-8 py-4 text-[15px] text-ink/85 hover:border-sageDk hover:text-sageDk transition-colors"
          >
            ← 저널 목록으로
          </a>
        </div>
      </article>
    </main>
  );
}

/* ── 화면 전환 ─────────────────────────────────────────────────────────────
   주소의 # 뒤만 보고 화면을 고른다. 서버 설정이 필요 없어 정적 호스팅에서 그대로 돈다.
     (없음)              홈
     #/column            저널 목록
     #/column/<글id>     글 한 편
   ────────────────────────────────────────────────────────────────────────── */

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 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 />
      <main>
        <Hero />
        <Pillars />
        <KeyPoints />
        <Manifesto />
        <Strengths />
        <Gallery />
        <PainPoints />
        <Programs />
        <Equipment />
        <Teachers />
        <Guide />
        <Reviews />
        <Faq />
        <JournalPreview />
        <CtaBand />
        <Booking />
      </main>
      <Footer />
      <FloatingCta />
    </React.Fragment>
  );
}

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