// Goyo Club — App root + GNB right (static links, disabled) + Tweaks panel

const { useState: useStateA, useEffect: useEffectA, useMemo: useMemoA, useRef: useRefA } = React;

// ---------------------------------------------------------------------------
// Page background vertical guide lines.
// `target` accepts any CSS length / percentage / calc() and decides where the
// line ends up. `delay`/`duration` (ms) drive the parallax fly-in: every line
// starts at left: 0 (page edge) on first render and slides to its target with
// a magnetic cubic-bezier ease. Tweak this list freely to add/remove lines or
// reposition them — the JSX maps it directly.
// ---------------------------------------------------------------------------
// `target`은 콘텐츠 좌우 끝선 (var(--gutter) = 36px)과 절대 일치하지 않도록 절대값으로 둠.
const PAGE_GUIDES = [
  { id: "rail-left",  target: "14px",                          delay: 0, duration: 4500, rail: true },
  { id: "g1",         target: "9%",                            delay: 0, duration: 5500              },
  { id: "g2",         target: "23%",                           delay: 0, duration: 6500              },
  { id: "g3",         target: "31%",                           delay: 0, duration: 5000              },
  { id: "g4",         target: "47%",                           delay: 0, duration: 7500              },
  { id: "g5",         target: "62%",                           delay: 0, duration: 6000              },
  { id: "g6",         target: "78%",                           delay: 0, duration: 6800              },
  { id: "g7",         target: "88%",                           delay: 0, duration: 5200              },
  { id: "rail-right", target: "calc(100% - 22px)",             delay: 0, duration: 5800, rail: true },
];

// --- Top center logo — clickable, acts as Home link ------------------------
function GoyoLogo({ onClick }) {
  return (
    <button className="logo" aria-label="Goyo Club — Home" onClick={onClick}>
      <img src="assets/goyo-logo.png" alt="Goyo Club" className="logo__img" />
    </button>);

}

// --- GNB-left Back button (replaces TopTabs/SeriesChipsRow on static pages) -
function GnbBack({ onClick }) {
  return (
    <button className="gnb-back" onClick={onClick}>
      [ <span className="btn-label">← Back</span> ]
    </button>);

}

// --- GNB left tabs (top row) -----------------------------------------------
function TopTabs({ typeTabs, typeCounts, typeFilter, onTypeClick, openSearch, onTabHover }) {
  return (
    <nav className="top-tabs">
      {typeTabs.map((t, i) =>
      <React.Fragment key={t.id}>
          <button
          className={`top-tab ${typeFilter === t.id ? "top-tab--active" : ""}`}
          onClick={() => onTypeClick(t.id)}
          onMouseEnter={() => onTabHover(t.id)}>

            <span className="top-tab__label">{t.label}</span>
            <sup className="filter-count">({String(typeCounts[t.id] || 0).padStart(2, "0")})</sup>
          </button>
          {i < typeTabs.length - 1 && <span className="top-tab__sep">·</span>}
        </React.Fragment>
      )}
      <span className="top-tab__sep">·</span>
      <button className="top-tabs__search-btn" onClick={openSearch} aria-label="Search">
        <svg className="top-tabs__search-icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6">
          <circle cx="9" cy="9" r="6" />
          <line x1="13.5" y1="13.5" x2="18" y2="18" strokeLinecap="round" />
        </svg>
      </button>
    </nav>);

}

// --- Search Modal ----------------------------------------------------------
function SearchModal({ open, query, setQuery, onClose, results, onSelectResult }) {
  const inputRef = useRefA(null);
  useEffectA(() => {
    if (open && inputRef.current) {
      const id = setTimeout(() => inputRef.current?.focus(), 30);
      return () => clearTimeout(id);
    }
  }, [open]);
  useEffectA(() => {
    if (!open) return;
    const onKey = (e) => {if (e.key === "Escape") onClose();};
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open, onClose]);
  if (!open) return null;
  return (
    <div className="search-modal" role="dialog" aria-modal="true" onClick={onClose}>
      <div className="search-modal__inner" onClick={(e) => e.stopPropagation()}>
        <button className="search-modal__close" onClick={onClose} aria-label="Close">[ <span className="btn-label">× CLOSE</span> ]</button>
        <div className="search-modal__field">
          <svg className="search-modal__icon" viewBox="0 0 20 20" width="28" height="28" fill="none" stroke="currentColor" strokeWidth="1.4">
            <circle cx="9" cy="9" r="6" />
            <line x1="13.5" y1="13.5" x2="18" y2="18" strokeLinecap="round" />
          </svg>
          <input
            ref={inputRef}
            className="search-modal__input"
            type="text"
            placeholder="SEARCH GOYO CLUB —"
            value={query}
            onChange={(e) => setQuery(e.target.value)} />
          
        </div>
        {query &&
        <div className="search-modal__results">
            <div className="search-modal__count">
              {results.length === 0 ? "NO RESULTS" : `${results.length} RESULT${results.length === 1 ? "" : "S"}`}
            </div>
            <div className="search-modal__list">
              {results.slice(0, 12).map((r) =>
            <button key={r.id} className="search-modal__row" onClick={() => onSelectResult(r.id)}>
                  <span className="search-modal__row-type">{r.type}</span>
                  <span className="search-modal__row-title">{r.title}</span>
                  <span className="search-modal__row-date">{r.date}</span>
                </button>
            )}
            </div>
          </div>
        }
      </div>
    </div>);

}

// --- Series chips row (visible when any TYPE hovered/active) --------------
function SeriesChipsRow({ visible, series, parentType, seriesFilter, seriesCounts, onSeriesClick }) {
  return (
    <div className={`series-row ${visible ? "series-row--on" : "series-row--off"}`}>
      <div className="series-row__chips">
        {(series || []).map((s) =>
        <button
          key={s.id}
          className={`chip ${seriesFilter === s.id ? "chip--active" : ""}`}
          onClick={() => onSeriesClick(s.id, parentType)}>

            <span className="chip__label">{s.label}</span>
            <sup className="filter-count">({String(seriesCounts[s.id] || 0).padStart(2, "0")})</sup>
          </button>
        )}
      </div>
    </div>);

}

// --- GNB right (page links, with active state for current page) ------------
const STATIC_PAGES = [
  { id: "about",   label: "ABOUT" },
  { id: "service", label: "SERVICE" },
  { id: "contact", label: "CONTACT" }
];

function GnbRight({ currentPage, onNavigate }) {
  return (
    <nav className="gnb-right" aria-label="static pages">
      {STATIC_PAGES.map((p) =>
      <button
        key={p.id}
        className={`gnb-right__link ${currentPage === p.id ? "gnb-right__link--active" : ""}`}
        onClick={() => onNavigate(p.id)}>

          <span className="btn-label">{p.label}</span>
        </button>
      )}
    </nav>);

}

// --- Static pages (About, Service, Contact placeholders) -------------------
function AboutPage({ goHome }) {
  const { HERO_INTRO } = window.GoyoRight;
  // About is its own composition (not <HeroBody />): Back button replaces the
  // header row, the brand video is replaced with assets/about.jpeg, intro
  // paragraphs reused from the home hero.
  return (
    <main className="static-page">
      <div className="rbody rbody--about">
        <header className="rbody__header">
          <button className="rbody__cta" onClick={goHome}>
            [ <span className="btn-label">← Back</span> ]
          </button>
        </header>
        <div className="rbody__hero">
          <img src="assets/about.jpeg" alt="Goyo Club" />
        </div>
        <div className="rbody__intro">
          {HERO_INTRO.map((p, i) => <p key={i}>{p}</p>)}
        </div>
      </div>
    </main>);

}

function StaticPlaceholderPage({ title }) {
  return (
    <main className="static-page">
      <div className="rbody rbody--placeholder">
        <header className="rbody__header">
          <span className="rbody__title">{title}</span>
        </header>
        <div className="rbody__intro">
          <p>Coming soon.</p>
        </div>
      </div>
    </main>);

}

const SERVICES = [
{
  label: "VIDEO PRODUCTION",
  description: "Music videos, commercials, branding content, and visual storytelling. We handle everything from concept to final cut. Whether for an artist or a brand, inside a studio or out in the world, we capture the narrative with the texture and authenticity that defines us."
},
{
  label: "EVENT HOSTING AND MANAGEMENT",
  description: "Parties, pop ups, showcases, and fashion brand launches. We build stages around LA, gathering the community to experience the culture. From intimate shows to major events, we curate experiences that honor the spaces and the people who come and go."
},
{
  label: "EVENT VIDEO PRODUCTION",
  description: "DJ live mix recording and live performance filming. We capture the energy of the stage and the sound of the artist. Whether it is a set in a shop or a show on a stage, we ensure the performance is archived and the spirit is preserved."
}];


// ---- Contact page form ---------------------------------------------------
const PURPOSE_OPTIONS = [
{ value: "general", label: "GENERAL CONTACT" },
{ value: "client",  label: "CLIENT SERVICE" },
{ value: "artist",  label: "ARTIST SUBMISSION" }];


const SERIES_OPTIONS = [
{ value: "finding-records",   label: "FINDING RECORDS" },
{ value: "electric-cleaners", label: "ELECTRIC CLEANERS" },
{ value: "neon-collection",   label: "NEON COLLECTION" }];


const SERVICE_TYPE_OPTIONS = [
{ value: "video",       label: "VIDEO PRODUCTION" },
{ value: "event",       label: "EVENT HOSTING & MANAGEMENT" },
{ value: "event-video", label: "EVENT VIDEO PRODUCTION" },
{ value: "other",       label: "OTHER" }];


const BUDGET_OPTIONS = [
{ value: "lt-5",  label: "< $5K" },
{ value: "5-15",  label: "$5K – $15K" },
{ value: "15-50", label: "$15K – $50K" },
{ value: "gt-50", label: "$50K+" },
{ value: "tbd",   label: "TBD" }];


const FIND_US_OPTIONS = [
{ value: "instagram", label: "INSTAGRAM" },
{ value: "wom",       label: "WORD OF MOUTH" },
{ value: "search",    label: "SEARCH" },
{ value: "event",     label: "EVENT" },
{ value: "other",     label: "OTHER" }];


const ROLE_OPTIONS = [
{ value: "musician", label: "MUSICIAN" },
{ value: "director", label: "DIRECTOR" },
{ value: "both",     label: "BOTH" }];


function FormRow({ label, multiline, children }) {
  return (
    <div className={`kv kv--form ${multiline ? "kv--form-multiline" : ""}`}>
      <span className="kv__k">{label}</span>
      <div className="kv__v">{children}</div>
    </div>);

}

function FormInput({ placeholder, type = "text" }) {
  return <input className="form-input" type={type} placeholder={placeholder} />;
}

function FormTextarea({ placeholder, rows = 3 }) {
  return <textarea className="form-input form-input--textarea" placeholder={placeholder} rows={rows} />;
}

function FormRadios({ label, options, selected, onChange }) {
  return (
    <div className="kv kv--radios">
      <span className="kv__k">{label}</span>
      <div className="kv__v form-radios">
        {options.map((opt) =>
        <button
          key={opt.value}
          type="button"
          className={`form-radio ${selected === opt.value ? "form-radio--active" : ""}`}
          onClick={() => onChange(opt.value)}>

            <span className="btn-label">{opt.label}</span>
          </button>
        )}
      </div>
    </div>);

}

function GeneralFields() {
  return (
    <React.Fragment>
      <FormRow label="SUBJECT"><FormInput placeholder="Brief subject of your inquiry" /></FormRow>
      <FormRow label="MESSAGE" multiline><FormTextarea placeholder="Tell us what's on your mind" /></FormRow>
    </React.Fragment>);

}

function ClientFields() {
  const [serviceType, setServiceType] = useStateA(null);
  const [budget, setBudget] = useStateA(null);
  const [findUs, setFindUs] = useStateA(null);
  return (
    <React.Fragment>
      <FormRow label="COMPANY / BRAND / ARTIST"><FormInput placeholder="Who's reaching out?" /></FormRow>
      <FormRow label="INSTAGRAM OR WEBSITE"><FormInput placeholder="@handle or url" /></FormRow>
      <FormRadios label="SERVICE TYPE" options={SERVICE_TYPE_OPTIONS} selected={serviceType} onChange={setServiceType} />
      <FormRow label="PROJECT TYPE"><FormInput placeholder="e.g. music video, fashion launch" /></FormRow>
      <FormRow label="PROJECT DESCRIPTION" multiline><FormTextarea placeholder="What are we making? Who's it for? Tone?" /></FormRow>
      <FormRadios label="BUDGET RANGE" options={BUDGET_OPTIONS} selected={budget} onChange={setBudget} />
      <FormRow label="TIMELINE"><FormInput placeholder="Target date or window" /></FormRow>
      <FormRow label="REFERENCE / MOOD" multiline><FormTextarea placeholder="Links to anything inspiring this project" /></FormRow>
      <FormRadios label="HOW DID YOU FIND US?" options={FIND_US_OPTIONS} selected={findUs} onChange={setFindUs} />
    </React.Fragment>);

}

function FindingRecordsFields() {
  return (
    <React.Fragment>
      <FormRow label="MUSIC LINKS" multiline><FormTextarea placeholder="Where can we find your music?" /></FormRow>
      <FormRow label="GENRES"><FormInput placeholder="How would you describe your music? (optional)" /></FormRow>
      <FormRow label="ORIGINAL SONG TO PERFORM"><FormInput placeholder="Title of an original song you'd like to perform" /></FormRow>
      <FormRow label="LIVE PERFORMANCE VIDEOS" multiline><FormTextarea placeholder="Links to live (or self-recorded) performances" /></FormRow>
      <FormRow label="BEST WAY TO CONTACT"><FormInput placeholder="Phone, DM, etc. (optional)" /></FormRow>
    </React.Fragment>);

}

function ElectricCleanersFields() {
  return (
    <React.Fragment>
      <FormRow label="DJ EXPERIENCE"><FormInput placeholder="How long have you been DJing? (optional)" /></FormRow>
      <FormRow label="PREFERRED GENRE"><FormInput placeholder="What do you like to play? (optional)" /></FormRow>
      <FormRow label="SET / MIX LINKS" multiline><FormTextarea placeholder="Links to sets that best represent your style" /></FormRow>
      <FormRow label="BEST WAY TO CONTACT"><FormInput placeholder="Phone, DM, etc. (optional)" /></FormRow>
    </React.Fragment>);

}

function NeonCollectionFields() {
  const [role, setRole] = useStateA(null);
  return (
    <React.Fragment>
      <FormRadios label="ROLE" options={ROLE_OPTIONS} selected={role} onChange={setRole} />
      <FormRow label="MUSIC OR VIDEO LINKS" multiline><FormTextarea placeholder="Share links to your music or music videos" /></FormRow>
    </React.Fragment>);

}

function ArtistFields({ series, onSeriesChange }) {
  return (
    <React.Fragment>
      <FormRadios label="WHICH SERIES?" options={SERIES_OPTIONS} selected={series} onChange={onSeriesChange} />
      {series === "finding-records" && <FindingRecordsFields />}
      {series === "electric-cleaners" && <ElectricCleanersFields />}
      {series === "neon-collection" && <NeonCollectionFields />}
    </React.Fragment>);

}

function IdentityFields({ purpose }) {
  const isArtist = purpose === "artist";
  return (
    <React.Fragment>
      <FormRow label="NAME"><FormInput placeholder="Your full name" /></FormRow>
      <FormRow label="EMAIL"><FormInput placeholder="name@example.com" type="email" /></FormRow>
      {isArtist &&
      <React.Fragment>
          <FormRow label="INSTAGRAM"><FormInput placeholder="@handle" /></FormRow>
          <FormRow label="BASED IN LA?"><FormInput placeholder="Los Angeles / Seoul / etc." /></FormRow>
        </React.Fragment>
      }
    </React.Fragment>);

}

function ContactPage({ goHome }) {
  const [purpose, setPurpose] = useStateA(null);
  const [series, setSeries] = useStateA(null);

  const handlePurposeChange = (p) => {
    setPurpose(p);
    setSeries(null);
  };

  // Identity + Submit appear once the purpose-specific section has content
  // (general/client → after purpose; artist → after series too).
  const showIdentity = purpose && (purpose !== "artist" || series);

  return (
    <main className="static-page">
      <div className="rbody rbody--contact">
        <header className="rbody__header">
          <button className="rbody__cta" onClick={goHome}>
            [ <span className="btn-label">← Back</span> ]
          </button>
        </header>

        <FormRadios
          label="WHAT'S THIS ABOUT?"
          options={PURPOSE_OPTIONS}
          selected={purpose}
          onChange={handlePurposeChange} />

        {purpose === "general" && <GeneralFields />}
        {purpose === "client" && <ClientFields />}
        {purpose === "artist" && <ArtistFields series={series} onSeriesChange={setSeries} />}

        {showIdentity && <IdentityFields purpose={purpose} />}

        {showIdentity &&
        <div className="form-submit">
            <button className="rbody__cta" type="button">
              [ <span className="btn-label">SUBMIT</span> ]
            </button>
          </div>
        }
      </div>
    </main>);

}

function ServicePage({ goHome }) {
  return (
    <main className="static-page">
      <div className="rbody rbody--service">
        <header className="rbody__header">
          <button className="rbody__cta" onClick={goHome}>
            [ <span className="btn-label">← Back</span> ]
          </button>
        </header>
        <div className="rbody__hero">
          <img src="assets/service.jpeg" alt="Goyo Club Services" />
        </div>
        <div className="rbody__info">
          {SERVICES.map((s, i) =>
          <div key={i} className="kv">
              <span className="kv__k">{s.label}</span>
              <span className="kv__v">{s.description}</span>
            </div>
          )}
        </div>
      </div>
    </main>);

}

// --- Site footer (app-level, fixed at bottom of page like the GNB header) --
const FOOTER_LINKS = [
  { label: "INSTAGRAM",   href: "https://www.instagram.com/goyo.club/" },
  { label: "YOUTUBE",     href: "https://www.youtube.com/@goyoclub" },
  { label: "APPLE MUSIC", href: "https://music.apple.com/us/curator/goyo-club/1796068196" }
];

function SiteFooter() {
  return (
    <footer className="site-footer">
      <span className="site-footer__copy">© GOYO</span>
      <div className="site-footer__links">
        {FOOTER_LINKS.map((l) =>
        <a
          key={l.label}
          className="site-footer__link"
          href={l.href}
          target="_blank"
          rel="noopener noreferrer">

            [ <span className="btn-label">{l.label}</span> ]
          </a>
        )}
      </div>
    </footer>);

}


// --- App -------------------------------------------------------------------
function App() {
  const { ROWS, HERO_ROW, SERIES_BY_TYPE, TYPE_TABS } = window.GOYO_DATA;
  const { LeftPane } = window.GoyoLeft;
  const { RightPane } = window.GoyoRight;

  const tweaks = { split: "60:40", hoverStyle: "invert", darkMode: false, transition: "fade" };

  const [typeFilter, setTypeFilter] = useStateA(null);
  const [seriesFilter, setSeriesFilter] = useStateA(null);
  const [tagFilter, setTagFilter] = useStateA(null);
  const [search, setSearch] = useStateA("");
  const [hoveredId, setHoveredId] = useStateA(null);
  const [selectedId, setSelectedId] = useStateA(null);
  const [hoveredTypeTab, setHoveredTypeTab] = useStateA(null);
  const [searchOpen, setSearchOpen] = useStateA(false);

  // Pagination — index renders only the first `visibleCount` rows.
  // LOAD MORE bumps by PAGE_SIZE; RESET ALL / filter changes reset to PAGE_SIZE.
  const PAGE_SIZE = 30;
  const [visibleCount, setVisibleCount] = useStateA(PAGE_SIZE);

  // Page routing — "home" is the split index/detail view; static pages
  // (about / service / contact) replace the GNB-left tabs with a Back button
  // and render centered body content.
  const [currentPage, setCurrentPage] = useStateA("home");
  const isHome = currentPage === "home";
  const goHome = () => setCurrentPage("home");
  const navigateTo = (id) => setCurrentPage(id);

  // --- counts (for GNB tabs) ------------------------------------------------
  const typeCounts = useMemoA(() => {
    const c = {};
    TYPE_TABS.forEach((t) => {c[t.id] = 0;});
    ROWS.forEach((r) => {if (c[r.type] !== undefined) c[r.type] += 1;});
    return c;
  }, [ROWS, TYPE_TABS]);

  // --- counts (for series chips) -------------------------------------------
  const seriesCounts = useMemoA(() => {
    const c = {};
    ROWS.forEach((r) => { if (r.series) c[r.series] = (c[r.series] || 0) + 1; });
    return c;
  }, [ROWS]);

  // --- filtered rows --------------------------------------------------------
  // Order preserved from ROWS (data.jsx pre-shuffles via Fisher-Yates so
  // categories mix). Filters narrow but don't re-sort.
  const filteredRows = useMemoA(() => {
    return ROWS.filter((r) => {
      if (typeFilter && r.type !== typeFilter) return false;
      if (seriesFilter && r.series !== seriesFilter) return false;
      if (tagFilter) {
        const tags = (r.body && r.body.tags) || [];
        if (!tags.includes(tagFilter)) return false;
      }
      if (search) {
        const q = search.toLowerCase();
        const hay = (r.title + " " + r.meta + " " + r.type + " " + (r.series || "")).toLowerCase();
        if (!hay.includes(q)) return false;
      }
      return true;
    });
  }, [ROWS, typeFilter, seriesFilter, tagFilter, search]);

  // Sliced for index display
  const pagedRows = useMemoA(
    () => filteredRows.slice(0, visibleCount),
    [filteredRows, visibleCount]
  );
  const hasMore = filteredRows.length > visibleCount;

  // Reset pagination whenever the filter narrows/changes — fresh page from top.
  useEffectA(() => {
    setVisibleCount(PAGE_SIZE);
  }, [typeFilter, seriesFilter, tagFilter, search]);

  // --- right pane row -------------------------------------------------------
  // hover overrides selected for preview
  const previewRow = useMemoA(() => {
    if (hoveredId) {
      if (hoveredId === HERO_ROW.id) return HERO_ROW;
      return ROWS.find((r) => r.id === hoveredId) || HERO_ROW;
    }
    if (selectedId === HERO_ROW.id) return HERO_ROW;
    return ROWS.find((r) => r.id === selectedId) || HERO_ROW;
  }, [hoveredId, selectedId, ROWS, HERO_ROW]);

  const onReset = () => {
    setTypeFilter(null);
    setSeriesFilter(null);
    setTagFilter(null);
    setSearch("");
    setSelectedId(HERO_ROW.id);
    setHoveredId(null);
    setVisibleCount(PAGE_SIZE);
  };

  const onLoadMore = () => setVisibleCount((c) => c + PAGE_SIZE);

  // Tag chip click — toggles tagFilter without resetting selectedId so the
  // user keeps viewing the row whose tag they just clicked.
  const handleTagClick = (tag) => {
    setTagFilter((prev) => (prev === tag ? null : tag));
  };

  // --- filter cascade handlers --------------------------------------------
  // TYPE click: toggle off → also clear series. Switch type → also clear series.
  const handleTypeClick = (id) => {
    if (typeFilter === id) {
      setTypeFilter(null);
      setSeriesFilter(null);
    } else {
      setTypeFilter(id);
      setSeriesFilter(null);
    }
    setSelectedId(HERO_ROW.id);
  };
  // SERIES click: toggle off, OR set series + ensure parent type matches.
  const handleSeriesClick = (id, parentType) => {
    if (seriesFilter === id) {
      setSeriesFilter(null);
    } else {
      setSeriesFilter(id);
      if (parentType && typeFilter !== parentType) setTypeFilter(parentType);
    }
    setSelectedId(HERO_ROW.id);
  };

  // split ratio CSS var
  const splitMap = { "50:50": "1fr 1fr", "60:40": "1.5fr 1fr", "70:30": "2.33fr 1fr" };
  const gridCols = splitMap[tweaks.split] || splitMap["60:40"];

  // dark mode body class
  useEffectA(() => {
    document.documentElement.dataset.theme = tweaks.darkMode ? "dark" : "light";
  }, [tweaks.darkMode]);

  return (
    <div className="app" style={{ "--split-cols": gridCols }}>
      {/* Page-background vertical guide lines. Positions / parallax timing
          come from the PAGE_GUIDES config at the top of this file. */}
      <div className="page-guides" aria-hidden="true">
        {PAGE_GUIDES.map((g) =>
          <span
            key={g.id}
            className={`page-guides__line ${g.rail ? "page-guides__line--rail" : ""}`}
            style={{
              "--target-left": g.target,
              "--guide-delay": `${g.delay}ms`,
              "--guide-duration": `${g.duration}ms`,
            }} />
        )}
      </div>

      {/* GNB top — single row spanning full width.
          GNB-left renders TopTabs+SeriesChipsRow on home, GnbBack on
          service/contact placeholders, and stays empty on About (Back is
          inside the About body's header row instead). */}
      <header className="gnb-top">
        <div className="gnb-top__left" onMouseLeave={() => setHoveredTypeTab(null)}>
          {isHome &&
          <React.Fragment>
              <TopTabs
              typeTabs={TYPE_TABS}
              typeCounts={typeCounts}
              typeFilter={typeFilter}
              onTypeClick={handleTypeClick}
              openSearch={() => setSearchOpen(true)}
              onTabHover={setHoveredTypeTab} />

              <SeriesChipsRow
              visible={!!(hoveredTypeTab || typeFilter)}
              series={SERIES_BY_TYPE[hoveredTypeTab || typeFilter] || []}
              parentType={hoveredTypeTab || typeFilter}
              seriesFilter={seriesFilter}
              seriesCounts={seriesCounts}
              onSeriesClick={handleSeriesClick} />
            </React.Fragment>
          }
          {/* about / service / contact: GNB-left empty (Back is in body header) */}
        </div>
        <div className="gnb-top__center"><GoyoLogo onClick={goHome} /></div>
        <div className="gnb-top__right">
          <GnbRight currentPage={currentPage} onNavigate={navigateTo} />
        </div>
      </header>

      {isHome &&
      <main className="split">
          <LeftPane
          rows={pagedRows}
          hoveredId={hoveredId}
          setHoveredId={setHoveredId}
          selectedId={selectedId}
          setSelectedId={setSelectedId}
          hoverStyle={tweaks.hoverStyle}
          onReset={onReset}
          onLoadMore={onLoadMore}
          hasMore={hasMore} />

          <RightPane
          row={previewRow}
          transitionMode={tweaks.transition}
          onTagClick={handleTagClick}
          activeTag={tagFilter} />
        </main>
      }
      {currentPage === "about" && <AboutPage goHome={goHome} />}
      {currentPage === "service" && <ServicePage goHome={goHome} />}
      {currentPage === "contact" && <ContactPage goHome={goHome} />}

      <SiteFooter />

      <SearchModal
        open={searchOpen}
        query={search}
        setQuery={setSearch}
        onClose={() => setSearchOpen(false)}
        results={filteredRows}
        onSelectResult={(id) => {
          setSelectedId(id);
          setSearchOpen(false);
          // If the picked row is past the current page boundary, expand
          // visibleCount so it shows up in the index list.
          const idx = filteredRows.findIndex((r) => r.id === id);
          if (idx >= 0) setVisibleCount((c) => Math.max(c, idx + 1));
        }} />
      
    </div>);

}

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