{# PDF detail-view component (#942 cycle 1b). Full-bleed PDF viewer with header (back link + title + optional prev/next sibling nav), body (browser-native ````), and a footer with keyboard-shortcut hints. The ``data-dz-widget="pdf-viewer"`` attribute is the bridge mount point — keyboard handlers (Esc → back, j / ← → prev, k / → → next) live in ``static/js/pdf-viewer.js``. NOT co-located with ``x-data`` on the wrapper; the widget contract from #940 stays intact. Bridge ↔ Alpine separation: chrome state is data-driven, handler is global. ## Required parameters - ``src`` — proxy URL for the PDF, typically ``/api/storage//proxy?key=`` (cycle 1a, #942). - ``back_url`` — where Esc / the back button navigates to. ## Optional parameters - ``title`` — header title (defaults to "Document"). - ``prev_url`` / ``next_url`` — sibling URLs for j/k keyboard nav. When unset, the keys are ignored. Projects derive these from their own scope rules (the framework can't generically know what "the next manuscript" means). ## Why ```` and not PDF.js? Browser-native PDF rendering ships in every modern browser today. PDF.js (~250kb) buys cross-browser consistency for chrome like zoom + page count, but the chrome already lives inside the embedded viewer. v1 keeps the framework footprint tight; if a project reports a real cross-browser inconsistency we'll vendor PDF.js as a follow-up cycle. #}
Back

{{ title | default("Document") }}

{% if prev_url or next_url %} {% endif %}
{# #942 cycle 2a / #943 cycle 5a — optional right-side panels. Project renders the content into a string per panel and passes them via ``panels=[{name, label, key, html}, …]``; the framework wraps each in chrome and wires the toggle. CSS-only show/hide via the adjacent-sibling combinator so the wrapper stays free of x-data (preserves the widget contract from #940). Project HTML is trusted ({{ ... | safe }}) — the project renders its own templates with autoescape, then passes the resulting HTML in as a string. Backwards compat: cycle 2a's ``panel_html`` + ``panel_label`` still work — they're normalised to a one-element ``panels`` list with the conventional ``p`` key so cycle 2a adopters see no behaviour change. #} {% if panels %} {% set _panels = panels %} {% elif panel_html %} {% set _panels = [{'name': 'panel', 'label': panel_label or 'Related', 'key': 'p', 'html': panel_html}] %} {% else %} {% set _panels = [] %} {% endif %} {% for panel in _panels %} {% endfor %}
{# #944 cycle 6a — optional footer slot for project-specific controls (page counter, zoom buttons, panel toggle pills, custom action buttons). Renders before the keyboard legend. Project HTML is trusted ({{ ... | safe }}) — projects render their own templates with autoescape, then pass the resulting HTML in as a string. Same trust contract as cycle 2a's ``panel_html``. #} {% if footer_slot_html %} {% endif %} {# ``show_kbd_legend`` defaults to true; pass false explicitly to hide the cycle 1b legend (e.g. when the slot already advertises every binding). #} {% if show_kbd_legend is not defined or show_kbd_legend %} Esc Back {% if prev_url or next_url %} · j Previous k Next {% endif %} {% for panel in _panels %} · {{ panel.key }} {{ panel.label }} {% endfor %} · ? Help {% endif %}
{# #943 cycle 5c — keyboard cheat-sheet overlay. Auto-renders from the chrome bindings and the panel declarations so projects don't have to hand-roll a help modal. ``?`` opens; Esc closes (the bridge handler in pdf-viewer.js dispatches Esc → cheat-sheet close → panel close → back-nav, in that priority order). The dialog is a `` element so focus management + backdrop come for free; the bridge calls .showModal() / .close() rather than toggling a CSS class so the browser's accessibility tree reflects the modal state correctly. #}

Keyboard shortcuts

Esc
Back (or close this overlay first)
{% if prev_url or next_url %}
j /
Previous
k /
Next
{% endif %} {% for panel in _panels %}
{{ panel.key }}
Toggle {{ panel.label }}
{% endfor %}
?
Show this overlay