Storyline: split the chapter page, dock the notes at the bottom
The chapter page now fills the viewport as two horizontal panes: the
chapter scrolls in the top ~2/3, the notes sit in a fixed dock in the
bottom ~1/3, always visible while you read. Drag the divider to change
the ratio, click the "Chapter notes" header to collapse the dock to a
bar; both the height and the collapsed state persist in localStorage.
- base.html.twig: add a {% block body_class %} hook on <body>.
- chapter.html.twig sets body class "storyline-chapter-page"; the split
layout CSS is scoped to it and falls back to normal stacked flow under
768px wide or 560px tall.
- app.js publishes the navbar height as --app-nav-h so the split can size
itself to viewport-minus-navbar even when the navbar wraps.
- new chapter_notes Stimulus controller: drag-resize + collapse + persist.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body class="d-flex flex-column">
|
||||
<body class="d-flex flex-column {% block body_class %}{% endblock %}">
|
||||
{% block navbar %}
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary border-bottom">
|
||||
<div class="container">
|
||||
|
||||
@@ -2,68 +2,82 @@
|
||||
|
||||
{% block title %}{{ chapter.title }} · Storyline · dmtools{% endblock %}
|
||||
|
||||
{% block body_class %}storyline-chapter-page{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="row g-4 storyline-layout">
|
||||
<nav class="col-lg-3 order-lg-2">
|
||||
<div class="storyline-toc">
|
||||
<a href="{{ path('app_storyline') }}" class="text-decoration-none small d-inline-block mb-2">← All chapters</a>
|
||||
<div class="text-uppercase small text-body-secondary fw-semibold mb-2" style="letter-spacing:.08em">Chapters</div>
|
||||
<ol class="list-unstyled mb-0">
|
||||
{% for s, c in chapters %}
|
||||
<li>
|
||||
<a href="{{ path('app_storyline_chapter', {slug: s}) }}"
|
||||
class="storyline-toc-link{{ s == slug ? ' is-active' }}">
|
||||
<span class="storyline-toc-num">{{ c.tag }}</span>{{ c.title }}
|
||||
<div class="storyline-split" data-controller="chapter-notes">
|
||||
<div class="storyline-split-main">
|
||||
<div class="row g-4 storyline-layout">
|
||||
<nav class="col-lg-3 order-lg-2">
|
||||
<div class="storyline-toc">
|
||||
<a href="{{ path('app_storyline') }}" class="text-decoration-none small d-inline-block mb-2">← All chapters</a>
|
||||
<div class="text-uppercase small text-body-secondary fw-semibold mb-2" style="letter-spacing:.08em">Chapters</div>
|
||||
<ol class="list-unstyled mb-0">
|
||||
{% for s, c in chapters %}
|
||||
<li>
|
||||
<a href="{{ path('app_storyline_chapter', {slug: s}) }}"
|
||||
class="storyline-toc-link{{ s == slug ? ' is-active' }}">
|
||||
<span class="storyline-toc-num">{{ c.tag }}</span>{{ c.title }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="col-lg-9 order-lg-1">
|
||||
<article class="storyline-doc">
|
||||
<header class="storyline-doc-head">
|
||||
<div class="storyline-eyebrow">Storyline · {{ chapter.eyebrow }}</div>
|
||||
<h1>{{ chapter.title }}</h1>
|
||||
<p class="storyline-standfirst">{{ chapter.summary }}</p>
|
||||
</header>
|
||||
|
||||
{{ include(chapter.template) }}
|
||||
</article>
|
||||
|
||||
<nav class="storyline-pager">
|
||||
{% if prev %}
|
||||
<a href="{{ path('app_storyline_chapter', {slug: prev}) }}" class="storyline-pager-link">
|
||||
<span class="storyline-pager-dir">← Previous</span>
|
||||
<span class="storyline-pager-title">{{ chapters[prev].title }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="col-lg-9 order-lg-1">
|
||||
<details class="card shadow-sm mb-4 storyline-notes" open>
|
||||
<summary class="card-header d-flex justify-content-between align-items-center">
|
||||
<span>📝 Chapter notes</span>
|
||||
{% if note.updatedAt %}
|
||||
<span class="text-body-secondary small fw-normal">saved {{ note.updatedAt|date('Y-m-d H:i') }}</span>
|
||||
{% endif %}
|
||||
</summary>
|
||||
<div class="card-body">
|
||||
<p class="text-body-secondary small mb-2">
|
||||
Your running log for this chapter — what actually happened at the table.
|
||||
</p>
|
||||
{{ form_start(form) }}
|
||||
{{ form_row(form.notes) }}
|
||||
<button class="btn btn-primary mt-2">Save notes</button>
|
||||
{{ form_end(form) }}
|
||||
{% else %}<span></span>{% endif %}
|
||||
{% if next %}
|
||||
<a href="{{ path('app_storyline_chapter', {slug: next}) }}" class="storyline-pager-link text-lg-end">
|
||||
<span class="storyline-pager-dir">Next →</span>
|
||||
<span class="storyline-pager-title">{{ chapters[next].title }}</span>
|
||||
</a>
|
||||
{% else %}<span></span>{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<article class="storyline-doc">
|
||||
<header class="storyline-doc-head">
|
||||
<div class="storyline-eyebrow">Storyline · {{ chapter.eyebrow }}</div>
|
||||
<h1>{{ chapter.title }}</h1>
|
||||
<p class="storyline-standfirst">{{ chapter.summary }}</p>
|
||||
</header>
|
||||
|
||||
{{ include(chapter.template) }}
|
||||
</article>
|
||||
|
||||
<nav class="storyline-pager">
|
||||
{% if prev %}
|
||||
<a href="{{ path('app_storyline_chapter', {slug: prev}) }}" class="storyline-pager-link">
|
||||
<span class="storyline-pager-dir">← Previous</span>
|
||||
<span class="storyline-pager-title">{{ chapters[prev].title }}</span>
|
||||
</a>
|
||||
{% else %}<span></span>{% endif %}
|
||||
{% if next %}
|
||||
<a href="{{ path('app_storyline_chapter', {slug: next}) }}" class="storyline-pager-link text-lg-end">
|
||||
<span class="storyline-pager-dir">Next →</span>
|
||||
<span class="storyline-pager-title">{{ chapters[next].title }}</span>
|
||||
</a>
|
||||
{% else %}<span></span>{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="storyline-notes-resize" data-chapter-notes-target="handle"
|
||||
data-action="pointerdown->chapter-notes#startResize" role="separator"
|
||||
aria-label="Resize notes panel" title="Drag to resize"></div>
|
||||
|
||||
<section class="storyline-notes-dock" data-chapter-notes-target="dock">
|
||||
<div class="storyline-notes-bar">
|
||||
<button type="button" class="storyline-notes-toggle" data-action="chapter-notes#toggle"
|
||||
data-chapter-notes-target="toggle" aria-expanded="true">
|
||||
<span class="storyline-notes-caret">▾</span> Chapter notes
|
||||
</button>
|
||||
{% if note.updatedAt %}
|
||||
<span class="text-body-secondary small">saved {{ note.updatedAt|date('Y-m-d H:i') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="storyline-notes-body">
|
||||
{{ form_start(form, {attr: {class: 'storyline-notes-form'}}) }}
|
||||
{{ form_errors(form.notes) }}
|
||||
{{ form_widget(form.notes, {attr: {class: 'form-control storyline-notes-text'}}) }}
|
||||
<div class="storyline-notes-actions">
|
||||
<button class="btn btn-primary btn-sm">Save notes</button>
|
||||
<span class="text-body-secondary small">This chapter only. Saved to the server.</span>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user