<script>
  import { Button }  from '$lib/components/ui/button/index.js'
  import { Input }   from '$lib/components/ui/input/index.js'
  import { Select }  from '$lib/components/ui/select/index.js'
  import { Card, CardContent } from '$lib/components/ui/card/index.js'
  import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '$lib/components/ui/table/index.js'
  import { setupI18n } from '$lib/i18n.js'
  import { _, locale } from '@sveltia/i18n'
  import Icon from '@iconify/svelte'
  import orcidSvg from './assets/orcid.svg'
  import rorSvg from './assets/ror.svg'
  import 'katex/dist/katex.min.css'
  import WorkCard from './WorkCard.svelte'
  import OrgCard from './OrgCard.svelte'
  import PersonCard from './PersonCard.svelte'
  import { Pagination } from '$lib/components/ui/pagination/index.js'
  import { typeLabel } from '$lib/bib-utils.js'

  // ── Entity page init (set by server via window.__DRAGOMAN_INIT__) ─────────
  const _initData = typeof window !== 'undefined' ? (window.__DRAGOMAN_INIT__ ?? null) : null
  const isEntityPage = Boolean(_initData)

  // ── App config ────────────────────────────────────────────────────────────
  let isDark = $state(document.documentElement.classList.contains('dark'))
  function toggleDark() {
    isDark = !isDark
    document.documentElement.classList.toggle('dark', isDark)
    localStorage.setItem('app-dark', String(isDark))
  }

  // ── Bibliography ──────────────────────────────────────────────────────────
  let bibDoi    = $state('')
  let bibStyle  = $state(localStorage.getItem('dragoman-style')  ?? 'apa')
  const initialLocale = localStorage.getItem('dragoman-locale') ?? (() => {
    const supported = ['en-US','de-DE','fr-FR','es-ES','it-IT','ja-JP','ko-KR','nl-NL','pt-BR','sv-SE','zh-CN']
    const lang = navigator.language
    return supported.find(v => v === lang)
        ?? supported.find(v => v.startsWith(lang.split('-')[0] + '-'))
        ?? 'en-US'
  })()
  setupI18n(initialLocale)
  let bibLocale = $state(initialLocale)
  let bibliography   = $state([])
  const PAGE_SIZE    = 10
  const MAX_SAVED    = 50
  let liveWorksCount = $state(null)  // actual DB count for org/person index card
  let liveCiteCount  = $state(null)  // actual DB count for work citation card
  let allRefDois     = $state([])   // all DOI strings from rawData.references
  let refCache       = $state({})   // { doi: entry } for fetched refs
  let refFetching    = $state(false)
  let allCiteDois    = $state([])   // all DOI strings from rawData.citations
  let citeCache      = $state({})   // { doi: entry } for fetched citations
  let citeFetching   = $state(false)
  let refPage   = $state(1)
  let citePage  = $state(1)
  let worksPage = $state(1)
  let savedPage = $state(1)
  let bibLoading        = $state(false)
  let bibFetchingRemote = $state(false)
  let bibError          = $state('')
  let _remoteTimer      = null
  let randomLoading = $state(false)
  let hasDb = $state(false)
  fetch('/status').then(r => r.json()).then(d => { hasDb = d.db }).catch(() => {})
  const isLocalhost = typeof window !== 'undefined' &&
    (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')
  let copiedCiteId = $state(null)
  let copiedPreIdx = $state(null)
  let addedId      = $state(null)
  let dragId       = $state(null)
  let dropPosition = $state(null)
  let searchSuggestions  = $state([])
  let searchOpen         = $state(false)
  let searchHighlighted  = $state(-1)
  let _searchTimer       = null
  let savedDragHandleActive = $state(false)
  let savedItems = $state((() => {
    try { return JSON.parse(localStorage.getItem('dragoman-saved') ?? '[]') }
    catch { return [] }
  })())

  $effect(() => { localStorage.setItem('dragoman-saved',  JSON.stringify(savedItems)) })
  $effect(() => { localStorage.setItem('dragoman-style',  bibStyle)  })
  $effect(() => { localStorage.setItem('dragoman-locale', bibLocale); locale.set(bibLocale) })

  $effect(() => {
    const q = bibDoi.trim()
    clearTimeout(_searchTimer)
    if (q.length < 2 || !hasDb) {
      searchSuggestions = []
      searchHighlighted = -1
      searchOpen = false
      return
    }
    _searchTimer = setTimeout(async () => {
      try {
        const res = await fetch(`/search?q=${encodeURIComponent(q)}`)
        if (res.ok) {
          const data = await res.json()
          searchSuggestions = data
          searchHighlighted = -1
          searchOpen = data.length > 0
        }
      } catch { /* ignore network errors */ }
    }, 250)
  })

  // ── Export ────────────────────────────────────────────────────────────────
  let expFormat  = $state('commonmeta')
  let expError   = $state('')
  let expLoading = $state(false)
  let expCopied  = $state(false)

  // ── Saved items export ────────────────────────────────────────────────────
  let savedExpFormat  = $state('commonmeta')
  let savedExpLoading = $state(false)
  let savedExpError   = $state('')
  let savedExpCopied  = $state(false)
  const savedExpFormats = [
    { value: 'commonmeta', label: 'Commonmeta' },
  ]

  // ── Style picker ──────────────────────────────────────────────────────────
  let stylePickerOpen = $state(false)
  let styleSearch     = $state('')

  // ── Data ──────────────────────────────────────────────────────────────────
  const commonStyles = [
    { value: 'apa',                       label: 'American Psychological Association 7th edition' },
    { value: 'chicago-author-date',       label: 'Chicago Manual of Style 18th Edition (Author-Date)' },
    { value: 'harvard-cite-them-right',   label: 'Cite Them Right 12th Edition (Harvard)' },
    { value: 'ieee',                      label: 'IEEE' },
    { value: 'modern-language-association', label: 'MLA Handbook 9th Edition' },
    { value: 'vancouver',                 label: 'Vancouver' },
  ]

  const allCitationStyles = [
    { value: 'apa',                                              label: 'American Psychological Association 7th edition' },
    { value: 'alphanumeric',                                     label: 'Alphanumeric' },
    { value: 'american-anthropological-association',             label: 'American Anthropological Association' },
    { value: 'american-chemical-society',                        label: 'American Chemical Society' },
    { value: 'american-geophysical-union',                       label: 'American Geophysical Union' },
    { value: 'american-institute-of-aeronautics-and-astronautics', label: 'American Institute of Aeronautics and Astronautics' },
    { value: 'american-institute-of-physics',                    label: 'American Institute of Physics' },
    { value: 'american-medical-association',                     label: 'American Medical Association' },
    { value: 'american-meteorological-society',                  label: 'American Meteorological Society' },
    { value: 'american-physics-society',                         label: 'American Physical Society' },
    { value: 'american-physiological-society',                   label: 'American Physiological Society' },
    { value: 'american-political-science-association',           label: 'American Political Science Association' },
    { value: 'american-society-for-microbiology',                label: 'American Society for Microbiology' },
    { value: 'american-society-of-civil-engineers',              label: 'American Society of Civil Engineers' },
    { value: 'american-society-of-mechanical-engineers',         label: 'American Society of Mechanical Engineers' },
    { value: 'american-sociological-association',                label: 'American Sociological Association' },
    { value: 'angewandte-chemie',                                label: 'Angewandte Chemie International Edition' },
    { value: 'annual-reviews',                                   label: 'Annual Reviews' },
    { value: 'annual-reviews-author-date',                       label: 'Annual Reviews (Author-Date)' },
    { value: 'associacao-brasileira-de-normas-tecnicas',         label: 'Associação Brasileira de Normas Técnicas' },
    { value: 'association-for-computing-machinery',              label: 'Association for Computing Machinery' },
    { value: 'biomed-central',                                   label: 'BioMed Central' },
    { value: 'bmj',                                              label: 'BMJ' },
    { value: 'bristol-university-press',                         label: 'Bristol University Press' },
    { value: 'cell',                                             label: 'Cell' },
    { value: 'chicago-author-date',                              label: 'Chicago Manual of Style 18th Edition (Author-Date)' },
    { value: 'chicago-notes',                                    label: 'Chicago Manual of Style 18th Edition (Notes & Bibliography)' },
    { value: 'chicago-shortened-notes',                          label: 'Chicago Manual of Style 18th Edition (Shortened Notes)' },
    { value: 'copernicus',                                       label: 'Copernicus Publications' },
    { value: 'council-of-science-editors',                       label: 'Council of Science Editors (Citation-Sequence)' },
    { value: 'council-of-science-editors-author-date',           label: 'Council of Science Editors (Name-Year)' },
    { value: 'current-opinion',                                  label: 'Current Opinion' },
    { value: 'deutsche-gesellschaft-für-psychologie',            label: 'Deutsche Gesellschaft für Psychologie (Deutsch)' },
    { value: 'deutsche-sprache',                                 label: 'Deutsche Sprache (Deutsch)' },
    { value: 'elsevier-harvard',                                 label: 'Elsevier (Harvard)' },
    { value: 'elsevier-vancouver',                               label: 'Elsevier (Vancouver)' },
    { value: 'elsevier-with-titles',                             label: 'Elsevier (Numeric, With Titles)' },
    { value: 'frontiers',                                        label: 'Frontiers' },
    { value: 'future-medicine',                                  label: 'Future Medicine' },
    { value: 'future-science',                                   label: 'Future Science Group' },
    { value: 'gb-7714-2005-numeric',                             label: 'GB/T 7714-2005 (Numeric)' },
    { value: 'gb-7714-2015-author-date',                         label: 'GB/T 7714-2015 (Author-Date)' },
    { value: 'gb-7714-2015-note',                                label: 'GB/T 7714-2015 (Note)' },
    { value: 'gb-7714-2015-numeric',                             label: 'GB/T 7714-2015 (Numeric)' },
    { value: 'gost-r-705-2008-numeric',                          label: 'GOST R 7.0.5-2008 (Numeric)' },
    { value: 'harvard-cite-them-right',                          label: 'Cite Them Right 12th Edition (Harvard)' },
    { value: 'ieee',                                             label: 'IEEE' },
    { value: 'institute-of-physics-numeric',                     label: 'Institute of Physics (Numeric)' },
    { value: 'iso-690-author-date',                              label: 'ISO 690 (Author-Date)' },
    { value: 'iso-690-numeric',                                  label: 'ISO 690 (Numeric)' },
    { value: 'karger',                                           label: 'Karger' },
    { value: 'mary-ann-liebert-vancouver',                       label: 'Mary Ann Liebert (Vancouver)' },
    { value: 'modern-humanities-research-association',           label: 'MHRA Style Guide 4th Edition (Notes)' },
    { value: 'mla',                                              label: 'MLA Handbook 9th Edition' },
    { value: 'multidisciplinary-digital-publishing-institute',   label: 'Multidisciplinary Digital Publishing Institute' },
    { value: 'nature',                                           label: 'Nature' },
    { value: 'vancouver',                                        label: 'Vancouver' },
    { value: 'vancouver-superscript',                            label: 'NLM/Vancouver (Superscript)' },
    { value: 'pensoft',                                          label: 'Pensoft Journals' },
    { value: 'plos',                                             label: 'Public Library of Science' },
    { value: 'royal-society-of-chemistry',                       label: 'Royal Society of Chemistry' },
    { value: 'sage-vancouver',                                   label: 'SAGE (Vancouver)' },
    { value: 'sist02',                                           label: 'SIST02' },
    { value: 'spie',                                             label: 'SPIE' },
    { value: 'springer-basic',                                   label: 'Springer Basic (Numeric)' },
    { value: 'springer-basic-author-date',                       label: 'Springer Basic (Author-Date)' },
    { value: 'springer-fachzeitschriften-medizin-psychologie',   label: 'Springer Fachzeitschriften Medizin Psychologie (Deutsch)' },
    { value: 'springer-humanities-author-date',                  label: 'Springer Humanities (Author-Date)' },
    { value: 'springer-lecture-notes-in-computer-science',       label: 'Springer Lecture Notes in Computer Science' },
    { value: 'springer-mathphys',                                label: 'Springer MathPhys (Numeric)' },
    { value: 'springer-socpsych-author-date',                    label: 'Springer SocPsych (Author-Date)' },
    { value: 'springer-vancouver',                               label: 'Springer (Vancouver)' },
    { value: 'taylor-and-francis-chicago-author-date',           label: 'Taylor & Francis (Chicago Author-Date)' },
    { value: 'taylor-and-francis-national-library-of-medicine',  label: 'Taylor & Francis (NLM/Vancouver)' },
    { value: 'the-institution-of-engineering-and-technology',    label: 'Institution of Engineering and Technology' },
    { value: 'the-lancet',                                       label: 'The Lancet' },
    { value: 'thieme',                                           label: 'Thieme' },
    { value: 'trends',                                           label: 'Trends' },
    { value: 'turabian-author-date',                             label: 'Chicago Manual of Style 17th Edition (Author-Date)' },
    { value: 'turabian-fullnote-8',                              label: 'Chicago Manual of Style 17th Edition (Notes & Bibliography)' },
  ]

  const locales = [
    { value: 'en-US', label: 'English (US)' },
    { value: 'de-DE', label: 'Deutsch' },
    { value: 'fr-FR', label: 'Français' },
    { value: 'es-ES', label: 'Español' },
    { value: 'it-IT', label: 'Italiano' },
    { value: 'ja-JP', label: '日本語' },
    { value: 'ko-KR', label: '한국어' },
    { value: 'nl-NL', label: 'Nederlands' },
    { value: 'pt-BR', label: 'Português (Brasil)' },
    { value: 'sv-SE', label: 'Svenska' },
    { value: 'zh-CN', label: '中文（简体）' },
  ]

  const expFormats = [
    { value: 'bibtex',       label: 'BibTeX' },
    { value: 'commonmeta',   label: 'Commonmeta' },
    { value: 'crossref',     label: 'Crossref' },
    { value: 'crossref_xml', label: 'Crossref XML' },
    { value: 'csl',          label: 'CSL-JSON' },
    { value: 'datacite',     label: 'DataCite JSON' },
    { value: 'datacite_xml', label: 'DataCite XML' },
    { value: 'citation',     i18n: 'format.citation' },
    { value: 'inveniordm',   label: 'InvenioRDM' },
    { value: 'ris',          label: 'RIS' },
    { value: 'schemaorg',    label: 'Schema.org' },
  ]

  // ── Helpers ───────────────────────────────────────────────────────────────
  function cleanDoi(raw) {
    return raw.trim().replace(/^https?:\/\/(?:dx\.)?doi\.org\//, '')
  }

  // Returns { type, id } for the given raw identifier string, or null if empty.
  // Supports CURIEs (openalex:, doi:, pmid:, pmcid:, arxiv:) and full URLs.
  function parseIdentifier(raw) {
    const s = raw.trim()
    if (!s) return null

    // Self-referential URL: strip own origin or canonical commonmeta.org
    let selfPath = null
    if (s.startsWith(location.origin + '/')) selfPath = s.slice(location.origin.length + 1)
    else { const m = s.match(/^https?:\/\/commonmeta\.org\/(.+)/); if (m) selfPath = m[1] }
    if (selfPath) return parseIdentifier(selfPath.replace(/^(pmid|pmcid|arxiv|openalex)\//, '$1:'))

    // OpenAlex: openalex:Wxxx  or  https://openalex.org/[works/]Wxxx  or  bare Wxxx
    const oaM = s.match(/^(?:openalex:|https?:\/\/openalex\.org\/(?:works\/)?)?(W\d+)$/i)
    if (oaM) return { type: 'openalex', id: oaM[1].toUpperCase() }

    // PMID: pmid:xxx  or  https://pubmed.ncbi.nlm.nih.gov/xxx
    const pmidM = s.match(/^(?:pmid:|https?:\/\/pubmed\.ncbi\.nlm\.nih\.gov\/)(\d+)\/?$/)
    if (pmidM) return { type: 'pmid', id: pmidM[1] }

    // PMCID: pmcid:PMCxxx  or  https://www.ncbi.nlm.nih.gov/pmc/articles/PMCxxx
    const pmcM = s.match(/^(?:pmcid:|https?:\/\/(?:www\.)?ncbi\.nlm\.nih\.gov\/pmc\/articles\/)(PMC\d+)\/?$/i)
    if (pmcM) return { type: 'pmcid', id: pmcM[1].toUpperCase() }

    // arXiv: arxiv:XXXX.XXXXX  or  https://arxiv.org/abs/XXXX.XXXXX
    const arxivM = s.match(/^(?:arxiv:|https?:\/\/arxiv\.org\/(?:abs|pdf)\/)(\d{4}\.\d{4,}(?:v\d+)?)\/?$/i)
    if (arxivM) return { type: 'arxiv', id: arxivM[1] }

    // ROR: ror:0xxxxxxx  or  https://ror.org/0xxxxxxx
    const rorM = s.match(/^(?:ror:|https?:\/\/ror\.org\/)([a-z0-9]+)\/?$/i)
    if (rorM) return { type: 'ror', id: rorM[1] }

    // ORCID: bare XXXX-XXXX-XXXX-XXXZ, orcid:..., or https://orcid.org/...
    const orcidM = s.match(/^(?:orcid:|https?:\/\/orcid\.org\/)?(\d{4}-\d{4}-\d{4}-\d{3}[\dX])\/?$/i)
    if (orcidM) return { type: 'orcid', id: orcidM[1] }

    // DOI: doi:xxx  or  URL  or  bare 10.xxx
    const doi = s.replace(/^doi:/i, '').replace(/^https?:\/\/(?:dx\.)?doi\.org\//, '')
    return { type: 'doi', id: doi }
  }

  function stripHtml(html) {
    const doc = new DOMParser().parseFromString(html, 'text/html')
    return doc.body.textContent ?? ''
  }

  function parseOrgMeta(cmData) {
    try {
      const raw = JSON.parse(cmData)
      const d = Array.isArray(raw) ? raw[0] : raw
      if (!d?.id?.startsWith('https://ror.org/')) return null
      return {
        entityType:       'Organization',
        id:               d.id ?? '',
        name:             d.name ?? d.title ?? '',
        additional_names: [d.acronym, ...(d.additional_names ?? [])].filter(Boolean),
        country:          d.country ?? '',
        location:         d.location ?? null,
        urls:             (d.urls ?? []).filter(u => u?.url),
        identifiers:      (d.identifiers ?? []),
        types:            d.types ?? [],
        relations:        d.relations ?? [],
      }
    } catch { return null }
  }

  function entityId(cmData) {
    try { const d = JSON.parse(cmData); return (Array.isArray(d) ? d[0] : d)?.id ?? '' }
    catch { return '' }
  }

  function countWorksFor(id) {
    if (!id) return 0
    return savedItems.filter(wi => detectEntityType(wi.data) === 'Work' && wi.data.includes(id)).length
  }

  function detectEntityType(cmData) {
    try {
      const raw = JSON.parse(cmData)
      const d = Array.isArray(raw) ? raw[0] : raw
      if (d?.id?.startsWith('https://ror.org/')) return 'Organization'
      if (d?.id?.startsWith('https://orcid.org/') || d?.given_name || d?.family_name) return 'Person'
      return 'Work'
    } catch { return 'Work' }
  }

  function bibGroups() {
    const groups = []
    bibliography.forEach((entry, i) => {
      const type = detectEntityType(entry.data)
      if (type === 'Work' && groups.length > 0 && groups[groups.length - 1].type === 'Work') {
        groups[groups.length - 1].entries.push({ entry, idx: i })
      } else {
        groups.push({ type, entries: [{ entry, idx: i }] })
      }
    })
    return groups
  }

  function parseMeta(cmData) {
    try {
      const d = JSON.parse(cmData)

      // Build raw author list (no affiliation indices yet)
      const rawAuthors = (d.contributors ?? [])
        .filter(c => !c.roles?.length || c.roles.includes('Author'))
        .map(c => {
          if (c.type === 'Person' && c.person) {
            const p = c.person
            const name = [p.given_name, p.family_name].filter(Boolean).join(' ')
            if (!name) return null
            return { name, orcid: p.id?.startsWith('https://orcid.org/') ? p.id : '', rawAffs: p.affiliations ?? [] }
          }
          const name = c.organization?.name ?? ''
          return name ? { name, orcid: '', rawAffs: [] } : null
        })
        .filter(Boolean)

      // Truncate to display set: first 19 + last if > 20
      const truncated = rawAuthors.length > 20
      const displayRaw = truncated
        ? [...rawAuthors.slice(0, 19), rawAuthors[rawAuthors.length - 1]]
        : rawAuthors

      // Build affiliations only from displayed authors
      const affMap = new Map()
      const affiliations = []
      function affIndex(aff) {
        const key = aff.id || aff.name
        if (!key) return null
        if (!affMap.has(key)) {
          affiliations.push({ name: aff.name || aff.id, ror: aff.id?.startsWith('https://ror.org/') ? aff.id : '' })
          affMap.set(key, affiliations.length)
        }
        return affMap.get(key)
      }
      const toAuthor = r => ({ name: r.name, orcid: r.orcid, affIndices: r.rawAffs.map(affIndex).filter(Boolean) })

      const authorList = truncated
        ? [...displayRaw.slice(0, 19).map(toAuthor), { isEllipsis: true }, toAuthor(displayRaw[displayRaw.length - 1])]
        : displayRaw.map(toAuthor)

      const funders = (d.funding_references ?? [])
        .filter(f => f.funder_name || f.funder_id)

      const cid    = d.container?.identifier ?? ''
      const cidType = (d.container?.identifier_type ?? '').toUpperCase()
      const containerUrl = cidType === 'DOI'  ? `https://doi.org/${cid}`
                         : cidType === 'ISSN' ? `https://portal.issn.org/resource/ISSN/${cid}`
                         : ''
      return {
        title:          d.title ?? '',
        type:           d.type ?? '',
        version:        d.version ?? '',
        datePublished:  d.date_published ?? '',
        containerTitle: d.container?.title ?? '',
        containerUrl,
        authorList,
        affiliations,
        funders,
        description:    d.description ?? '',
        subjects:       (d.subjects ?? []).map(s => s.subject).filter(Boolean),
        referenceCount: (d.references ?? []).length,
        citationCount:  (d.citations  ?? []).length,
        language:       d.language ?? '',
        licenseTitle:   d.license?.title ?? '',
        licenseId:      d.license?.id ?? '',
        licenseUrl:     d.license?.url ?? '',
        id:             d.id ?? '',
      }
    } catch { return null }
  }

  // ── Navigation ────────────────────────────────────────────────────────────
  $effect(() => {
    function handleNavClick(e) {
      const a = e.target.closest('a[href]')
      if (!a) return
      if (a.target === '_blank') return
      let url
      try { url = new URL(a.href) } catch { return }
      if (url.origin !== location.origin) return
      if (url.pathname === location.pathname && url.search === location.search) return
      e.preventDefault()
      bibFetchingRemote = true
      const dest = a.href
      requestAnimationFrame(() => requestAnimationFrame(() => { location.href = dest }))
    }
    document.addEventListener('click', handleNavClick)
    return () => document.removeEventListener('click', handleNavClick)
  })

  function idToPath(id) {
    for (const prefix of ['https://doi.org/', 'https://orcid.org/', 'https://ror.org/']) {
      if (id.startsWith(prefix)) return '/' + id.slice(prefix.length)
    }
    return '/' + id
  }

  function selectSuggestion(id) {
    searchOpen = false
    searchHighlighted = -1
    searchSuggestions = []
    bibDoi = ''
    bibFetchingRemote = true
    window.location.href = idToPath(id)
  }

  function onSearchKeydown(e) {
    if (e.key === 'Escape') {
      searchOpen = false
      searchHighlighted = -1
      return
    }
    if (!searchOpen || searchSuggestions.length === 0) return
    if (e.key === 'ArrowDown') {
      e.preventDefault()
      searchHighlighted = Math.min(searchHighlighted + 1, searchSuggestions.length - 1)
    } else if (e.key === 'ArrowUp') {
      e.preventDefault()
      searchHighlighted = searchHighlighted > 0 ? searchHighlighted - 1 : -1
    } else if (e.key === 'Enter' && searchHighlighted >= 0) {
      e.preventDefault()
      selectSuggestion(searchSuggestions[searchHighlighted].id)
    }
  }

  $effect(() => {
    if (searchHighlighted >= 0) {
      document.querySelector(`[data-search-idx="${searchHighlighted}"]`)
        ?.scrollIntoView({ block: 'nearest' })
    }
  })

  let groupedSuggestions = $derived.by(() => {
    const groups = new Map()
    for (const s of searchSuggestions) {
      if (!groups.has(s.entity_type)) groups.set(s.entity_type, [])
      groups.get(s.entity_type).push(s)
    }
    let offset = 0
    return [...groups.entries()].map(([type, rawItems]) => {
      const items = rawItems.map((item, i) => ({ ...item, flatIndex: offset + i }))
      offset += rawItems.length
      return { type, items }
    })
  })

  function goToId(e) {
    e.preventDefault()
    const parsed = parseIdentifier(bibDoi.trim())
    if (!parsed?.id) return
    bibLoading = true
    bibFetchingRemote = false
    clearTimeout(_remoteTimer)
    _remoteTimer = setTimeout(() => { bibFetchingRemote = true }, 300)
    const routePrefix = {
      openalex: '/openalex',
      pmid:     '/pmid',
      pmcid:    '/pmcid',
      arxiv:    '/arxiv',
    }
    const prefix = routePrefix[parsed.type] ?? ''
    window.location.href = prefix ? `${prefix}/${parsed.id}` : `/${parsed.id}`
  }

  async function initEntityPage(rawData) {
    liveWorksCount = null
    liveCiteCount  = null
    refPage   = 1
    citePage  = 1
    worksPage = 1
    const rawArr = Array.isArray(rawData) ? rawData : null
    const firstId = rawArr?.[0]?.id ?? ''
    const isEntityArray = rawArr && rawArr.length > 0 && (
      firstId.startsWith('https://ror.org/') || firstId.startsWith('https://orcid.org/')
    )
    if (isEntityArray) {
      const entityEntry = {
        id:   crypto.randomUUID(),
        doi:  rawArr[0].id,
        data: JSON.stringify([rawArr[0]]),
        url:  null,
        html: '',
      }
      const workEntries = rawArr.slice(1, 201).map(work => ({
        id:   crypto.randomUUID(),
        doi:  (work.id ?? '').replace(/^https?:\/\/doi\.org\//, ''),
        data: JSON.stringify(work),
        url:  work.url ?? null,
        html: '',
      }))
      bibliography = [entityEntry, ...workEntries]
      const workItems = workEntries.map(e => ({ id: e.id, data: e.data }))
      if (workItems.length > 0) {
        try {
          const formatted = await formatItems(workItems, bibStyle, bibLocale)
          const byId = Object.fromEntries(formatted.map(i => [i.id, i.html]))
          bibliography = bibliography.map(e => ({ ...e, html: byId[e.id] ?? e.html }))
        } catch { /* continue without formatted citations */ }
      }
    } else {
      const cmData = JSON.stringify(rawData)
      const entryId = crypto.randomUUID()
      const doi = typeof rawData?.id === 'string' && rawData.id.startsWith('https://doi.org/')
        ? rawData.id.replace('https://doi.org/', '')
        : ''
      const url = rawData?.url ?? null
      let html = ''
      try {
        const formatted = await formatItems([{ id: entryId, data: cmData }], bibStyle, bibLocale)
        html = formatted[0]?.html ?? ''
      } catch { /* continue without */ }
      const mainEntry = { id: entryId, doi, data: cmData, url, html }
      bibliography = [mainEntry]

      // Extract DOI lists — actual fetching is done lazily by page via $effect
      allRefDois = (rawData?.references ?? [])
        .map(r => typeof r?.id === 'string' && r.id.startsWith('https://doi.org/')
          ? r.id.replace('https://doi.org/', '') : null)
        .filter(Boolean)
      refCache = {}

      allCiteDois = (rawData?.citations ?? [])
        .map(c => typeof c?.id === 'string' && c.id.startsWith('https://doi.org/')
          ? c.id.replace('https://doi.org/', '') : null)
        .filter(Boolean)
      citeCache = {}
    }

  }

  $effect(() => { if (isEntityPage) initEntityPage(_initData) })

  // Fetch actual counts from DB for entity pages (works for org/person, citations for DOI).
  // Runs once hasDb is true — avoids the race where bibliography is set before /status resolves.
  $effect(() => {
    if (!isEntityPage || !hasDb || bibliography.length === 0) return
    const id = entityId(bibliography[0].data)
    if (!id) return
    fetch(`/count?id=${encodeURIComponent(id)}`)
      .then(r => r.ok ? r.json() : null)
      .then(d => {
        if (d?.works    != null) liveWorksCount = d.works
        if (d?.citations != null) liveCiteCount  = d.citations
      })
      .catch(() => {})
  })

  // Lazy fetch: load the current page of refs from SQLite, batch of PAGE_SIZE
  $effect(() => {
    const page = refPage
    const dois = allRefDois
    const pageDois = dois.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
    if (pageDois.length === 0) return
    void fetchDoiPage(pageDois, refCache, newEntries => { refCache = { ...refCache, ...newEntries } },
      () => { refFetching = true }, () => { refFetching = false })
  })

  // Lazy fetch: load the current page of citations from SQLite, batch of PAGE_SIZE
  $effect(() => {
    const page = citePage
    const dois = allCiteDois
    const pageDois = dois.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
    if (pageDois.length === 0) return
    void fetchDoiPage(pageDois, citeCache, newEntries => { citeCache = { ...citeCache, ...newEntries } },
      () => { citeFetching = true }, () => { citeFetching = false })
  })

  async function fetchDoiPage(pageDois, cache, mergeCache, setLoading, clearLoading) {
    const missing = pageDois.filter(doi => !(doi in cache))
    if (missing.length === 0) return
    setLoading()
    try {
      const settled = await Promise.allSettled(missing.map(async doi => {
        const url = isLocalhost ? `/${doi}?format=commonmeta` : `/${doi}?format=commonmeta&local_only=true`
        const resp = await fetch(url)
        if (!resp.ok) return null
        return { doi, d: await resp.json() }
      }))
      const newEntries = {}
      const toFormat = []
      for (const res of settled) {
        if (res.status !== 'fulfilled' || !res.value) continue
        const { doi, d } = res.value
        const id = crypto.randomUUID()
        const rdoi = d?.id?.startsWith('https://doi.org/') ? d.id.replace('https://doi.org/', '') : doi
        const entry = { id, doi: rdoi, data: JSON.stringify(d), url: d?.url ?? null, html: '' }
        newEntries[doi] = entry
        toFormat.push({ id, data: entry.data })
      }
      if (toFormat.length > 0) {
        try {
          const formatted = await formatItems(toFormat, bibStyle, bibLocale)
          const byId = Object.fromEntries(formatted.map(i => [i.id, i.html]))
          for (const doi of Object.keys(newEntries)) {
            const e = newEntries[doi]
            newEntries[doi] = { ...e, html: byId[e.id] ?? '' }
          }
        } catch { }
      }
      mergeCache(newEntries)
    } finally {
      clearLoading()
    }
  }

  const entityTitle = $derived.by(() => {
    if (!isEntityPage || bibliography.length === 0) return ''
    try {
      const d = JSON.parse(bibliography[0].data)
      const obj = Array.isArray(d) ? d[0] : d
      return obj?.name || obj?.title || ''
    } catch { return '' }
  })

  async function pickRandom() {
    randomLoading = true
    bibError = ''
    try {
      const resp = await fetch('/random')
      if (!resp.ok) throw new Error(await resp.text().catch(() => resp.statusText))
      const { id } = await resp.json()
      bibDoi = id
    } catch (err) {
      bibError = String(err)
    } finally {
      randomLoading = false
    }
  }

  async function formatItems(items, style, locale) {
    const resp = await fetch('/bibliography', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ items, style, locale }),
    })
    if (!resp.ok) {
      const text = await resp.text().catch(() => resp.statusText)
      throw new Error(`${resp.status}: ${text}`)
    }
    return (await resp.json()).items
  }

  function onBibStyleChange(e) {
    const val = e.target.value
    if (val === '__more__') {
      e.target.value = bibStyle
      stylePickerOpen = true
      styleSearch = ''
      return
    }
    bibStyle = val
    if (bibliography.length > 0) reformatBibliography(val, bibLocale)
  }

  function pickMoreStyle(style) {
    bibStyle = style.value
    stylePickerOpen = false
    styleSearch = ''
    if (bibliography.length > 0) reformatBibliography(style.value, bibLocale)
  }

  function onBibLocaleChange(e) {
    const locale = e.target.value
    bibLocale = locale
    if (bibliography.length > 0) reformatBibliography(bibStyle, locale)
  }

  async function reformatBibliography(style, locale) {
    bibLoading = true
    try {
      const cachedRefValues  = Object.values(refCache)
      const cachedCiteValues = Object.values(citeCache)
      const allItems = [
        ...bibliography.map(e => ({ id: e.id, data: e.data })),
        ...cachedRefValues.map(e => ({ id: e.id, data: e.data })),
        ...cachedCiteValues.map(e => ({ id: e.id, data: e.data })),
      ]
      const formatted = await formatItems(allItems, style, locale)
      const byId = Object.fromEntries(formatted.map(i => [i.id, i.html]))
      bibliography = bibliography.map(e => ({ ...e, html: byId[e.id] ?? e.html }))
      refCache  = Object.fromEntries(Object.entries(refCache).map(([doi, e])  => [doi, { ...e, html: byId[e.id]  ?? e.html }]))
      citeCache = Object.fromEntries(Object.entries(citeCache).map(([doi, e]) => [doi, { ...e, html: byId[e.id] ?? e.html }]))
    } catch (err) {
      bibError = String(err)
    } finally {
      bibLoading = false
    }
  }

  function onSavedDragStart(e, item) {
    dragId = item.id
    e.dataTransfer.effectAllowed = 'move'
  }

  function onSavedDragOver(e, item) {
    if (!dragId || dragId === item.id) return
    e.preventDefault()
    const rect = e.currentTarget.getBoundingClientRect()
    dropPosition = { id: item.id, before: e.clientY < rect.top + rect.height / 2 }
  }

  function onSavedDragLeave(e) {
    if (!e.currentTarget.contains(e.relatedTarget)) dropPosition = null
  }

  function onSavedDrop(e, item) {
    e.preventDefault()
    if (!dragId || !dropPosition || dragId === item.id) return
    const from = savedItems.findIndex(b => b.id === dragId)
    const moved = savedItems[from]
    const rest = savedItems.filter(b => b.id !== dragId)
    const toIdx = rest.findIndex(b => b.id === dropPosition.id)
    rest.splice(dropPosition.before ? toIdx : toIdx + 1, 0, moved)
    savedItems = rest
    dragId = null
    dropPosition = null
  }

  function onSavedDragEnd() {
    dragId = null
    dropPosition = null
  }

  function addToSaved(id, data) {
    if (!id) return
    if (!savedItems.some(item => item.id === id) && savedItems.length < MAX_SAVED) {
      savedItems = [...savedItems, { id, data }]
    }
    addedId = id
    setTimeout(() => { addedId = null }, 2000)
  }

  function removeFromSaved(id) {
    savedItems = savedItems.filter(item => item.id !== id)
  }

  function fetchSavedExport() {
    const items = savedItems.map(item => {
      try { return JSON.parse(item.data) } catch { return null }
    }).filter(Boolean)
    return JSON.stringify(items.length === 1 ? items[0] : items, null, 2)
  }

  async function exportSaved() {
    if (!savedItems.length) return
    savedExpLoading = true
    savedExpError = ''
    try {
      const content = fetchSavedExport()
      const url = URL.createObjectURL(new Blob([content], { type: 'application/json' }))
      Object.assign(document.createElement('a'), { href: url, download: `saved.json` }).click()
      URL.revokeObjectURL(url)
    } catch (err) { savedExpError = String(err) }
    finally { savedExpLoading = false }
  }

  async function copySavedExport() {
    if (!savedItems.length) return
    savedExpLoading = true
    savedExpError = ''
    savedExpCopied = false
    try {
      await navigator.clipboard.writeText(fetchSavedExport())
      savedExpCopied = true
      setTimeout(() => { savedExpCopied = false }, 2000)
    } catch (err) { savedExpError = String(err) }
    finally { savedExpLoading = false }
  }

  function deleteEntry(id) {
    bibliography = bibliography.filter(e => e.id !== id)
  }

  function deleteBibliography() {
    bibliography = bibliography.slice(0, 1)
    allRefDois = []; refCache = {}
    allCiteDois = []; citeCache = {}
  }

  async function copyCitation(entry) {
    await navigator.clipboard.writeText(JSON.stringify(entry.data, null, 2))
    copiedCiteId = entry.id
    setTimeout(() => { copiedCiteId = null }, 2000)
  }

  async function copyPre(text, idx) {
    await navigator.clipboard.writeText(text)
    copiedPreIdx = idx
    setTimeout(() => { copiedPreIdx = null }, 2000)
  }


  // ── Export actions ────────────────────────────────────────────────────────
  const expExtensions = { citation: 'txt', bibtex: 'bib', ris: 'ris', csl: 'json', commonmeta: 'json',
    crossref: 'json', crossref_xml: 'xml', datacite: 'json', datacite_xml: 'xml',
    inveniordm: 'json', schemaorg: 'json' }
  const jsonFormats = new Set(['csl', 'commonmeta', 'crossref', 'datacite', 'inveniordm', 'schemaorg'])

  async function fetchBibExport(items) {
    if (expFormat === 'citation') {
      return items.map(e => stripHtml(e.html)).join('\n\n')
    }
    const results = await Promise.all(
      items.map(async entry => {
        const resp = await fetch(`/${entry.doi}?format=${expFormat}`)
        if (!resp.ok) { const t = await resp.text().catch(() => resp.statusText); throw new Error(`${resp.status}: ${t}`) }
        return resp.text()
      })
    )
    return jsonFormats.has(expFormat)
      ? JSON.stringify(results.map(r => JSON.parse(r)), null, 2)
      : results.join('\n\n')
  }

  async function exportBibliography(items) {
    if (!items.length) return
    expLoading = true
    expError = ''
    try {
      const combined = await fetchBibExport(items)
      const ext = expExtensions[expFormat] ?? 'txt'
      const url = URL.createObjectURL(new Blob([combined], { type: 'text/plain' }))
      Object.assign(document.createElement('a'), { href: url, download: `bibliography.${ext}` }).click()
      URL.revokeObjectURL(url)
    } catch (err) { expError = String(err) }
    finally { expLoading = false }
  }

  async function copyBibExport(items) {
    if (!items.length) return
    expLoading = true
    expError = ''
    expCopied = false
    try {
      const combined = await fetchBibExport(items)
      await navigator.clipboard.writeText(combined)
      expCopied = true
      setTimeout(() => { expCopied = false }, 2000)
    } catch (err) { expError = String(err) }
    finally { expLoading = false }
  }
</script>

<div class="min-h-screen flex flex-col bg-background text-foreground antialiased">

  <!-- Header -->
  <header class="bg-[#f2f2f2] dark:bg-gray-800 shrink-0 border-b border-gray-200 dark:border-gray-700">
    <div class="max-w-full md:max-w-3xl lg:max-w-5xl mx-auto px-6 h-14 flex items-center">
      <!-- Brand -->
      <div class="flex items-center gap-2 flex-1">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6 text-primary shrink-0">
          <path d="M12 .75a8.25 8.25 0 0 0-4.135 15.39c.686.398 1.115 1.143 1.115 1.942V18h5.25v-.008c0-.799.43-1.544 1.115-1.942A8.25 8.25 0 0 0 12 .75Z" />
          <path fill-rule="evenodd" d="M9.013 19.9a.75.75 0 0 1 .877-.597 11.319 11.319 0 0 0 4.22 0 .75.75 0 1 1 .28 1.473 12.819 12.819 0 0 1-4.78 0 .75.75 0 0 1-.597-.876ZM9 22.5a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5A.75.75 0 0 1 9 22.5Z" clip-rule="evenodd" />
        </svg>
        <a href="/" class="text-base font-bold tracking-tight text-gray-900 dark:text-white hover:underline">Commonmeta</a>
      </div>

      <!-- Search form -->
      <div class="relative w-80 lg:w-96 min-w-0">
      <form onsubmit={goToId} class="w-full">
        <div class="flex items-center border border-input rounded-md bg-background overflow-hidden transition-colors focus-within:border-primary">
          <input
            type="text"
            bind:value={bibDoi}
            placeholder={_('bibliography.placeholder')}
            autocomplete="off"
            autocorrect="off"
            autocapitalize="none"
            spellcheck="false"
            onblur={() => setTimeout(() => { searchOpen = false }, 150)}
            onfocus={() => { if (searchSuggestions.length > 0) searchOpen = true }}
            onkeydown={onSearchKeydown}
            class="flex-1 h-9 px-5 text-sm font-mono bg-transparent outline-none min-w-0"
          />
          {#if bibDoi}
            <button
              type="button"
              aria-label="Clear"
              onclick={() => { bibDoi = ''; searchOpen = false; searchHighlighted = -1; searchSuggestions = [] }}
              class="h-9 w-8 flex items-center justify-center text-muted-foreground hover:text-foreground transition-colors shrink-0"
            >
              <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-3.5 h-3.5">
                <path d="M18 6 6 18M6 6l12 12"/>
              </svg>
            </button>
          {/if}
          <button
            type="submit"
            disabled={bibLoading || !bibDoi.trim()}
            aria-label={_('bibliography.go')}
            class="h-9 px-3 bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 shrink-0 flex items-center justify-center transition-colors"
          >
            {#if bibLoading}
              <span class="block w-4 h-4 rounded-full border-2 border-primary-foreground/30 border-t-primary-foreground animate-spin"></span>
            {:else}
              <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4">
                <circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
              </svg>
            {/if}
          </button>
        </div>
      </form>
      {#if searchOpen && groupedSuggestions.length > 0}
        <div class="absolute left-0 right-0 top-full mt-1 z-50 bg-background border border-input rounded-md shadow-lg overflow-hidden">
          {#each groupedSuggestions as group}
            <p class="px-3 py-1 text-xs font-semibold text-muted-foreground bg-muted/50">{group.type}</p>
            {#each group.items as item}
              <button
                type="button"
                data-search-idx={item.flatIndex}
                onmousedown={() => selectSuggestion(item.id)}
                class="w-full text-left px-4 py-2 text-sm truncate block transition-colors
                  {item.flatIndex === searchHighlighted
                    ? 'bg-accent text-accent-foreground'
                    : 'hover:bg-accent hover:text-accent-foreground'}"
              >{item.title || item.id}</button>
            {/each}
          {/each}
        </div>
      {/if}
      </div>

      <!-- Controls -->
      <div class="flex items-center gap-3 flex-1 justify-end">
        <button
          type="button"
          onclick={toggleDark}
          aria-label="Toggle dark mode"
          class="text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors"
        >
          {#if isDark}
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-5 h-5">
              <path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
            </svg>
          {:else}
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-5 h-5">
              <path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
            </svg>
          {/if}
        </button>
        <Select bind:value={bibLocale} onchange={onBibLocaleChange}
          class="h-7 w-36 text-xs py-0">
          {#each locales as l}
            <option value={l.value}>{l.label}</option>
          {/each}
        </Select>
      </div>
    </div>
  </header>

  <div class="h-1 w-full shrink-0" aria-live="polite">
    {#if bibFetchingRemote}
      <div class="fetch-progress h-full bg-primary"></div>
    {/if}
  </div>

  {#if bibError}
    <div class="max-w-full md:max-w-3xl lg:max-w-5xl w-full mx-auto px-6 pt-4">
      <div class="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-md text-sm text-destructive" role="alert">
        {bibError}
      </div>
    </div>
  {/if}

  <!-- Main -->
  <main class="flex-1 max-w-full md:max-w-3xl lg:max-w-5xl w-full mx-auto space-y-12">

    <!-- ── Bibliography ─────────────────────────────────────────────────── -->
    <section>

      <!-- Bibliography list -->
      {#if bibliography.length > 0}
        {#snippet bibFooter(items)}
          <div class="border-t border-border px-4 py-3 bg-muted flex items-center gap-2 flex-wrap">
            <Select bind:value={expFormat} class="w-40 h-8 text-xs py-0">
              {#each expFormats as f}
                <option value={f.value}>{f.i18n ? _(f.i18n) : f.label}</option>
              {/each}
            </Select>
            {#if expFormat === 'citation'}
              <div class="relative flex-1 min-w-36">
                <Select value={bibStyle} onchange={onBibStyleChange} class="w-full h-8 text-xs py-0">
                  {#if !commonStyles.some(s => s.value === bibStyle)}
                    <option value={bibStyle}>{allCitationStyles.find(s => s.value === bibStyle)?.label ?? bibStyle}</option>
                    <option disabled>──────────────────────</option>
                  {/if}
                  {#each commonStyles as s}
                    <option value={s.value}>{s.label}</option>
                  {/each}
                  <option disabled>──────────────────────</option>
                  <option value="__more__">More styles…</option>
                </Select>
                {#if stylePickerOpen}
                  <div class="fixed inset-0 z-40" role="presentation" onclick={() => { stylePickerOpen = false; styleSearch = '' }}></div>
                  <div class="absolute top-full left-0 right-0 z-50 mt-1 bg-background border border-border rounded-md shadow-lg">
                    <div class="p-2 border-b border-border">
                      <!-- svelte-ignore a11y_autofocus -->
                      <input
                        bind:value={styleSearch}
                        placeholder="Search styles…"
                        autofocus
                        class="w-full text-sm px-3 py-1.5 rounded border border-input bg-background outline-none focus:ring-1 focus:ring-ring"
                        onkeydown={e => { if (e.key === 'Escape') { stylePickerOpen = false; styleSearch = '' } }}
                      />
                    </div>
                    <ul class="max-h-72 overflow-y-auto py-1">
                      {#each allCitationStyles.filter(s => !styleSearch || s.label.toLowerCase().includes(styleSearch.toLowerCase())) as s}
                        <li>
                          <button
                            type="button"
                            class="w-full text-left px-3 py-1.5 text-sm hover:bg-muted {s.value === bibStyle ? 'font-semibold text-primary' : ''}"
                            onclick={() => pickMoreStyle(s)}
                          >{s.label}</button>
                        </li>
                      {/each}
                      {#if allCitationStyles.filter(s => !styleSearch || s.label.toLowerCase().includes(styleSearch.toLowerCase())).length === 0}
                        <li class="px-3 py-2 text-sm text-muted-foreground">No styles found</li>
                      {/if}
                    </ul>
                  </div>
                {/if}
              </div>
            {/if}
            <Button variant="default" onclick={() => exportBibliography(items)} disabled={expLoading} class="h-8 gap-1.5 text-xs px-3">
              {#if expLoading}
                <span class="w-3.5 h-3.5 rounded-full border-2 border-primary-foreground/30 border-t-primary-foreground animate-spin"></span>
              {:else}
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-3.5 h-3.5 shrink-0">
                  <path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
                </svg>
              {/if}
              {_('bibliography.export')}
            </Button>
            <Button variant="outline" onclick={() => copyBibExport(items)} disabled={expLoading} class="h-8 gap-1.5 text-xs px-3">
              {#if expCopied}
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-3.5 h-3.5 shrink-0">
                  <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
                </svg>
                {_('bibliography.copied')}
              {:else}
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-3.5 h-3.5 shrink-0">
                  <path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" />
                </svg>
                {_('bibliography.copy')}
              {/if}
            </Button>
            {#if expError}
              <p class="w-full text-xs text-destructive">{expError}</p>
            {/if}
          </div>
        {/snippet}

        <div class="mt-4 space-y-2">
          {#each bibGroups() as group (group.entries[0].entry.id)}
            {#if group.type === 'Work'}
              {#if isEntityPage && group.entries[0].idx === 0}
                {@const { entry } = group.entries[0]}
                {@const meta = parseMeta(entry.data)}
                <div class="border border-border rounded-md overflow-hidden">
                  <div class="px-5 py-3 border-b border-border bg-muted/50">
                    <h2 class="text-lg font-semibold text-primary">{typeLabel(meta.type, bibLocale)}</h2>
                  </div>
                  <ol>
                    <WorkCard
                      {entry} meta={{ ...meta, referenceCount: allRefDois.length, citationCount: liveCiteCount ?? 0 }} index={undefined}
                      {bibLocale} {copiedCiteId} {addedId}
                      isSaved={savedItems.some(item => item.id === entityId(entry.data))}
                      oncopy={() => copyCitation(entry)}
                      ondelete={undefined}
                      onadd={(id, data) => addToSaved(id, data)}
                    />
                  </ol>
                </div>
                {#if allRefDois.length > 0}
                  {@const refTotalPages = Math.max(1, Math.ceil(allRefDois.length / PAGE_SIZE))}
                  {@const refPageDois = allRefDois.slice((refPage - 1) * PAGE_SIZE, refPage * PAGE_SIZE)}
                  {@const refSlice = refPageDois.map(doi => refCache[doi]).filter(Boolean)}
                  <div class="border border-border rounded-md overflow-hidden">
                    <div class="px-5 py-3 border-b border-border bg-muted/50">
                      <h2 class="text-base font-semibold">{_('bibliography.references_heading')}</h2>
                    </div>
                    {#if refFetching && refSlice.length === 0}
                      <div class="px-5 py-4 text-sm text-muted-foreground">{_('bibliography.go')}…</div>
                    {:else}
                      <ol class="divide-y divide-border">
                        {#each refSlice as entry, idx (entry.id)}
                          {@const meta = parseMeta(entry.data)}
                          <WorkCard
                            {entry} {meta} index={(refPage - 1) * PAGE_SIZE + idx}
                            {bibLocale} {copiedCiteId} {addedId}
                            isSaved={savedItems.some(item => item.id === entityId(entry.data))}
                            oncopy={() => copyCitation(entry)}
                            ondelete={undefined}
                            onadd={(id, data) => addToSaved(id, data)}
                          />
                        {/each}
                      </ol>
                    {/if}
                    {#if refTotalPages > 1}
                      <div class="px-5 pb-4">
                        <Pagination page={refPage} totalPages={refTotalPages} onpage={p => refPage = p} />
                      </div>
                    {/if}
                    {@render bibFooter(Object.values(refCache))}
                  </div>
                {/if}
              {:else}
                {@const isPaginatedWorks = isEntityPage && group.entries[0].idx > 0}
                {@const worksTotalPages = isPaginatedWorks ? Math.max(1, Math.ceil(group.entries.length / PAGE_SIZE)) : 1}
                {@const worksSlice = isPaginatedWorks ? group.entries.slice((worksPage - 1) * PAGE_SIZE, worksPage * PAGE_SIZE) : group.entries}
                <div class="border border-border rounded-md overflow-hidden">
                  {#if isPaginatedWorks}
                    <div class="px-5 py-3 border-b border-border bg-muted/50">
                      <h2 class="text-base font-semibold">{_('bibliography.works_heading')}</h2>
                    </div>
                  {/if}
                  <ol class="divide-y divide-border">
                    {#each worksSlice as { entry, idx } (entry.id)}
                      {@const meta = parseMeta(entry.data)}
                      <WorkCard
                        {entry} {meta} index={idx === 0 ? undefined : idx - 1}
                        {bibLocale} {copiedCiteId} {addedId}
                        isSaved={savedItems.some(item => item.id === entityId(entry.data))}
                        oncopy={() => copyCitation(entry)}
                        ondelete={undefined}
                        onadd={(id, data) => addToSaved(id, data)}
                      />
                    {/each}
                  </ol>
                  {#if worksTotalPages > 1}
                    <div class="px-5 pb-4">
                      <Pagination page={worksPage} totalPages={worksTotalPages} onpage={p => worksPage = p} />
                    </div>
                  {/if}
                  {@render bibFooter(group.entries.map(e => e.entry))}
                </div>
              {/if}
            {:else if group.type === 'Organization'}
              {#each group.entries as { entry, idx } (entry.id)}
                {@const org = parseOrgMeta(entry.data)}
                {@const worksCount = isEntityPage && idx === 0 ? (liveWorksCount ?? 0) : 0}
                <div class="border border-border rounded-md overflow-hidden">
                  {#if isEntityPage && idx === 0}
                    <div class="px-5 py-3 border-b border-border bg-muted/50">
                      <h2 class="text-lg font-semibold text-primary">{_('entity.organization')}</h2>
                    </div>
                  {/if}
                  <OrgCard {entry} {org} {bibLocale} {addedId} {worksCount}
                    isSaved={savedItems.some(item => item.id === entityId(entry.data))}
                    ondelete={idx === 0 ? undefined : () => deleteEntry(entry.id)}
                    onadd={(id, data) => addToSaved(id, data)}
                  />
                </div>
              {/each}
            {:else}
              {#each group.entries as { entry, idx } (entry.id)}
                {@const worksCount = isEntityPage && idx === 0 ? (liveWorksCount ?? 0) : 0}
                <div class="border border-border rounded-md overflow-hidden">
                  {#if isEntityPage && idx === 0}
                    <div class="px-5 py-3 border-b border-border bg-muted/50">
                      <h2 class="text-lg font-semibold text-primary">{_('entity.person')}</h2>
                    </div>
                  {/if}
                  <PersonCard {entry} {addedId} {bibLocale} {worksCount}
                    isSaved={savedItems.some(item => item.id === entityId(entry.data))}
                    ondelete={idx === 0 ? undefined : () => deleteEntry(entry.id)}
                    onadd={(id, data) => addToSaved(id, data)}
                  />
                </div>
              {/each}
            {/if}
          {/each}

          {#if allCiteDois.length > 0}
            {@const citeTotalPages = Math.max(1, Math.ceil(allCiteDois.length / PAGE_SIZE))}
            {@const citePageDois = allCiteDois.slice((citePage - 1) * PAGE_SIZE, citePage * PAGE_SIZE)}
            {@const citeSlice = citePageDois.map(doi => citeCache[doi]).filter(Boolean)}
            <div class="border border-border rounded-md overflow-hidden">
              <div class="px-5 py-3 border-b border-border bg-muted/50">
                <h2 class="text-base font-semibold">{_('bibliography.citations_heading')}</h2>
              </div>
              {#if citeFetching && citeSlice.length === 0}
                <div class="px-5 py-4 text-sm text-muted-foreground">{_('bibliography.go')}…</div>
              {:else}
                <ol class="divide-y divide-border">
                  {#each citeSlice as entry, idx (entry.id)}
                    {@const meta = parseMeta(entry.data)}
                    <WorkCard
                      {entry} {meta} index={(citePage - 1) * PAGE_SIZE + idx}
                      {bibLocale} {copiedCiteId} {addedId}
                      isSaved={savedItems.some(item => item.id === entityId(entry.data))}
                      oncopy={() => copyCitation(entry)}
                      ondelete={undefined}
                      onadd={(id, data) => addToSaved(id, data)}
                    />
                  {/each}
                </ol>
              {/if}
              {#if citeTotalPages > 1}
                <div class="px-5 pb-4">
                  <Pagination page={citePage} totalPages={citeTotalPages} onpage={p => citePage = p} />
                </div>
              {/if}
              {@render bibFooter(Object.values(citeCache))}
            </div>
          {/if}
        </div>
      {/if}

      <!-- Saved items (homepage only) -->
      {#if !isEntityPage && savedItems.length > 0}
        {@const savedTotalPages = Math.max(1, Math.ceil(savedItems.length / PAGE_SIZE))}
        {@const savedSlice = savedItems.slice((savedPage - 1) * PAGE_SIZE, savedPage * PAGE_SIZE)}
        <div class="mt-4 space-y-2">
          {#each savedSlice as item, sIdx (item.id)}
            {@const sEntry = { id: item.id, doi: item.id.startsWith('https://doi.org/') ? item.id.replace('https://doi.org/', '') : '', data: item.data, url: null, html: '' }}
            {@const sType = detectEntityType(item.data)}
            <!-- svelte-ignore a11y_no_static_element_interactions -->
            <div class="border border-border rounded-md overflow-hidden transition-opacity
                        {dragId === item.id ? 'opacity-30' : ''}
                        {dropPosition?.id === item.id && dropPosition.before  ? 'border-t-2 border-primary' : ''}
                        {dropPosition?.id === item.id && !dropPosition.before ? 'border-b-2 border-primary' : ''}"
              draggable={savedDragHandleActive}
              onpointerdown={() => { savedDragHandleActive = false }}
              ondragstart={e => { if (!savedDragHandleActive) { e.preventDefault(); return } onSavedDragStart(e, item) }}
              ondragover={e => onSavedDragOver(e, item)}
              ondragleave={onSavedDragLeave}
              ondrop={e => onSavedDrop(e, item)}
              ondragend={() => { savedDragHandleActive = false; onSavedDragEnd() }}>
              <!-- svelte-ignore a11y_no_static_element_interactions -->
              <div class="px-5 py-3 border-b border-border bg-muted/50 flex items-center gap-2">
                <div onpointerdown={e => { e.stopPropagation(); savedDragHandleActive = true }}
                  class="shrink-0 cursor-grab active:cursor-grabbing text-muted-foreground/30 hover:text-muted-foreground/60">
                  <svg viewBox="0 0 8 14" fill="currentColor" class="w-2 h-3.5">
                    <circle cx="2" cy="2" r="1.5"/><circle cx="6" cy="2" r="1.5"/>
                    <circle cx="2" cy="7" r="1.5"/><circle cx="6" cy="7" r="1.5"/>
                    <circle cx="2" cy="12" r="1.5"/><circle cx="6" cy="12" r="1.5"/>
                  </svg>
                </div>
                {#if sType === 'Organization'}
                  <h2 class="text-base font-semibold">{_('entity.organization')}</h2>
                {:else if sType === 'Person'}
                  <h2 class="text-base font-semibold">{_('entity.person')}</h2>
                {:else}
                  {@const sMeta = parseMeta(item.data)}
                  <h2 class="text-base font-semibold">{typeLabel(sMeta.type, bibLocale)}</h2>
                {/if}
              </div>
              {#if sType === 'Organization'}
                {@const sOrg = parseOrgMeta(item.data)}
                <OrgCard entry={sEntry} org={sOrg} {bibLocale} {addedId}
                  ondelete={() => removeFromSaved(item.id)}
                />
              {:else if sType === 'Person'}
                <PersonCard entry={sEntry} {addedId} {bibLocale}
                  ondelete={() => removeFromSaved(item.id)}
                />
              {:else}
                {@const sMeta = parseMeta(item.data)}
                <ol>
                  <WorkCard entry={sEntry} meta={sMeta}
                    {bibLocale} {copiedCiteId} {addedId}
                    oncopy={() => copyCitation(sEntry)}
                    ondelete={() => removeFromSaved(item.id)}
                  />
                </ol>
              {/if}
            </div>
          {/each}

          {#if savedTotalPages > 1}
            <Pagination page={savedPage} totalPages={savedTotalPages} onpage={p => savedPage = p} />
          {/if}

          <!-- Saved items actions -->
          <div class="mt-2 border border-border rounded-md bg-muted px-4 py-3 flex items-center gap-2 flex-wrap">
            <Select bind:value={savedExpFormat} class="w-40 h-8 text-xs py-0">
              {#each savedExpFormats as f}
                <option value={f.value}>{f.label}</option>
              {/each}
            </Select>
            <Button variant="default" onclick={exportSaved} disabled={savedExpLoading} class="h-8 gap-1.5 text-xs px-3">
              {#if savedExpLoading}
                <span class="w-3.5 h-3.5 rounded-full border-2 border-primary-foreground/30 border-t-primary-foreground animate-spin"></span>
              {:else}
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-3.5 h-3.5 shrink-0">
                  <path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
                </svg>
              {/if}
              {_('bibliography.export')}
            </Button>
            <Button variant="outline" onclick={copySavedExport} disabled={savedExpLoading} class="h-8 gap-1.5 text-xs px-3">
              {#if savedExpCopied}
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-3.5 h-3.5 shrink-0">
                  <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
                </svg>
                {_('bibliography.copied')}
              {:else}
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-3.5 h-3.5 shrink-0">
                  <path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" />
                </svg>
                {_('bibliography.copy')}
              {/if}
            </Button>
            <Button variant="outline" onclick={() => { savedItems = [] }} class="ml-auto h-8 gap-1.5 text-xs px-3 hover:text-destructive hover:border-destructive">
              <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-3.5 h-3.5 shrink-0">
                <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
              </svg>
              {_('bibliography.delete')}
            </Button>
            {#if savedExpError}
              <p class="w-full text-xs text-destructive">{savedExpError}</p>
            {/if}
          </div>
        </div>
      {/if}
    </section>

    <!-- ── Docs (homepage only) ─────────────────────────────────────────── -->
    {#if !isEntityPage}
    {#snippet codeBlock(code, idx)}
      <div class="relative group my-3">
        <pre class="!my-0"><code>{code}</code></pre>
        <button
          type="button"
          onclick={() => copyPre(code, idx)}
          aria-label="Copy"
          class="absolute top-1.5 right-1.5 h-7 w-7 flex items-center justify-center rounded
                 text-muted-foreground hover:text-foreground
                 opacity-0 group-hover:opacity-100 transition-opacity"
        >
          {#if copiedPreIdx === idx}
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-3.5 h-3.5">
              <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
            </svg>
          {:else}
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-3.5 h-3.5">
              <path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" />
            </svg>
          {/if}
        </button>
      </div>
    {/snippet}

    <div class="prose prose-sm prose-slate max-w-none
                [&_h2]:text-primary [&_h2]:border-b [&_h2]:border-border [&_h2]:pb-1
                [&_a]:text-primary [&_pre]:bg-muted [&_pre]:border [&_pre]:border-border
                [&_pre]:text-sm [&_pre]:text-gray-800 dark:[&_pre]:text-gray-200">

      <h2>{_('docs.database.title')}</h2>
      <p>{_('docs.database.intro')}</p>

      <h2>{_('docs.search.title')}</h2>
      <p>{_('docs.search.intro')}</p>

      <h2>{_('docs.saved.title')}</h2>
      <p>{_('docs.saved.intro')}</p>

      <h2>{_('docs.formats.title')}</h2>
      <div class="not-prose rounded-md border border-border mb-6">
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead>Format</TableHead>
              <TableHead>Name</TableHead>
              <TableHead>Content Type</TableHead>
              <TableHead class="text-center">Read</TableHead>
              <TableHead class="text-center">Write</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            <TableRow>
              <TableCell>Commonmeta</TableCell>
              <TableCell class="font-mono text-xs">commonmeta</TableCell>
              <TableCell class="font-mono text-xs">application/vnd.commonmeta+json</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center">✓</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="http://en.wikipedia.org/wiki/BibTeX" class="text-primary hover:underline">BibTeX</a></TableCell>
              <TableCell class="font-mono text-xs">bibtex</TableCell>
              <TableCell class="font-mono text-xs">application/x-bibtex</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center">✓</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="https://citation-file-format.github.io/" class="text-primary hover:underline">Citation File Format (CFF)</a></TableCell>
              <TableCell class="font-mono text-xs">cff</TableCell>
              <TableCell class="font-mono text-xs">application/vnd.cff+yaml</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center text-muted-foreground">later</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="https://codemeta.github.io/" class="text-primary hover:underline">Codemeta</a></TableCell>
              <TableCell class="font-mono text-xs">codemeta</TableCell>
              <TableCell class="font-mono text-xs">application/vnd.codemeta.ld+json</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center text-muted-foreground">later</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="https://api.crossref.org" class="text-primary hover:underline">Crossref</a></TableCell>
              <TableCell class="font-mono text-xs">crossref</TableCell>
              <TableCell class="font-mono text-xs">application/vnd.crossref+json</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center">✓</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="https://www.crossref.org/schema/documentation/unixref1.1/unixref1.1.html" class="text-primary hover:underline">CrossRef XML</a></TableCell>
              <TableCell class="font-mono text-xs">crossref_xml</TableCell>
              <TableCell class="font-mono text-xs">application/vnd.crossref.unixref+xml</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center">✓</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="https://citationstyles.org/" class="text-primary hover:underline">CSL-JSON</a></TableCell>
              <TableCell class="font-mono text-xs">csl</TableCell>
              <TableCell class="font-mono text-xs">application/vnd.citationstyles.csl+json</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center">✓</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="https://api.datacite.org/" class="text-primary hover:underline">DataCite</a></TableCell>
              <TableCell class="font-mono text-xs">datacite</TableCell>
              <TableCell class="font-mono text-xs">application/vnd.datacite.datacite+json</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center">✓</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="https://datacite-metadata-schema.readthedocs.io/en/4.7/" class="text-primary hover:underline">DataCite XML</a></TableCell>
              <TableCell class="font-mono text-xs">datacite_xml</TableCell>
              <TableCell class="font-mono text-xs">application/vnd.datacite.datacite+xml</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center">✓</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="https://citationstyles.org/" class="text-primary hover:underline">Formatted Citation</a></TableCell>
              <TableCell class="font-mono text-xs">citation</TableCell>
              <TableCell class="font-mono text-xs">text/x-bibliography</TableCell>
              <TableCell class="text-center text-muted-foreground">n/a</TableCell>
              <TableCell class="text-center">✓</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="https://inveniordm.docs.cern.ch/reference/metadata/" class="text-primary hover:underline">InvenioRDM</a></TableCell>
              <TableCell class="font-mono text-xs">inveniordm</TableCell>
              <TableCell class="font-mono text-xs">application/vnd.inveniordm.v1+json</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center">✓</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="https://www.jsonfeed.org/" class="text-primary hover:underline">JSON Feed</a></TableCell>
              <TableCell class="font-mono text-xs">jsonfeed</TableCell>
              <TableCell class="font-mono text-xs">application/feed+json</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center text-muted-foreground">later</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="https://www.openalex.org/" class="text-primary hover:underline">OpenAlex</a></TableCell>
              <TableCell class="font-mono text-xs">openalex</TableCell>
              <TableCell class="font-mono text-xs">n/a</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center text-muted-foreground">later</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="http://en.wikipedia.org/wiki/RIS_(file_format)" class="text-primary hover:underline">RIS</a></TableCell>
              <TableCell class="font-mono text-xs">ris</TableCell>
              <TableCell class="font-mono text-xs">application/x-research-info-systems</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center">✓</TableCell>
            </TableRow>
            <TableRow>
              <TableCell><a href="http://schema.org/" class="text-primary hover:underline">Schema.org (JSON-LD)</a></TableCell>
              <TableCell class="font-mono text-xs">schemaorg</TableCell>
              <TableCell class="font-mono text-xs">application/vnd.schemaorg.ld+json</TableCell>
              <TableCell class="text-center">✓</TableCell>
              <TableCell class="text-center">✓</TableCell>
            </TableRow>
          </TableBody>
        </Table>
      </div>

      <h2>{_('docs.content_negotiation.title')}</h2>
      <p>{@html _('docs.content_negotiation.intro')}</p>
      <p>{_('docs.content_negotiation.bibtex_label')}</p>
      {@render codeBlock(`curl -H "Accept: application/x-bibtex" \\\n     https://commonmeta.org/10.1371/journal.pcbi.1000204`, 1)}

      <p>{@html _('docs.content_negotiation.format_param_label')}</p>
      {@render codeBlock('curl https://commonmeta.org/10.1371/journal.pcbi.1000204?format=bibtex', 2)}

      <p>{@html _('docs.content_negotiation.citation_style_label')}</p>
      {@render codeBlock(`curl -H "Accept: text/x-bibliography; style=vancouver; locale=de-DE" \\\n     https://commonmeta.org/10.1371/journal.pcbi.1000204`, 3)}

      <p>{_('docs.content_negotiation.query_params_label')}</p>
      {@render codeBlock('curl "https://commonmeta.org/10.1371/journal.pcbi.1000204?format=citation&style=vancouver&locale=de-DE"', 4)}

      <p>{@html _('docs.content_negotiation.multiple_types_label')}</p>
      {@render codeBlock(`curl -H "Accept: application/vnd.citationstyles.csl+json;q=0.9, application/x-bibtex" \\\n     https://commonmeta.org/10.1371/journal.pcbi.1000204`, 5)}
    </div>
    {/if}
  </main>

  <!-- Footer -->
  <footer class="py-6 text-xs text-gray-700 dark:text-gray-300 leading-5">
    <div class="max-w-full md:max-w-3xl lg:max-w-5xl mx-auto px-6 flex items-center justify-between">
      <span class="[&_a]:hover:text-gray-900 dark:[&_a]:hover:text-gray-100 [&_a]:transition-colors">
        {@html _('footer.copyright')}
      </span>
      <span class="flex items-center gap-3">
        <a href="mailto:info@front-matter.de" aria-label="Email"
           class="hover:text-gray-900 dark:hover:text-gray-100 transition-colors">
          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>
          </svg>
        </a>
        <a href="https://hachyderm.io/@mfenner" target="_blank" rel="noreferrer" aria-label="Mastodon"
           class="hover:text-gray-900 dark:hover:text-gray-100 transition-colors">
          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
            <path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a4 4 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522q0-1.288.66-2.046c.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764q.662.757.661 2.046z"/>
          </svg>
        </a>
      </span>
    </div>
  </footer>

</div>

<style>
  @keyframes fetch-fill {
    from { width: 0%; }
    to   { width: 100%; }
  }
  .fetch-progress {
    width: 0%;
    animation: fetch-fill 1s ease-out forwards;
  }
</style>
