online Minecraft written book viewer

refactor(ui): make footer reused

kokirigla.de 93e40f05 f2e89532

verified
+173 -177
+8 -1
templates/base.html
··· 11 11 </head> 12 12 13 13 <body class="font-minecraft"> 14 - {% block body %}{% endblock %} 14 + <main class="{% block page_class %}page{% endblock %}" {% block page_attrs %}{% endblock %}> 15 + {% block body %}{% endblock %} 16 + <footer class="footer"> 17 + <p class="subtle">build <a 18 + href="https://tangled.org/did:plc:uthy5qqccx3hdwxo7sriplmh/nara/commit/{{ git_hash }}">{{ &git_hash[0..6] 19 + }}</a></p> 20 + </footer> 21 + </main> 15 22 </body> 16 23 17 24 </html>
+37 -40
templates/book.html
··· 12 12 <meta name="twitter:description" content="By {{ book.author }}. {{ book.page_count }} pages." /> 13 13 {% endblock %} 14 14 15 + {% block page_class %}page detail{% endblock %} 16 + {% block page_attrs %} style="--book-mask: url('/assets/image/{{ texture_kind }}/written_book.webp')"{% endblock %} 17 + 15 18 {% block body %} 16 - <main class="page detail" style="--book-mask: url('/assets/image/{{ texture_kind }}/written_book.webp')"> 17 - <header class="hero detail-hero"> 18 - <div class="brand"> 19 - <span class="enchanted book-badge"> 20 - <img class="item" src="/assets/image/{{ texture_kind }}/written_book.webp" alt=""> 21 - </span> 22 - <div> 23 - <p class="eyebrow">Book Detail</p> 24 - <h1 class="font-minecraft">{{ book.title }}</h1> 25 - <p class="meta">by <a href="{{ book.author_href }}">{{ book.author }}</a></p> 26 - <div class="meta-row"> 27 - {% if book.has_category %} 28 - <a class="tag-link" href="{{ book.category_href }}"><span class="tag">{{ book.category }}</span></a> 29 - {% endif %} 30 - {% if book.has_location %} 31 - <span class="tag subtle">{{ book.location }}</span> 32 - {% endif %} 33 - <span class="tag subtle">{{ book.page_count }} pages</span> 34 - </div> 19 + <header class="hero detail-hero"> 20 + <div class="brand"> 21 + <span class="enchanted book-badge"> 22 + <img class="item" src="/assets/image/{{ texture_kind }}/written_book.webp" alt=""> 23 + </span> 24 + <div> 25 + <p class="eyebrow">Book Detail</p> 26 + <h1 class="font-minecraft">{{ book.title }}</h1> 27 + <p class="meta">by <a href="{{ book.author_href }}">{{ book.author }}</a></p> 28 + <div class="meta-row"> 29 + {% if book.has_category %} 30 + <a class="tag-link" href="{{ book.category_href }}"><span class="tag">{{ book.category }}</span></a> 31 + {% endif %} 32 + {% if book.has_location %} 33 + <span class="tag subtle">{{ book.location }}</span> 34 + {% endif %} 35 + <span class="tag subtle">{{ book.page_count }} pages</span> 35 36 </div> 36 37 </div> 37 - <div class="detail-actions"> 38 - <a class="button ghost" href="{{ back_href }}">Back to list</a> 39 - </div> 40 - </header> 41 - 42 - <section class="detail-body"> 43 - <div class="pages"> 44 - {% for page in book.pages %} 45 - <article class="book-page"> 46 - <div class="book-page-sprite"> 47 - <p class="book-page-header font-minecraft">Page {{ page.index }} of {{ book.page_count }}</p> 48 - <div class="book-page-text font-minecraft">{{ page.html }}</div> 49 - </div> 50 - </article> 51 - {% endfor %} 52 - </div> 53 - </section> 38 + </div> 39 + <div class="detail-actions"> 40 + <a class="button ghost" href="{{ back_href }}">Back to list</a> 41 + </div> 42 + </header> 54 43 55 - <footer class="footer"> 56 - <p class="subtle">build {{ git_hash }}</p> 57 - </footer> 58 - </main> 44 + <section class="detail-body"> 45 + <div class="pages"> 46 + {% for page in book.pages %} 47 + <article class="book-page"> 48 + <div class="book-page-sprite"> 49 + <p class="book-page-header font-minecraft">Page {{ page.index }} of {{ book.page_count }}</p> 50 + <div class="book-page-text font-minecraft">{{ page.html }}</div> 51 + </div> 52 + </article> 53 + {% endfor %} 54 + </div> 55 + </section> 59 56 {% endblock %}
+128 -136
templates/index.html
··· 15 15 <meta name="twitter:image" content="/assets/image/{{ texture_kind }}/written_book.webp" /> 16 16 {% endblock %} 17 17 18 + {% block page_attrs %} style="--book-mask: url('/assets/image/{{ texture_kind }}/written_book.webp')"{% endblock %} 19 + 18 20 {% block body %} 19 - <main class="page" style="--book-mask: url('/assets/image/{{ texture_kind }}/written_book.webp')"> 20 - <header class="hero"> 21 - <div class="brand"> 22 - <span class="enchanted book-badge"> 23 - <img class="item" src="/assets/image/{{ texture_kind }}/written_book.webp" alt=""> 24 - </span> 25 - <div> 26 - <p class="eyebrow">nara</p> 27 - <h1 class="font-minecraft">Book Archive</h1> 28 - <p class="subtle">{{ book_count }} books indexed</p> 29 - </div> 21 + <header class="hero"> 22 + <div class="brand"> 23 + <span class="enchanted book-badge"> 24 + <img class="item" src="/assets/image/{{ texture_kind }}/written_book.webp" alt=""> 25 + </span> 26 + <div> 27 + <p class="eyebrow">nara</p> 28 + <h1 class="font-minecraft">Book Archive</h1> 29 + <p class="subtle">{{ book_count }} books indexed</p> 30 30 </div> 31 - 32 - <form class="search" method="get" action="/"> 33 - <label class="field"> 34 - <span>Search</span> 35 - <input type="search" name="q" value="{{ query.q }}" placeholder="Title, author, or text" /> 36 - </label> 37 - <label class="field"> 38 - <span>Scope</span> 39 - <select name="scope"> 40 - <option value="all" {% if query.scope=="all" %}selected{% endif %}>All fields</option> 41 - <option value="title" {% if query.scope=="title" %}selected{% endif %}>Title</option> 42 - <option value="author" {% if query.scope=="author" %}selected{% endif %}>Author</option> 43 - <option value="contents" {% if query.scope=="contents" %}selected{% endif %}>Contents</option> 44 - </select> 45 - </label> 46 - <label class="field"> 47 - <span>Author filter</span> 48 - <select name="author"> 49 - <option value="" {% if !query.has_author %}selected{% endif %}>All authors</option> 50 - {% for a in authors %} 51 - <option value="{{ a.name }}" {% if query.author==a.name %}selected{% endif %}>{{ a.name }}</option> 52 - {% endfor %} 53 - </select> 54 - </label> 55 - <label class="field"> 56 - <span>Category filter</span> 57 - <select name="category"> 58 - <option value="" {% if !query.has_category %}selected{% endif %}>All categories</option> 59 - {% for c in categories %} 60 - <option value="{{ c.name }}" {% if query.category==c.name %}selected{% endif %}>{{ c.name }} 61 - </option> 62 - {% endfor %} 63 - </select> 64 - </label> 65 - <div class="actions"> 66 - <button type="submit">Search</button> 67 - {% if query.active %} 68 - <a class="link-reset" href="/">Clear</a> 69 - {% endif %} 70 - <button class="surprise-button" type="submit" formaction="/random">Surprise me</button> 71 - </div> 72 - </form> 31 + </div> 73 32 74 - {% if query.has_category || query.has_author %} 75 - <div class="active-filters"> 76 - {% if query.has_category %} 77 - <span class="chip">Category: {{ query.category }}</span> 78 - {% endif %} 79 - {% if query.has_author %} 80 - <span class="chip">Author: {{ query.author }}</span> 33 + <form class="search" method="get" action="/"> 34 + <label class="field"> 35 + <span>Search</span> 36 + <input type="search" name="q" value="{{ query.q }}" placeholder="Title, author, or text" /> 37 + </label> 38 + <label class="field"> 39 + <span>Scope</span> 40 + <select name="scope"> 41 + <option value="all" {% if query.scope=="all" %}selected{% endif %}>All fields</option> 42 + <option value="title" {% if query.scope=="title" %}selected{% endif %}>Title</option> 43 + <option value="author" {% if query.scope=="author" %}selected{% endif %}>Author</option> 44 + <option value="contents" {% if query.scope=="contents" %}selected{% endif %}>Contents</option> 45 + </select> 46 + </label> 47 + <label class="field"> 48 + <span>Author filter</span> 49 + <select name="author"> 50 + <option value="" {% if !query.has_author %}selected{% endif %}>All authors</option> 51 + {% for a in authors %} 52 + <option value="{{ a.name }}" {% if query.author==a.name %}selected{% endif %}>{{ a.name }}</option> 53 + {% endfor %} 54 + </select> 55 + </label> 56 + <label class="field"> 57 + <span>Category filter</span> 58 + <select name="category"> 59 + <option value="" {% if !query.has_category %}selected{% endif %}>All categories</option> 60 + {% for c in categories %} 61 + <option value="{{ c.name }}" {% if query.category==c.name %}selected{% endif %}>{{ c.name }} 62 + </option> 63 + {% endfor %} 64 + </select> 65 + </label> 66 + <div class="actions"> 67 + <button type="submit">Search</button> 68 + {% if query.active %} 69 + <a class="link-reset" href="/">Clear</a> 81 70 {% endif %} 71 + <button class="surprise-button" type="submit" formaction="/random">Surprise me</button> 82 72 </div> 73 + </form> 74 + 75 + {% if query.has_category || query.has_author %} 76 + <div class="active-filters"> 77 + {% if query.has_category %} 78 + <span class="chip">Category: {{ query.category }}</span> 83 79 {% endif %} 84 - </header> 80 + {% if query.has_author %} 81 + <span class="chip">Author: {{ query.author }}</span> 82 + {% endif %} 83 + </div> 84 + {% endif %} 85 + </header> 85 86 86 - <section class="layout"> 87 - <aside class="sidebar"> 88 - <div class="panel"> 89 - <h2>Browse Categories</h2> 90 - <a class="all-link" href="/">All books</a> 91 - <ul class="category-list"> 92 - {% for c in categories %} 93 - <li> 94 - <a class="category-link {% if c.active %}active{% endif %}" href="{{ c.href }}"> 95 - <span>{{ c.name }}</span> 96 - <span class="count">{{ c.count }}</span> 97 - </a> 98 - </li> 99 - {% endfor %} 100 - </ul> 101 - </div> 102 - </aside> 87 + <section class="layout"> 88 + <aside class="sidebar"> 89 + <div class="panel"> 90 + <h2>Browse Categories</h2> 91 + <a class="all-link" href="/">All books</a> 92 + <ul class="category-list"> 93 + {% for c in categories %} 94 + <li> 95 + <a class="category-link {% if c.active %}active{% endif %}" href="{{ c.href }}"> 96 + <span>{{ c.name }}</span> 97 + <span class="count">{{ c.count }}</span> 98 + </a> 99 + </li> 100 + {% endfor %} 101 + </ul> 102 + </div> 103 + </aside> 103 104 104 - <section class="results"> 105 - <div class="results-header"> 106 - <div> 107 - <h2>{{ view_label }}</h2> 108 - <p class="subtle">{{ results_label }}</p> 109 - </div> 110 - {% if total > 0 %} 111 - <p class="subtle">Showing {{ page_start }}-{{ page_end }} of {{ total }}</p> 112 - {% endif %} 105 + <section class="results"> 106 + <div class="results-header"> 107 + <div> 108 + <h2>{{ view_label }}</h2> 109 + <p class="subtle">{{ results_label }}</p> 113 110 </div> 114 - 115 - {% if has_prev || has_next %} 116 - <nav class="pager pager-top"> 117 - {% if has_prev %} 118 - <a class="button ghost" 119 - href="/?{{ pager_query }}offset={{ prev_offset }}&limit={{ limit }}">Previous</a> 120 - {% endif %} 121 - <span class="pager-spacer"></span> 122 - {% if has_next %} 123 - <a class="button ghost" href="/?{{ pager_query }}offset={{ next_offset }}&limit={{ limit }}">Next</a> 124 - {% endif %} 125 - </nav> 111 + {% if total > 0 %} 112 + <p class="subtle">Showing {{ page_start }}-{{ page_end }} of {{ total }}</p> 126 113 {% endif %} 114 + </div> 127 115 128 - {% if books.len() == 0 %} 129 - <div class="empty-state"> 130 - <p>No books found.</p> 131 - <p class="subtle">Try a different search term or browse a category.</p> 132 - </div> 116 + {% if has_prev || has_next %} 117 + <nav class="pager pager-top"> 118 + {% if has_prev %} 119 + <a class="button ghost" href="/?{{ pager_query }}offset={{ prev_offset }}&limit={{ limit }}">Previous</a> 133 120 {% endif %} 121 + <span class="pager-spacer"></span> 122 + {% if has_next %} 123 + <a class="button ghost" href="/?{{ pager_query }}offset={{ next_offset }}&limit={{ limit }}">Next</a> 124 + {% endif %} 125 + </nav> 126 + {% endif %} 134 127 135 - <div class="card-grid icon-grid"> 136 - {% for book in books %} 137 - <article class="book-tile"> 138 - <a class="book-icon" href="{{ book.detail_href }}" aria-label="Open {{ book.title }}"> 139 - <span class="enchanted"> 140 - <img class="item" src="/assets/image/{{ texture_kind }}/written_book.webp" alt=""> 141 - </span> 142 - </a> 143 - <h3 class="font-minecraft">{{ book.title }}</h3> 144 - <p class="meta">by <a href="{{ book.author_href }}">{{ book.author }}</a></p> 145 - </article> 146 - {% endfor %} 147 - </div> 128 + {% if books.len() == 0 %} 129 + <div class="empty-state"> 130 + <p>No books found.</p> 131 + <p class="subtle">Try a different search term or browse a category.</p> 132 + </div> 133 + {% endif %} 148 134 149 - {% if has_prev || has_next %} 150 - <nav class="pager"> 151 - {% if has_prev %} 152 - <a class="button ghost" 153 - href="/?{{ pager_query }}offset={{ prev_offset }}&limit={{ limit }}">Previous</a> 154 - {% endif %} 155 - <span class="pager-spacer"></span> 156 - {% if has_next %} 157 - <a class="button ghost" href="/?{{ pager_query }}offset={{ next_offset }}&limit={{ limit }}">Next</a> 158 - {% endif %} 159 - </nav> 135 + <div class="card-grid icon-grid"> 136 + {% for book in books %} 137 + <article class="book-tile"> 138 + <a class="book-icon" href="{{ book.detail_href }}" aria-label="Open {{ book.title }}"> 139 + <span class="enchanted"> 140 + <img class="item" src="/assets/image/{{ texture_kind }}/written_book.webp" alt=""> 141 + </span> 142 + </a> 143 + <h3 class="font-minecraft">{{ book.title }}</h3> 144 + <p class="meta">by <a href="{{ book.author_href }}">{{ book.author }}</a></p> 145 + </article> 146 + {% endfor %} 147 + </div> 148 + 149 + {% if has_prev || has_next %} 150 + <nav class="pager"> 151 + {% if has_prev %} 152 + <a class="button ghost" href="/?{{ pager_query }}offset={{ prev_offset }}&limit={{ limit }}">Previous</a> 160 153 {% endif %} 161 - </section> 154 + <span class="pager-spacer"></span> 155 + {% if has_next %} 156 + <a class="button ghost" href="/?{{ pager_query }}offset={{ next_offset }}&limit={{ limit }}">Next</a> 157 + {% endif %} 158 + </nav> 159 + {% endif %} 162 160 </section> 163 - 164 - <footer class="footer"> 165 - <p class="subtle">build <a 166 - href="https://tangled.org/did:plc:uthy5qqccx3hdwxo7sriplmh/nara/commit/{{ git_hash }}">{{ 167 - &git_hash[0..6] }}</a></p> 168 - </footer> 169 - </main> 161 + </section> 170 162 {% endblock %}