/* ═══════════════════════════════════════════════════════════════════════════
   PORTAL DE TRANSPARÈNCIA — public document archive.

   LIVE since F1: GET /transparencia feeds the whole portal (59 published
   documents; size=100 covers it in one page). `DOCS` below stays as the
   eternal fallback (INT-1). Live items carry their own `title` (the legacy
   import is ca-only — INT-6 exemption — so pickLang falls back to ca in the
   3 languages) and a flat `cat` from the CMS `tipus` taxonomy; seed entries
   instead derive their title from { kind, year } via TP_STRINGS[lang].kinds.
   Either way the category accordion + year filter are DERIVED from the data,
   so the portal re-navigates itself as documents are published.
   ═══════════════════════════════════════════════════════════════════════════ */

const { useState, useEffect, useMemo } = React;
const EsbartNav = window.EsbartNav;
const EsbartFooter = window.EsbartFooter;
const EsbartTweaksPanel = window.EsbartTweaksPanel;
const EsbartAccordion = window.EsbartAccordion;

const ESBART_TWEAKS = /*EDITMODE-BEGIN*/{ "accentHue": 350, "typeScale": 1 }/*EDITMODE-END*/;
window.ESBART_TWEAKS = ESBART_TWEAKS;

/* Category display order (language-independent keys): first the live CMS
   `tipus` taxonomy, then the seed taxonomy — only the categories present in
   the current data render as chips, so the two sets never mix on screen. */
const CAT_ORDER = [
  "actes", "memoria", "comptes", "governanca", "socis",
  "assemblees", "memories", "economia", "projectes", "normativa",
];

/* ─── The archive (CMS-backed in production) ─────────────────────────────── */
const DOCS = [
  // — Assemblea general 27/06/2025 —
  { kind: "convocatoria", cat: "assemblees", year: 2025, date: "27/06/2025", url: "https://www.esbartcatala.org/doc/3146/Convocat%C3%B2ria%20assemblea%20ordin%C3%A0ria%2027%20de%20juny.pdf" },
  { kind: "acta", cat: "assemblees", year: 2024, date: "28/04/2024", url: "https://www.esbartcatala.org/doc/3140/Acta%20Assemblea%2020240428.pdf" },
  { kind: "projecte", cat: "projectes", year: 2025, url: "https://www.esbartcatala.org/doc/3142/Projecte%202025%20.pdf" },
  { kind: "pressupost", cat: "economia", year: 2025, url: "https://www.esbartcatala.org/doc/3144/Pressupost%202025.pdf" },
  { kind: "memoria-economica", cat: "economia", year: 2024, url: "https://www.esbartcatala.org/doc/3141/Mem%C3%B2ria%2020250315%20.pdf" },

  // — Memòries d'activitats (sèrie anual) —
  { kind: "memoria-activitats", cat: "memories", year: 2024, url: "https://www.esbartcatala.org/doc/3143/Mem%C3%B2ria%2020250315%20.pdf" },
  { kind: "memoria-activitats", cat: "memories", year: 2023, url: "https://www.esbartcatala.org/doc/3037/Mem%C3%B2ria%202023%20202404.pdf" },
  { kind: "memoria-activitats", cat: "memories", year: 2022, url: "https://www.esbartcatala.org/doc/3030/Mem%C3%B2ria%202022%2020230508%20MG.pdf" },
  { kind: "memoria-activitats", cat: "memories", year: 2021, url: "https://www.esbartcatala.org/doc/2832/Mem%C3%B2ria%20Activitats%202021.pdf" },
  { kind: "memoria-activitats", cat: "memories", year: 2020, url: "https://www.esbartcatala.org/doc/2696/2020%20Mem%C3%B2ria.pdf" },
  { kind: "memoria-activitats", cat: "memories", year: 2019, url: "https://www.esbartcatala.org/doc/2436/2019%20Mem%C3%B2ria.pdf" },
  { kind: "memoria-activitats", cat: "memories", year: 2018, url: "https://www.esbartcatala.org/doc/2024/2018%20Mem%C3%B2ria%20V2.pdf" },
  { kind: "memoria-activitats", cat: "memories", year: 2017, url: "https://www.esbartcatala.org/doc/1890/2017%20Mem%C3%B2ria.pdf" },
  { kind: "memoria-activitats", cat: "memories", year: 2016, url: "https://www.esbartcatala.org/doc/1744/2016%20Mem%C3%B2ria.pdf" },
  { kind: "memoria-activitats", cat: "memories", year: 2015, url: "https://www.esbartcatala.org/doc/1353/2015%20Mem%C3%B2ria%20Final.pdf" },
  { kind: "memoria-activitats", cat: "memories", year: 2014, url: "http://www.esbartcatala.org//img/440/2014%20Mem%C3%B2ria%20d'Activitats.pdf" },
  { kind: "activitats-historic", cat: "memories", year: 2023, span: "1908–2023", url: "https://www.esbartcatala.org/doc/3029/Activitats%201908-2023%20Actualitzat.pdf" },

  // — Normativa —
  { kind: "estatuts", cat: "normativa", year: 2022, url: "https://www.esbartcatala.org/doc/2889/Estatuts%20ECD%202022.pdf" },
];

/* The Estatuts get a standalone featured block; the rest feed the searchable
   archive. Seed marks them with kind="estatuts", the CMS with tipus="estatuts"
   (mapped to `cat`) — the split happens inside App, on whichever is live. */
const isEstatuts = (d) => d.kind === "estatuts" || d.cat === "estatuts";

/* ─── i18n ─────────────────────────────────────────────────────────────────── */
const TP_STRINGS = {
  ca: {
    pageTitle: "Portal de Transparència · Esbart Català de Dansaires",
    eyebrow: "Transparència",
    title: ["Portal de ", "Transparència"],
    lead: "L'Esbart Català de Dansaires posa a disposició dels socis i de la ciutadania la documentació institucional, econòmica i de govern de l'entitat. Tots els documents són públics i descarregables.",
    estEyebrow: "Document fundacional",
    estTitle: "Estatuts",
    estLead: "Els Estatuts són la norma que regeix l'Esbart Català de Dansaires: defineixen els fins de l'entitat, els drets i deures dels socis i el funcionament dels seus òrgans de govern.",
    estCta: "Consultar els Estatuts",
    estMeta: "Vigents des de 2022 · PDF",
    metaDocs: "documents públics",
    metaUpdated: "Documents fins al {year}",
    yearAll: "Tots els anys",
    searchPh: "Cerca per títol o any…",
    empty: "No s'ha trobat cap document amb aquests filtres.",
    download: "Descarregar",
    countOne: "document",
    countMany: "documents",
    footNote: "Els documents s'obren en format PDF en una pestanya nova. Per sol·licitar informació o documentació addicional, escriviu a info@esbartcatala.org.",
    cats: { actes: "Actes", memoria: "Memòries", comptes: "Comptes", governanca: "Governança", socis: "Socis", assemblees: "Assemblees", memories: "Memòries d'activitats", economia: "Economia", projectes: "Projectes", normativa: "Normativa" },
    kinds: {
      "memoria-activitats": { label: "Memòria d'activitats", desc: "Resum de l'activitat de l'any: espectacles, programació, formació i recerca." },
      "memoria-economica": { label: "Memòria econòmica", desc: "Balanç econòmic de l'exercici, amb ingressos i despeses detallats." },
      "pressupost": { label: "Pressupost", desc: "Previsió d'ingressos i despeses aprovada per a l'exercici." },
      "projecte": { label: "Projecte d'activitats", desc: "Pla d'activitats i objectius previstos per a l'any." },
      "convocatoria": { label: "Convocatòria d'assemblea", desc: "Ordre del dia i convocatòria oficial de l'Assemblea General." },
      "acta": { label: "Acta d'assemblea", desc: "Acta aprovada de l'Assemblea General anterior." },
      "estatuts": { label: "Estatuts", desc: "Estatuts vigents que regeixen l'entitat." },
      "activitats-historic": { label: "Recull d'activitats", desc: "Recull cronològic de l'activitat de l'Esbart des de la seva fundació." },
    },
  },
  es: {
    pageTitle: "Portal de Transparencia · Esbart Català de Dansaires",
    eyebrow: "Transparencia",
    title: ["Portal de ", "Transparencia"],
    lead: "El Esbart Català de Dansaires pone a disposición de socios y ciudadanía la documentación institucional, económica y de gobierno de la entidad. Todos los documentos son públicos y descargables.",
    estEyebrow: "Documento fundacional",
    estTitle: "Estatutos",
    estLead: "Los Estatutos son la norma que rige el Esbart Català de Dansaires: definen los fines de la entidad, los derechos y deberes de los socios y el funcionamiento de sus órganos de gobierno.",
    estCta: "Consultar los Estatutos",
    estMeta: "Vigentes desde 2022 · PDF",
    metaDocs: "documentos públicos",
    metaUpdated: "Documentos hasta {year}",
    yearAll: "Todos los años",
    searchPh: "Buscar por título o año…",
    empty: "No se ha encontrado ningún documento con estos filtros.",
    download: "Descargar",
    countOne: "documento",
    countMany: "documentos",
    footNote: "Los documentos se abren en formato PDF en una pestaña nueva. Para solicitar información o documentación adicional, escribe a info@esbartcatala.org.",
    cats: { actes: "Actas", memoria: "Memorias", comptes: "Cuentas", governanca: "Gobernanza", socis: "Socios", assemblees: "Asambleas", memories: "Memorias de actividades", economia: "Economía", projectes: "Proyectos", normativa: "Normativa" },
    kinds: {
      "memoria-activitats": { label: "Memoria de actividades", desc: "Resumen de la actividad del año: espectáculos, programación, formación e investigación." },
      "memoria-economica": { label: "Memoria económica", desc: "Balance económico del ejercicio, con ingresos y gastos detallados." },
      "pressupost": { label: "Presupuesto", desc: "Previsión de ingresos y gastos aprobada para el ejercicio." },
      "projecte": { label: "Proyecto de actividades", desc: "Plan de actividades y objetivos previstos para el año." },
      "convocatoria": { label: "Convocatoria de asamblea", desc: "Orden del día y convocatoria oficial de la Asamblea General." },
      "acta": { label: "Acta de asamblea", desc: "Acta aprobada de la Asamblea General anterior." },
      "estatuts": { label: "Estatutos", desc: "Estatutos vigentes que rigen la entidad." },
      "activitats-historic": { label: "Recopilación de actividades", desc: "Recopilación cronológica de la actividad del Esbart desde su fundación." },
    },
  },
  en: {
    pageTitle: "Transparency Portal · Esbart Català de Dansaires",
    eyebrow: "Transparency",
    title: ["Transparency ", "Portal"],
    lead: "The Esbart Català de Dansaires makes its institutional, financial and governance documentation available to members and the public. Every document is public and downloadable.",
    estEyebrow: "Founding document",
    estTitle: "Bylaws",
    estLead: "The bylaws are the rules that govern the Esbart Català de Dansaires: they define the entity's purposes, members' rights and duties, and how its governing bodies operate.",
    estCta: "View the bylaws",
    estMeta: "In force since 2022 · PDF",
    metaDocs: "public documents",
    metaUpdated: "Documents through {year}",
    yearAll: "All years",
    searchPh: "Search by title or year…",
    empty: "No documents match these filters.",
    download: "Download",
    countOne: "document",
    countMany: "documents",
    footNote: "Documents open as PDFs in a new tab. To request further information or documentation, write to info@esbartcatala.org.",
    cats: { actes: "Minutes", memoria: "Reports", comptes: "Finances", governanca: "Governance", socis: "Members", assemblees: "Assemblies", memories: "Activity reports", economia: "Finances", projectes: "Projects", normativa: "Governance" },
    kinds: {
      "memoria-activitats": { label: "Activity report", desc: "Summary of the year's activity: performances, programming, training and research." },
      "memoria-economica": { label: "Financial report", desc: "Financial balance for the year, with detailed income and expenses." },
      "pressupost": { label: "Budget", desc: "Forecast of income and expenditure approved for the year." },
      "projecte": { label: "Activity plan", desc: "Planned activities and objectives for the year." },
      "convocatoria": { label: "Assembly notice", desc: "Agenda and official notice of the General Assembly." },
      "acta": { label: "Assembly minutes", desc: "Approved minutes of the previous General Assembly." },
      "estatuts": { label: "Bylaws", desc: "The entity's current governing bylaws." },
      "activitats-historic": { label: "Activities compendium", desc: "A chronological record of the Esbart's activity since its founding." },
    },
  },
};

/* ─── Helpers ────────────────────────────────────────────────────────────── */
function docTitle(t, d) {
  if (d.title) return d.title; // live documents bring their own CMS title
  const k = t.kinds[d.kind];
  if (!k) return d.kind;
  if (d.span) return `${k.label} ${d.span}`;
  return `${k.label} ${d.year}`;
}
function docDesc(t, d) { return (t.kinds[d.kind] || {}).desc || ""; }
function catLabel(t, c) { return t.cats[c] || c; }

/* ─── Download glyph ─────────────────────────────────────────────────────── */
function DownIcon() {
  return (
    <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M12 3v12" /><path d="m7 10 5 5 5-5" /><path d="M5 21h14" />
    </svg>
  );
}

/* ─── Arrow glyph (featured CTA) ─────────────────────────────────────────── */
function ArrowIcon() {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M7 17 17 7" /><path d="M8 7h9v9" />
    </svg>
  );
}

/* ─── Featured Estatuts ──────────────────────────────────────────────────────
   The founding document gets a prominent, standalone treatment ABOVE the
   searchable archive — independent of the filters, search and results grid. */
function FeaturedEstatuts({ t, d }) {
  const bookRef = React.useRef(null);

  // 3D book choreography. The book is built from real CSS-3D faces (front
  // cover, spine, fore-edge of pages, back board + inner leaves). GSAP swings
  // the front cover open on its spine (rotationY around left center) and fans
  // the inner leaves out with a stagger. Hover/focus opens it on desktop;
  // on touch it auto-opens once when scrolled into view. Reduced-motion users
  // get the static, closed volume.
  React.useEffect(() => {
    if (!d) return;
    const root = bookRef.current;
    const g = window.gsap;
    if (!root || !g) return;

    const inner = root.querySelector(".tp-book__inner");
    const cover = root.querySelector(".tp-book__cover");
    const back = root.querySelector(".tp-book__face--back");
    const leaves = root.querySelectorAll(".tp-book__leaf");

    // Base 3D pose + depth stacking (all transform values live in GSAP so the
    // open/close tween never fights inline CSS transforms). Chrome paints the
    // cover's two coplanar faces in DOM order rather than by depth, so the
    // printed front would be hidden by the inside face — we keep the inside
    // face transparent while closed and fade it in as the cover passes edge-on.
    g.set(inner, { rotateY: -30, rotateX: 3 });
    g.set(cover, { transformOrigin: "left center", z: 13, rotateY: 0 });
    g.set(back, { opacity: 0 });
    g.set(leaves, { transformOrigin: "left center", z: (i) => 4 + i * 3, rotateY: 0 });

    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;

    const tl = g.timeline({ paused: true });
    tl.to(cover, { rotateY: -158, duration: 0.95, ease: "power3.inOut" }, 0)
      .to(back, { opacity: 1, duration: 0.12, ease: "none" }, 0.46)
      .to(leaves, { rotateY: (i) => -(35 + i * 38), duration: 0.9, ease: "power2.inOut", stagger: 0.06 }, 0.12);

    let opened = false;
    const open = () => { if (!opened) { opened = true; tl.play(); } };
    const close = () => { if (opened) { opened = false; tl.reverse(); } };

    const cleanups = [];
    if (window.matchMedia("(hover: hover)").matches) {
      root.addEventListener("mouseenter", open);
      root.addEventListener("mouseleave", close);
      root.addEventListener("focusin", open);
      root.addEventListener("focusout", close);
      cleanups.push(() => {
        root.removeEventListener("mouseenter", open);
        root.removeEventListener("mouseleave", close);
        root.removeEventListener("focusin", open);
        root.removeEventListener("focusout", close);
      });
    } else {
      const io = new IntersectionObserver((entries) => {
        entries.forEach((e) => { if (e.isIntersecting) open(); });
      }, { threshold: 0.55 });
      io.observe(root);
      cleanups.push(() => io.disconnect());
    }

    return () => { cleanups.forEach((fn) => fn()); tl.kill(); };
  }, [d]);

  if (!d) return null;
  return (
    <section className="tp-estatuts" aria-labelledby="estatuts-title">
      <div className="wrap">
        <div className="tp-estatuts__inner">
          <div className="tp-book" ref={bookRef}>
            <a className="tp-book__inner" href={d.url} target="_blank" rel="noopener"
               aria-label={`${t.estTitle} ${d.year} — PDF`}>
              <div className="tp-book__back" aria-hidden="true"></div>
              <div className="tp-book__spine" aria-hidden="true"></div>
              <div className="tp-book__foredge" aria-hidden="true"></div>
              <div className="tp-book__leaf" aria-hidden="true"><div className="tp-book__lines"></div></div>
              <div className="tp-book__leaf" aria-hidden="true"><div className="tp-book__lines"></div></div>
              <div className="tp-book__leaf" aria-hidden="true"><div className="tp-book__lines"></div></div>
              <div className="tp-book__cover">
                <div className="tp-book__face tp-book__face--front">
                  <img className="tp-book__crest" src="assets/logo-ecd.png" alt="" />
                  <span className="tp-book__rule" aria-hidden="true"></span>
                  <span className="tp-book__title">{t.estTitle}</span>
                  <span className="tp-book__year">{d.year}</span>
                </div>
                <div className="tp-book__face tp-book__face--back" aria-hidden="true"><div className="tp-book__lines tp-book__lines--inside"></div></div>
              </div>
            </a>
          </div>
          <div className="tp-estatuts__body">
            <div className="eyebrow eyebrow-white">{t.estEyebrow}</div>
            <h2 className="tp-estatuts__title" id="estatuts-title">{t.estTitle}</h2>
            <p className="tp-estatuts__lead">{t.estLead}</p>
            <div className="tp-estatuts__actions">
              <a className="btn light" href={d.url} target="_blank" rel="noopener">
                {t.estCta} <ArrowIcon />
              </a>
              <span className="tp-estatuts__meta">{t.estMeta}</span>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ─── Document card ──────────────────────────────────────────────────────── */
function DocCard({ t, d }) {
  return (
    <a className="doc-card"
       href={d.url} target="_blank" rel="noopener" title={docTitle(t, d)}>
      <div className="doc-card__top">
        <span className="doc-card__ext">PDF</span>
        <span className="doc-card__year">{d.span || d.year}</span>
      </div>
      <div className="doc-card__main">
        <h3 className="doc-card__title">{docTitle(t, d)}</h3>
        {d.date ? <div className="doc-card__date">{d.date}</div> : null}
        <p className="doc-card__desc">{docDesc(t, d)}</p>
      </div>
      <div className="doc-card__foot">
        <span className="doc-card__cat">{catLabel(t, d.cat)}</span>
        <span className="doc-card__dl">{t.download}<DownIcon /></span>
      </div>
    </a>
  );
}

/* ═══════════════════════════════════════════════════════════════════════════
   APP
   ═══════════════════════════════════════════════════════════════════════════ */
function App() {
  const { lang, setLang, t } = window.EsbartI18n.useI18n(TP_STRINGS);
  const tweaks = window.EsbartTweaks.useTweaks();

  const [year, setYear] = useState("all");
  const [q, setQ] = useState("");

  useEffect(() => { if (t.pageTitle) document.title = t.pageTitle; }, [lang, t]);

  // LIVE since F1 — the whole portal derives from `docs` (CMS or seed).
  const { items: docs } = window.EsbartData.useCollection(
    `transparencia?lang=${lang}&size=100`,
    DOCS,
    { map: (j) => j.items.map((d) => ({ id: d.id, title: d.title, cat: d.tipus, year: d.year, url: d.url })) }
  );
  const estatuts = useMemo(() => docs.find(isEstatuts), [docs]);
  const archive = useMemo(() => docs.filter((d) => !isEstatuts(d)), [docs]);

  // Browse mode groups the archive by category (GOV.UK accordion pattern):
  // the visitor scans a handful of headers instead of scrolling 58 cards,
  // and every header carries its honest count.
  const groups = useMemo(() => {
    const by = new Map();
    archive.forEach((d) => {
      if (!by.has(d.cat)) by.set(d.cat, []);
      by.get(d.cat).push(d);
    });
    return CAT_ORDER.filter((c) => by.has(c)).map((c) => ({
      id: `cat-${c}`, cat: c, docs: by.get(c).slice().sort((a, b) => b.year - a.year),
    }));
  }, [archive]);

  // Years present, newest first.
  const years = useMemo(() => {
    return Array.from(new Set(archive.map((d) => d.year))).sort((a, b) => b - a);
  }, [archive]);

  // When live data lands it swaps the taxonomy under the visitor's feet — an
  // active filter that no longer exists would silently show an empty archive.
  useEffect(() => {
    if (year !== "all" && !years.some((y) => String(y) === String(year))) setYear("all");
  }, [years]);

  const filtered = useMemo(() => {
    const needle = q.trim().toLowerCase();
    return archive
      .filter((d) => year === "all" || String(d.year) === String(year))
      .filter((d) => {
        if (!needle) return true;
        const hay = [docTitle(t, d), docDesc(t, d), catLabel(t, d.cat), d.year, d.date || "", d.span || ""].join(" ").toLowerCase();
        return hay.includes(needle);
      })
      .sort((a, b) => b.year - a.year);
  }, [archive, year, q, t]);

  const n = filtered.length;
  // Any active year/search switches to a flat, cross-category result grid;
  // without filters the accordion IS the navigation.
  const filtering = year !== "all" || q.trim() !== "";

  // Masthead entrance — once, on mount (GSAP writes inline styles; if GSAP is
  // absent or reduced-motion is on, content simply stays visible).
  useEffect(() => {
    const g = window.gsap;
    if (!g || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    g.from('.tp-hero .eyebrow, .tp-hero__title, .tp-hero__lead, .tp-hero__meta',
      { autoAlpha: 0, y: 18, duration: 0.7, ease: 'power3.out', stagger: 0.08 });
    g.from('.tp-book, .tp-estatuts__body .eyebrow, .tp-estatuts__title, .tp-estatuts__lead, .tp-estatuts__actions',
      { autoAlpha: 0, y: 20, duration: 0.7, ease: 'power3.out', stagger: 0.07, delay: 0.2 });
  }, []);

  // Result grid — staggered fade-up when the active year changes (a tactile
  // “results updated” feel). Search typing filters live without re-animating,
  // and in browse mode the accordion's own CSS transition does the choreography.
  useEffect(() => {
    if (!filtering) return;
    const g = window.gsap;
    if (!g || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const cards = document.querySelectorAll('.tp-grid--flat .doc-card');
    if (!cards.length) return;
    g.killTweensOf(cards);
    g.fromTo(cards, { autoAlpha: 0, y: 18 },
      { autoAlpha: 1, y: 0, duration: 0.5, ease: 'power3.out', stagger: { each: 0.04 }, clearProps: 'opacity,visibility,transform' });
  }, [year, archive]);

  return (
    <>
      <EsbartNav lang={lang} setLang={setLang} t={t} heroMode={false} current="sobre" />
      <main id="main-content" tabIndex={-1}>
        <header className="tp-hero">
          <div className="wrap">
            <div className="eyebrow">{t.eyebrow}</div>
            <h1 className="tp-hero__title">{t.title[0]}<em>{t.title[1]}</em></h1>
            <p className="tp-hero__lead">{t.lead}</p>
            <div className="tp-hero__meta">
              <span><strong>{docs.length}</strong> {t.metaDocs}</span>
              <span className="tp-hero__dot" aria-hidden="true"></span>
              <span>{t.metaUpdated.replace("{year}", String(Math.max(...docs.map((d) => d.year))))}</span>
            </div>
          </div>
        </header>

        <FeaturedEstatuts t={t} d={estatuts} />

        <section className="tp-archive" id="documents">
          <div className="wrap">
            <div className="pk-toolbar tp-toolbar">
              <div className="tp-tools">
                <div className="tp-year">
                  <select value={year} onChange={(e) => setYear(e.target.value)} aria-label="Any">
                    <option value="all">{t.yearAll}</option>
                    {years.map((y) => <option key={y} value={y}>{y}</option>)}
                  </select>
                  <svg className="tp-year__chev" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
                </div>
                <div className="pk-search">
                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><circle cx="11" cy="11" r="7" /><path d="m21 21-4.3-4.3" /></svg>
                  <input type="search" value={q} onChange={(e) => setQ(e.target.value)} placeholder={t.searchPh} />
                </div>
              </div>
            </div>

            <div className="tp-count pk-count">{n} {n === 1 ? t.countOne : t.countMany}</div>

            {filtering ? (
              n ? (
                <div className="tp-grid tp-grid--flat">
                  {filtered.map((d) => <DocCard key={d.id || d.url} t={t} d={d} />)}
                </div>
              ) : (
                <div className="pk-empty">{t.empty}</div>
              )
            ) : (
              /* Remount when the live taxonomy replaces the seed's, so the
                 default-open group always exists in the current data. */
              <EsbartAccordion
                key={groups.map((g) => g.id).join("|")}
                items={groups}
                defaultOpen={groups.length ? groups[0].id : null}
                idPrefix="tpacc"
                renderHead={(g) => (
                  <>
                    <span className="pk-acc__title">{catLabel(t, g.cat)}</span>
                    <span className="pk-acc__count">{g.docs.length} {g.docs.length === 1 ? t.countOne : t.countMany}</span>
                    <span className="pk-acc__icon" aria-hidden="true"></span>
                  </>
                )}
                renderBody={(g) => (
                  <div className="tp-acc__docs">
                    <div className="tp-grid">
                      {g.docs.map((d) => <DocCard key={d.id || d.url} t={t} d={d} />)}
                    </div>
                  </div>
                )}
              />
            )}

            <p className="tp-footnote">{t.footNote}</p>
          </div>
        </section>
      </main>
      <EsbartFooter t={t} />
      <EsbartTweaksPanel {...tweaks} />
    </>
  );
}

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