/* Cold-lander vehicle pages (BACKLOG #35) — the SEO surfaces a visitor lands on
   from Google ("قطع تويوتا", "قطع كامري"). Same design language + shared
   components as the warm home; the COLD variant completes the car, then hands
   off to the warm anchored home.

   Real production-shaped routes, dispatched in index.html's /m- branch:
     • Brand  /m-{brand}                → ColdLanderBrand
     • Model  /m-{brand}/{model}        → ColdLanderModel
     • Year   /m-{brand}/{model}/{year} → AnchoredHome (car from the URL, not a lander)

   Own files (app-coldlander.jsx/.css) per the new-flows convention; shared bits
   (hero, popular rail, FAQ, link chips) factored into ClHero/ClChips/etc.
   Prototype uses the hash form (#/m-…); production drops the #. See
   doc/COLD_LANDER.md. */

// Production-shaped URL slugs (latin: /m-{brand}/{model}/{year}). Brands mapped
// in full; Toyota's models get clean slugs, every other model falls back to an
// encoded-Arabic slug so any car still routes (production swaps in its real
// latin slugs later). brandFromSlug/modelFromSlug reverse them at dispatch.
const CL_BRAND_SLUGS = {
  "تويوتا": "toyota", "هيونداي": "hyundai", "كيا": "kia", "نيسان": "nissan", "هوندا": "honda",
  "مازدا": "mazda", "فورد": "ford", "شيفروليه": "chevrolet", "إم جي": "mg", "جيلي": "geely",
  "ميتسوبيشي": "mitsubishi", "شانجن": "changan", "لكزس": "lexus", "جيب": "jeep", "بي إم دبليو": "bmw",
};
const CL_MODEL_SLUGS = {
  "كامري": "camry", "كورولا": "corolla", "لاندكروزر": "land-cruiser", "هايلكس": "hilux",
  "فورتشنر": "fortuner", "RAV4": "rav4", "هايلاندر": "highlander", "أفالون": "avalon",
  "بريوس": "prius", "يارس": "yaris", "هايس": "hiace", "اينوفا": "innova", "أوريون": "aurion",
  "FJ": "fj", "لاندكروزر برادو": "prado", "سيكويا": "sequoia", "كرسيدا": "cressida",
  "كوستر": "coaster", "تندرا": "tundra", "شاص": "shaas", "بريفيا": "previa", "4Runner": "4runner",
  "كراون": "crown", "ايكو": "echo",
};
const clBrandSlug = (n) => CL_BRAND_SLUGS[n] || encodeURIComponent(n);
const clModelSlug = (n) => CL_MODEL_SLUGS[n] || encodeURIComponent(n);
const clBrandFromSlug = (s) => Object.keys(CL_BRAND_SLUGS).find((k) => CL_BRAND_SLUGS[k] === s) || decodeURIComponent(s);
const clModelFromSlug = (s) => Object.keys(CL_MODEL_SLUGS).find((k) => CL_MODEL_SLUGS[k] === s) || decodeURIComponent(s);
const clBrandPath = (b) => "/m-" + clBrandSlug(b);
const clModelPath = (b, m) => "/m-" + clBrandSlug(b) + "/" + clModelSlug(m);
const clYearPath = (b, m, y) => "/m-" + clBrandSlug(b) + "/" + clModelSlug(m) + "/" + y;

const CL_CATEGORIES = [
  { id: "bumpers-grilles",    label: "الصدامات", icon: <IconBumper /> },
  { id: "suspension",         label: "المساعدات", icon: <IconShock /> },
  { id: "brakes",             label: "الفرامل", icon: <IconBrakeDisc /> },
  { id: "ac",                 label: "التكييف", icon: <IconAC /> },
  { id: "lighting",           label: "الإضاءة", icon: <IconHeadlight /> },
  { id: "engine-trans",       label: "المحرك", icon: <IconEngine /> },
  { id: "filters",            label: "الفلاتر", icon: <IconOil /> },
  { id: "electrical-battery", label: "الكهرباء", icon: <IconBolt /> },
];

// Prototype catalog is car-agnostic, so the same sample keys stand in for every
// vehicle's "popular parts" rail.
const CL_POPULAR = ["bumperR", "grille", "pads", "shockR", "bumperL", "bracketR"];

/* Cold hero, in the cold-first-timer order: orient (crumb, H1, one-line lead),
   then a trust strip, then the primary action. A stranger from Google needs to
   know they're in the right place and that we're legit BEFORE we ask them to
   pick a car — so trust precedes the action. crumb is an array of {label, to?};
   img is optional (brand pages have none). */
function ClHero({ crumb, img, h1, lead, action }) {
  return (
    <section className="a-section cl-hero-sec">
      <nav className="pdp-crumb" aria-label="breadcrumb">
        {crumb.map((seg, i) => (
          <React.Fragment key={i}>
            {i > 0 && <span className="pdp-crumb-sep">/</span>}
            {seg.to
              ? <a className="pdp-crumb-link" onClick={() => navigate(seg.to)}>{seg.label}</a>
              : <span className="pdp-crumb-link">{seg.label}</span>}
          </React.Fragment>
        ))}
      </nav>
      {img && <img className="cl-car-img" src={img} alt={h1} />}
      <h1 className="cl-h1">{h1}</h1>
      {lead && <p className="cl-lead">{lead}</p>}
      <div className="anchor-trust"><div className="anchor-trust-inner"><TrustBadges /></div></div>
      {action}
    </section>
  );
}

/* Lower SEO body copy ("about"), placed beneath the browse blocks so the
   descriptive content feeds the page without pushing the action down. */
function ClAbout({ paras }) {
  return (
    <section className="a-section cl-about">
      {paras.map((p, i) => <p className="cl-intro" key={i}>{p}</p>)}
    </section>
  );
}


function ClCategories({ title = "تصفّح حسب القسم", brand, model }) {
  // On a cold lander, a category tile goes to the car-gated category lander,
  // scoped to whatever car dims are already known (?cold=1&brand=&model=).
  // Without a brand (warm context) it's the plain category page.
  const catLink = (id) => "/spare-parts/" + id + (brand ? "?cold=1&brand=" + clBrandSlug(brand) + (model ? "&model=" + clModelSlug(model) : "") : "");
  return (
    <section className="a-section">
      <div className="section-head"><h2 className="section-title">{title}</h2></div>
      <div className="cd-cats">
        {CL_CATEGORIES.map((c, i) => (
          <button key={i} className="cat-card" onClick={() => navigate(catLink(c.id))}>
            <span className="cat-icon">{c.icon}</span>
            <span className="cat-label">{c.label}</span>
          </button>
        ))}
      </div>
      <button className="home-browse-all cd-browse" onClick={() => navigate("/spare-parts/all")}>
        <span className="cd-browse-icon"><IconGrid /></span>
        <span className="home-browse-all-title">اكتشف كل القطع</span>
        <span className="home-browse-all-arrow"><IconChevron /></span>
      </button>
    </section>
  );
}

function ClPopular({ title }) {
  return (
    <section className="a-section" style={{ paddingInline: 0 }}>
      <div className="section-head" style={{ padding: "0 16px" }}>
        <h2 className="section-title">{title}</h2>
      </div>
      <div className="prod-rail">
        {CL_POPULAR.map((k, i) => <ProductCard key={k + i} p={findProduct(k)} onClick={() => navigate("/p/" + k)} />)}
      </div>
    </section>
  );
}

function ClFaq({ items, title = "أسئلة شائعة" }) {
  // Reuses the PDP FAQ look (compact list, rotating chevron, one open at a time).
  // Answers stay in the DOM (hidden toggle, not conditional render) so the FAQ
  // text remains crawlable for the FAQPage markup the SSR layer emits.
  const [openIndex, setOpenIndex] = React.useState(null);
  return (
    <section className="a-section">
      <div className="section-head"><h2 className="section-title">{title}</h2></div>
      <div className="pdp-faq">
        {items.map((item, i) => {
          const isOpen = openIndex === i;
          return (
            <div className={"pdp-faq-item" + (isOpen ? " is-open" : "")} key={i}>
              <button type="button" className="pdp-faq-q" onClick={() => setOpenIndex(isOpen ? null : i)} aria-expanded={isOpen}>
                <span className="pdp-faq-q-text">{item.q}</span>
                <span className="pdp-faq-q-chev" aria-hidden="true"><IconChevron /></span>
              </button>
              <div className="pdp-faq-a" hidden={!isOpen}>{item.a}</div>
            </div>
          );
        })}
      </div>
    </section>
  );
}

// Smooth collapse: clip a grid to `collapseAfter` items via an animated
// max-height. All items stay in the DOM (SEO-safe) — the overflow is clipped,
// not removed. JS-measured so it works with the variable grid/tile heights.
function useCollapse(enabled, expanded, collapseAfter) {
  const ref = React.useRef(null);
  React.useLayoutEffect(() => {
    const grid = ref.current;
    if (!grid) return;
    if (!enabled) { grid.style.maxHeight = ""; return; }
    if (expanded) { grid.style.maxHeight = grid.scrollHeight + "px"; return; }
    const boundary = grid.children[collapseAfter - 1];
    grid.style.maxHeight = boundary
      ? (boundary.getBoundingClientRect().bottom - grid.getBoundingClientRect().top) + "px"
      : "";
  }, [enabled, expanded, collapseAfter]);
  return ref;
}

// Internal-link blocks. items = array of {label, to?}; a `to` navigates (used to
// link the vehicle pages together). `tile` = the brand page's models grid.
function ClChips({ title, items, tile, head2, collapseAfter }) {
  const [expanded, setExpanded] = React.useState(false);
  // SEO-safe collapse: ALL items always render (crawlable links stay in the DOM);
  // collapsed = clipped by max-height, never fetched/injected on click.
  const collapsible = tile && collapseAfter && items.length > collapseAfter;
  const gridRef = useCollapse(collapsible, expanded, collapseAfter);
  return (
    <section className={"a-section" + (head2 ? " cl-linkblock" : "")}>
      <div className="section-head"><h2 className="section-title">{title}</h2></div>
      <div ref={gridRef} className={(tile ? "cl-tile-grid" : "cl-links") + (collapsible ? " cl-collapse" : "")}>
        {items.map((it, i) => (
          <button key={i} className={tile ? "cl-tile" : "cl-link-chip"} onClick={it.to ? () => navigate(it.to) : undefined}>{it.label}</button>
        ))}
      </div>
      {collapsible && (
        <button className={"cl-expand" + (expanded ? " is-open" : "")} onClick={() => setExpanded((e) => !e)}>
          {expanded ? "عرض أقل" : "عرض كل السيارات (" + items.length + ")"}
          <IconChevron />
        </button>
      )}
    </section>
  );
}

// Prominent visual years picker (model page). Each tile drills to the year page.
function ClYears({ years, onPick, title = "اختر سنة الموديل", collapseAfter }) {
  const [expanded, setExpanded] = React.useState(false);
  // Same SEO-safe smooth collapse as the models grid. At 4 columns,
  // collapseAfter=12 => 3 rows.
  const collapsible = collapseAfter && years.length > collapseAfter;
  const gridRef = useCollapse(collapsible, expanded, collapseAfter);
  return (
    <section className="a-section">
      <div className="section-head"><h2 className="section-title">{title}</h2></div>
      <div ref={gridRef} className={"cl-years" + (collapsible ? " cl-collapse" : "")}>
        {years.map((y, i) => (
          <button key={i} className="cl-year" onClick={() => onPick && onPick(y)}>{y}</button>
        ))}
      </div>
      {collapsible && (
        <button className={"cl-expand" + (expanded ? " is-open" : "")} onClick={() => setExpanded((e) => !e)}>
          {expanded ? "عرض أقل" : "عرض كل السنوات (" + years.length + ")"}
          <IconChevron />
        </button>
      )}
    </section>
  );
}

/* The year level (/m-{brand}/{model}/{year}) is NOT a cold lander — the car is
   fully specified, so that URL renders the warm AnchoredHome for the car (see
   index.html's /m- dispatch and doc/COLD_LANDER.md §5). No component here. */

/* ---- Layer 2: Model page  /m-{brand}/{model} ---- */
function ColdLanderModel({ brand = "تويوتا", model = "كامري", onSearch, onSelectCar }) {
  const name = brand + " " + model;
  const years = (typeof YEARS !== "undefined" ? YEARS : []).map((y) => String(2000 + y));
  const faqs = [
    { q: "أي سنة من " + model + " أختار؟",
      a: "اختر سنة موديل سيارتك من الشبكة أعلاه، ونعرض لك القطع المتوافقة مع تلك السنة تحديداً." },
    { q: "هل تختلف القطع بين سنوات " + model + "؟",
      a: "بعض القطع تتغير بين الأجيال والسنوات، لذلك نربط كل قطعة بالسنوات المتوافقة معها لتجنّب الشراء الخاطئ." },
    { q: "هل توفّرون قطع أصلية وبديلة؟",
      a: "نعم، لكل قطعة الخيار الأصلي (وكالة) والبدائل المتوفرة بأسعارها." },
    { q: "كم تستغرق مدة التوصيل؟",
      a: "التوصيل خلال 2-4 أيام عمل داخل المملكة." },
  ];
  return (
    <div className="cl-page">
      <ClHero
        crumb={[{ label: "الرئيسية", to: "/" }, { label: brand, to: clBrandPath(brand) }, { label: model }]}
        h1={"قطع غيار " + name}
      />
      {/* Make + model are known from the URL, so the only decision is the year.
          The years grid IS the primary action: tap a year → complete the car →
          warm home. (Brand = full selector, Model = years grid, Year = confirm
          CTA — each page's action sized to what's left to pick.) In production
          each tile is an <a href="/m-brand/model/{year}"> so the year-page link
          graph survives; the tap completes the car. */}
      <ClYears
        years={years}
        title="اختر سنة سيارتك"
        collapseAfter={12}
        onPick={(y) => { if (onSelectCar) onSelectCar(carName(brand, model) + " " + y); navigate("/car"); }}
      />
      <ClCategories title={"أقسام قطع " + model} brand={brand} model={model} />
      <ClPopular title={"الأكثر طلباً ل" + model} />
      <ClAbout paras={[
        "قطع غيار " + name + " لجميع السنوات: أصلية (وكالة) وبديلة بأسعار واضحة. بعض القطع تختلف بين الأجيال والسنوات، لذلك نربط كل قطعة بالسنوات المتوافقة معها.",
        "اختر سنة الموديل لعرض القطع المتوافقة تماماً مع سيارتك، مع التحقق من التوافق قبل الشحن.",
      ]} />
      <ClFaq items={faqs} title={"أسئلة شائعة عن قطع " + model} />
      <ClChips title={"سيارات " + brand + " أخرى"} items={((typeof MODELS !== "undefined" && MODELS[brand]) || []).filter((m) => m !== model).slice(0, 6).map((m) => ({ label: brand + " " + m, to: clModelPath(brand, m) }))} />
    </div>
  );
}

/* ---- Layer 1: Brand page  /m-{brand} ---- */
function ColdLanderBrand({ brand = "تويوتا", onSearch, onSelectCar }) {
  const models = (typeof MODELS !== "undefined" && MODELS[brand]) || ["كورولا", "يارس", "كامري", "لاندكروزر", "هايلكس", "فورتشنر", "RAV4", "هايلاندر", "أفالون", "بريوس"];
  const faqs = [
    { q: "ما الفرق بين قطع " + brand + " الأصلية (وكالة) والبديلة؟",
      a: "الأصلية (وكالة) من " + brand + " مباشرة بمواصفات المصنع، والبديلة من مصنّعين موثوقين بجودة عالية وسعر أقل. لكل قطعة نعرض الخيارين لتختار حسب ميزانيتك." },
    { q: "هل تغطّون كل سيارات " + brand + "؟",
      a: "نغطّي أشهر سيارات " + brand + " في السعودية لمعظم سنوات الموديل. اختر سيارتك من الأعلى لعرض القطع المتوافقة." },
    { q: "كيف ألقى رقم القطعة الأصلي لسيارتي " + brand + "؟",
      a: "رقم القطعة الأصلي معروض في صفحة كل قطعة، وتقدر تطابقه مع الرقم على القطعة القديمة أو تستخرجه من الوكالة برقم الشاسيه (VIN)." },
    { q: "كم تستغرق مدة التوصيل؟",
      a: "التوصيل خلال 2-4 أيام عمل داخل المملكة، مع تتبّع الطلب من حسابك." },
  ];
  return (
    <div className="cl-page">
      <ClHero
        crumb={[{ label: "الرئيسية", to: "/" }, { label: brand }]}
        h1={"قطع غيار " + brand + " الأصلية والبديلة"}
        action={<SelectorCard seedMake={brand} hideSaved title="اختر سيارتك لعرض القطع" onAddCar={onSelectCar} />}
      />
      <ClChips title={"تصفّح سيارات " + brand} items={models.map((m) => ({ label: m, to: clModelPath(brand, m) }))} tile collapseAfter={12} />
      <ClCategories title={"أقسام قطع " + brand} brand={brand} />
      <ClChips title="الأكثر بحثاً" items={models.slice(0, 5).map((m) => ({ label: brand + " " + m + " 2022", to: clYearPath(brand, m, "2022") }))} />
      <ClAbout paras={[
        "في سبيرو تلقى كل قطع غيار " + brand + " في مكان واحد: أصلية (وكالة) بمواصفات المصنع، وبدائل موثوقة بأسعار أوضح، لمعظم سيارات " + brand + " وسنواتها في السعودية.",
        "نتحقق من توافق كل قطعة مع سيارتك قبل الشحن، مع توصيل خلال 2-4 أيام وإمكانية الإرجاع خلال 14 يوماً.",
      ]} />
      <ClFaq items={faqs} title={"أسئلة شائعة عن قطع " + brand} />
    </div>
  );
}

/* ---- Category lander  /spare-parts/{cat} (cold / partial-car) ----
   MOCKUP of the eventual Option B: the cold STATE of the real category page
   (DiscoverScreen). Selector-led — the car selector is the hero at the top (the
   one primary action, no repeated prompts), then the browse/refine chrome
   (search, term chips, filters), then the SAME product grid (CARID-style: the
   category's catalog is always shown; the selector narrows to confirmed-fit, it
   isn't a gate), then SEO copy at the very bottom. Built in .dsc-* classes so it
   mimics warm; ports into a real isCold branch inside DiscoverScreen once signed
   off. Not yet wired to the landers' entry points (sandbox only). */
function ColdLanderCategory({ catId, brand, model, onSearch, onSelectCar }) {
  const cat = (typeof DISCOVER_CATEGORIES !== "undefined" ? DISCOVER_CATEGORIES.find((c) => c.id === catId) : null);
  const catLabel = (cat && cat.label) || catId;
  const catShort = (CL_CATEGORIES.find((c) => c.id === catId) || {}).label || catLabel;
  const scope = model ? " ل" + model : brand ? " ل" + brand : "";
  const resultsPath = "/spare-parts/" + catId;
  const [viewMode, setViewMode] = React.useState("grid");

  // Cold grid: the category's catalog, fitment NOT yet confirmed. Mirrors the
  // warm DiscoverScreen filter (cat === "part" && kind in category.kinds).
  const kinds = (cat && cat.kinds) || [];
  const baseResults = (typeof CATALOG !== "undefined" ? CATALOG : [])
    .filter((p) => p.cat === "part" && kinds.includes(p.kind))
    .map(enrichProduct)
    .filter(Boolean);
  // MOCKUP-ONLY: the prototype catalog holds only a handful of parts per category,
  // too few to page. Pad the grid to a realistic count (clones get a unique key;
  // clicking still routes to the real PDP via origKey) so pagination is
  // demonstrable. Drop at port time — real categories have hundreds of parts.
  const CL_CAT_SEED = 60;
  const results = baseResults.length
    ? Array.from({ length: Math.max(baseResults.length, CL_CAT_SEED) }, (_, i) => {
        const b = baseResults[i % baseResults.length];
        return i < baseResults.length ? b : { ...b, key: b.key + "__seed" + i, origKey: b.key };
      })
    : baseResults;

  // Pagination — same shape as the warm DiscoverScreen (DSC_PAGE_SIZE, shared
  // Paginator, jump-to-top on change) so the port carries over unchanged.
  const [page, setPage] = React.useState(1);
  const pageSize = typeof DSC_PAGE_SIZE !== "undefined" ? DSC_PAGE_SIZE : 12;
  const totalPages = Math.max(1, Math.ceil(results.length / pageSize));
  React.useEffect(() => { setPage(1); }, [catId]);
  const pagedResults = results.slice((page - 1) * pageSize, page * pageSize);
  const onPageChange = (next) => { setPage(next); window.scrollTo(0, 0); };

  const faqs = [
    { q: "كيف أتأكد أن القطعة تناسب سيارتي؟",
      a: "اختر سيارتك (الموديل والسنة) ونعرض لك القطع المتوافقة فقط، مع رقم القطعة الأصلي في كل صفحة." },
    { q: "هل توفّرون قطع أصلية وبديلة؟",
      a: "نعم، لكل قطعة الخيار الأصلي (وكالة) والبدائل الموثوقة بأسعارها الواضحة." },
    { q: "كم تستغرق مدة التوصيل؟",
      a: "التوصيل خلال 2-4 أيام عمل داخل المملكة، مع تتبّع الطلب." },
  ];

  return (
    <div className="dsc cl-cat">
      {/* Orient: breadcrumb + the single page H1 (SEO). */}
      <nav className="dsc-crumb cl-cat-crumb" aria-label="breadcrumb">
        <a className="dsc-crumb-link" onClick={() => navigate("/")}>الرئيسية</a>
        <span className="dsc-crumb-sep">/</span>
        {brand && <React.Fragment>
          <a className="dsc-crumb-link" onClick={() => navigate(clBrandPath(brand))}>{brand}</a>
          <span className="dsc-crumb-sep">/</span>
        </React.Fragment>}
        {model && <React.Fragment>
          <a className="dsc-crumb-link" onClick={() => navigate(clModelPath(brand, model))}>{model}</a>
          <span className="dsc-crumb-sep">/</span>
        </React.Fragment>}
        <span className="dsc-crumb-current">{catLabel}</span>
      </nav>
      <h1 className="cl-cat-h1">قطع {catShort}{scope}</h1>

      {/* Selector hero — the primary action, always open, seeded with the known
          dims. Completing it lands on the warm results (dest). */}
      <div className="cl-cat-hero-sel">
        <SelectorCard seedMake={brand || undefined} seedModel={model || undefined} hideSaved dest={resultsPath} title="اختر سيارتك لعرض القطع" onAddCar={onSelectCar} />
      </div>

      {/* Browse / refine chrome — term chips + filters (search omitted cold: the
          selector hero owns the top, and browse intent is served by the chips). */}
      <div className="stick-anchor dsc-anchor cl-cat-chrome">
        <div className="dsc-cluster-collapse"><div className="dsc-cluster-inner">
          {cat && cat.terms && <TermsRail category={cat} activeTerm={null} onSelect={() => {}} />}
        </div></div>

        {/* Filter controls — present but inert in the mockup (they narrow the
            grid; the view toggle is live so we can see both layouts). */}
        <div className="dsc-controls">
          <button className="dsc-control dsc-pos-control" onClick={() => {}} aria-label="تصفية حسب الجهة">
            <span className="dsc-pos-glyph"><PositionGlyph pos={null} /></span>
            <span>الجهة</span>
          </button>
          <button className="dsc-control" onClick={() => {}}><IconFunnel /> <span>الفلتر</span></button>
          <div className="dsc-view-spacer" />
          <div className="view-toggle">
            <button data-active={viewMode === "grid" || undefined} onClick={() => setViewMode("grid")} aria-label="عرض كشبكة"><IconGrid /></button>
            <button data-active={viewMode === "row" || undefined} onClick={() => setViewMode("row")} aria-label="عرض كقائمة"><IconRows /></button>
          </div>
        </div>
      </div>

      {viewMode === "grid" ? (
        <div className="dsc-grid">
          {pagedResults.map((p) => <ProductCard key={p.key} p={p} onClick={() => navigate("/p/" + (p.origKey || p.key))} />)}
        </div>
      ) : (
        <div className="dsc-list">
          {pagedResults.map((p) => <SearchResultRow key={p.key} p={p} onClick={() => navigate("/p/" + (p.origKey || p.key))} />)}
        </div>
      )}
      <Paginator page={page} totalPages={totalPages} onChange={onPageChange} />

      <ClAbout paras={[
        "اطلب قطع " + catShort + scope + " من سبيرو: أصلية (وكالة) وبديلة موثوقة بأسعار واضحة. اختر سيارتك لعرض القطع المتوافقة تماماً معها.",
        "نتحقق من التوافق قبل الشحن، مع توصيل خلال 2-4 أيام وإمكانية الإرجاع خلال 14 يوماً.",
      ]} />
      <ClFaq items={faqs} title={"أسئلة شائعة عن " + catShort} />
    </div>
  );
}

// Exposed for index.html's /m-* route dispatch (this file loads after the main
// script, so the dispatch reads window.CL at render + the nudge re-dispatches
// once it's registered — same load-order trick the demos used).
window.CL = Object.assign(window.CL || {}, {
  Brand: ColdLanderBrand,
  Model: ColdLanderModel,
  Category: ColdLanderCategory,
  brandFromSlug: clBrandFromSlug,
  modelFromSlug: clModelFromSlug,
  brandSlug: clBrandSlug,
});
window.dispatchEvent(new Event("hashchange"));
