ids theme: rework the references slide layout and auto-fit it

Address four points of criticism on the bibliography slide:
- "Authors (Year):" no longer sits bold on its own line but runs
  inline in a medium weight -- it was overemphasized for text that is
  already darker than the entry, and looked blurred at small sizes.
- Entries get breathing room below the title bar (padding, so the
  vertical-centering wrapper's first-child margin reset cannot eat it).
- Authors running inline saves a line per entry.
- The slide auto-fits: decreasing font sizes are tried and, when even
  the smallest would overflow, trailing entries move onto
  automatically created "References (cont.)" slides (Reveal.sync()
  registers them, so navigation, menu and numbering keep working).
  In print/PDF the scroll box is disabled so long bibliographies flow
  across pages instead of being clipped.

Change-Id: I59897723d9eabe824f7f5ed794cf75c0082e031b
diff --git a/.gitignore b/.gitignore
index fe7ddc6..ad16f6d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@
 .sass-cache/
 examples/#references.bib#
 examples/@inproceedings{kupietz-deliko2026,
+/Quantitative*.pdf
diff --git a/NEWS.md b/NEWS.md
index d0c36ab..691f2bd 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,7 @@
+# revealjs.ids (development version)
+
+- `ids` theme: the bibliography slide is reworked -- "Authors (Year):" no longer sits bold on its own line but runs inline, set darker than the rest of the entry but in normal weight (less overemphasized, more compact), entries have no extra margin between them (the hanging indent separates them), and reference slides stay top-aligned (excluded from the adaptive vertical centering). The slide now auto-fits: it tries decreasing font sizes and, when even the smallest would overflow, moves trailing entries onto automatically created "References (cont.)" slides. When printing to PDF, the on-screen scroll box is disabled so long bibliographies flow onto continuation pages instead of being clipped.
+
 # revealjs.ids 0.9.1.9011
 
 - New `chapter_numbering` yaml option controlling the automatic chapter numbering: `false` turns it off (headings keep exactly what was typed, including manually typed numbers), a number starts the enumeration at that value (e.g. `chapter_numbering: 5` for a continuation deck). The default `TRUE` numbers from 1 as before.
diff --git a/README.Rmd b/README.Rmd
index aecb0a8..c0e1180 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -558,7 +558,7 @@
 
 ## Bibliography and Appendix Slides
 
-When you cite with `[@key]` and set a `bibliography:` in the yaml header, the references are formatted with the bundled IDS citation style by default (based on the style developed for ICLC-10 2023 in Mannheim). To use a different style, specify it as usual with `csl:` in the yaml header.
+When you cite with `[@key]` and set a `bibliography:` in the yaml header, the references are formatted with the bundled IDS citation style by default (based on the style developed for ICLC-10 2023 in Mannheim). To use a different style, specify it as usual with `csl:` in the yaml header. The `ids` theme auto-fits the references slide: it tries decreasing font sizes and, when the bibliography is too long even for the smallest, moves trailing entries onto automatically created "References (cont.)" slides. When printing to PDF, the on-screen scroll box is disabled so long bibliographies flow across pages instead of being clipped.
 
 Pandoc/citeproc generates the reference list **at the very end of the document body**. That is fine when the references are your last content:
 
diff --git a/README.md b/README.md
index 4a762d5..59d2df3 100644
--- a/README.md
+++ b/README.md
@@ -738,7 +738,12 @@
 header, the references are formatted with the bundled IDS citation
 style by default (based on the style developed for ICLC-10 2023 in
 Mannheim). To use a different style, specify it as usual with `csl:`
-in the yaml header.
+in the yaml header. The `ids` theme
+auto-fits the references slide: it tries decreasing font sizes and,
+when the bibliography is too long even for the smallest, moves
+trailing entries onto automatically created "References (cont.)"
+slides. When printing to PDF, the on-screen scroll box is disabled so
+long bibliographies flow across pages instead of being clipped.
 
 Pandoc/citeproc generates the reference list **at the very end
 of the document body**. That is fine when the references are your last
diff --git a/inst/reveal.js-5.2.1/dist/theme/ids.css b/inst/reveal.js-5.2.1/dist/theme/ids.css
index 8d56808..1489184 100644
--- a/inst/reveal.js-5.2.1/dist/theme/ids.css
+++ b/inst/reveal.js-5.2.1/dist/theme/ids.css
@@ -969,7 +969,9 @@
   font-family: "Fira Sans Condensed", sans-serif;
   font-size: 16px;
   text-align: left;
-  margin-top: 10px;
+  /* breathing room below the title bar; padding instead of margin, because
+     the vertical-centering wrapper zeroes its first child's margin */
+  padding-top: 18px;
   max-height: 510px;
   font-weight: 400;
   color: #253b43;
@@ -983,13 +985,22 @@
 
 .references .csl-entry {
   color: #888888;
-  margin-bottom: 0.5em;
+  /* the hanging indent visually separates entries; no extra margin needed */
+  margin-bottom: 0;
 }
 
 .csl-entry .csl-authors-year {
-  display: block;
-  font-weight: bold;
-  color: #4d616b;
+  font-weight: 400;
+  color: #253b43;
+}
+
+/* in print/PDF, let long bibliographies flow onto continuation pages
+   instead of being clipped by the on-screen scroll box */
+@media print {
+  .reveal .references {
+    max-height: none !important;
+    overflow: visible !important;
+  }
 }
 
 .reveal code.sourceCode:last-child + pre {
diff --git a/inst/rmarkdown/templates/revealjs_presentation/resources/default.html b/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
index 287082e..5ee7d35 100644
--- a/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
+++ b/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
@@ -872,14 +872,15 @@
 
 $if(theme-ids)$
 <script>
-  // IDS theme: in the bibliography, "Authors (Year):" should form a bold first
-  // line, followed by a line break. Which part of an entry citeproc wraps in
-  // spans/links depends on the entry type, so a CSS "break before first child
-  // element" hack cannot place the break reliably. Wrap the leading text up to
-  // and including the first "):" explicitly instead; entries without a year
-  // marker are left untouched.
+  // IDS theme: in the bibliography, "Authors (Year):" should be set
+  // darker than the rest of the entry, but in normal weight. Which part
+  // of an entry citeproc wraps in spans/links depends on the entry type,
+  // so a CSS "break before first child element" hack cannot mark it
+  // reliably. Wrap the leading text up to and including the first "):"
+  // explicitly instead; entries without a year marker are left untouched.
   (function () {
     document.querySelectorAll('.csl-entry').forEach(function (entry) {
+      if (entry.querySelector('.csl-authors-year')) return;
       var textNodes = [];
       var accumulated = '';
       var children = Array.prototype.slice.call(entry.childNodes);
@@ -907,6 +908,117 @@
     });
   })();
 </script>
+<script>
+  // IDS theme: bibliography slides are tightly packed, so auto-fit them:
+  // try a window of font sizes and, when even the smallest one overflows,
+  // move trailing entries onto automatically created continuation slides.
+  // Only runs once per references slide (when it is first shown, so its
+  // entries are measurable); printing is handled by CSS, which lets long
+  // bibliographies flow across pages.
+  (function () {
+    var SIZES = [18, 17, 16, 15, 14, 13, 12];
+    var SPLIT_SIZE = 14;
+
+    function footerReserve() {
+      var v = parseFloat(getComputedStyle(document.documentElement)
+        .getPropertyValue('--ids-footer-reserve'));
+      return isNaN(v) ? 48 : v;
+    }
+
+    function availableHeight(ref) {
+      var slide = ref.closest('section');
+      if (!slide || !slide.clientHeight) return 0;
+      return Math.min(
+        slide.clientHeight - ref.offsetTop - footerReserve() - 10,
+        parseFloat(getComputedStyle(ref).maxHeight) || Infinity
+      );
+    }
+
+    function fits(ref) {
+      var avail = availableHeight(ref);
+      return avail > 0 && ref.scrollHeight <= avail + 1;
+    }
+
+    function splitOff(ref) {
+      ref.style.fontSize = SPLIT_SIZE + 'px';
+      if (fits(ref)) return; // readable again at the split size
+
+      var section = ref.closest('section');
+      var heading = section.querySelector('h1,h2');
+      var title = heading ? heading.textContent : 'References';
+      var avail = availableHeight(ref);
+
+      // measure all entries at the split size, keep those that fit on the
+      // original slide and distribute the rest over continuation slides
+      // with the same geometry
+      var entries = Array.prototype.slice.call(ref.querySelectorAll(':scope > .csl-entry'));
+      var heights = entries.map(function (entry) {
+        return entry.getBoundingClientRect().height +
+          parseFloat(getComputedStyle(entry).marginBottom);
+      });
+      var keep = 0;
+      var used = 0;
+      for (var k = 0; k < entries.length; k++) {
+        if (used + heights[k] > avail) break;
+        used += heights[k];
+        keep = k + 1;
+      }
+      if (keep === 0) keep = 1; // at least one entry stays on the slide
+      if (keep >= entries.length) return; // everything fits after all
+      var stash = entries.slice(keep);
+      stash.forEach(function (entry) { entry.remove(); });
+
+      var pages = [[]];
+      used = 0;
+      stash.forEach(function (entry, i) {
+        var height = heights[keep + i];
+        if (used + height > avail && pages[pages.length - 1].length) {
+          pages.push([]);
+          used = 0;
+        }
+        pages[pages.length - 1].push(entry);
+        used += height;
+      });
+
+      var insertAfter = section;
+      pages.forEach(function (pageEntries, i) {
+        if (!pageEntries.length) return;
+        var cont = document.createElement('section');
+        cont.className = section.className;
+        cont.id = (section.id || 'references') + '-cont-' + (i + 1);
+        if (heading) {
+          var h = document.createElement(heading.tagName);
+          h.textContent = title + ' (cont.)';
+          cont.appendChild(h);
+        }
+        var newRef = ref.cloneNode(false);
+        pageEntries.forEach(function (entry) { newRef.appendChild(entry); });
+        cont.appendChild(newRef);
+        insertAfter.parentNode.insertBefore(cont, insertAfter.nextSibling);
+        insertAfter = cont;
+      });
+      if (typeof Reveal !== 'undefined' && Reveal.isReady()) Reveal.sync();
+    }
+
+    function fitBibliographies() {
+      document.querySelectorAll('.reveal .references').forEach(function (ref) {
+        if (ref.dataset.fitDone) return;
+        if (!ref.closest('section').clientHeight) return; // not laid out yet
+        ref.dataset.fitDone = '1';
+        ref.style.fontSize = '';
+        for (var i = 0; i < SIZES.length; i++) {
+          ref.style.fontSize = SIZES[i] + 'px';
+          if (fits(ref)) return;
+        }
+        splitOff(ref);
+      });
+    }
+
+    Reveal.on('ready', fitBibliographies);
+    Reveal.on('slidechanged', function () { requestAnimationFrame(fitBibliographies); });
+    window.addEventListener('load', fitBibliographies);
+  })();
+</script>
 $endif$
 
 $if(theme-ids)$
@@ -1223,6 +1335,9 @@
     function wrapSlideContent(slide) {
       if (slide.dataset.contentWrapped) return;
       slide.dataset.contentWrapped = '1';
+      // bibliographies stay top-aligned: they are packed anyway, and the
+      // fitting/splitting logic below works from the slide top downwards
+      if (slide.querySelector('.references')) return;
       var content = Array.prototype.filter.call(
         Array.prototype.slice.call(slide.children, headingCount(slide)),
         function (el) { return el.tagName !== 'ASIDE'; }