Initial commit: The Daily EPUB full implementation
Full implementation of a personalized daily newspaper delivered as an EPUB. Articles are pulled from a local self-hosted Miniflux instance, enriched with comments, summarized and filtered by DeepSeek AI, and then assembled into two EPUB editions: standard and optimized for the Xteink X4 e-ink reader. Both are served by the local self-hosted BookOrbit OPDS server in a separate library. Then the X4 edition is futher converted to XTC format and served over a separate OPDS server hosted by the Rust binary. Runs are tracked in a local SQLite database so runs are idempotent per date. Full documentation of the plan is in docs/plans and setup and install instructions are in the README.md file.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# EPUB templates
|
||||
|
||||
Askama templates and stylesheets for the two editions (spec §3.10, implementation
|
||||
notes §11). `src/epub/build.rs` owns the structs they bind to; `askama.toml` at
|
||||
the crate root points askama here (`dirs = ["src/epub/templates"]`).
|
||||
|
||||
| File | Template struct | Purpose |
|
||||
|---|---|---|
|
||||
| `base.xhtml` | — | Shared XHTML skeleton (`{% block body_class %}`, `{% block content %}`) |
|
||||
| `cover_page.xhtml` | `CoverPage` | Page that displays the rasterized cover image |
|
||||
| `front_page.xhtml` | `FrontPage` | "From the Editor" + issue stats line |
|
||||
| `in_this_issue.xhtml` | `InThisIssue` | Introduction chapter: per-section linked index |
|
||||
| `section.xhtml` | `SectionPage` | Section title page + LLM intro |
|
||||
| `chapter.xhtml` | `ArticleChapter` | Article: header, body, rating/read-online footer |
|
||||
| `discussion.xhtml` | `DiscussionChapter` | Comment chapter (§3.7); body from `comments::render_xhtml` |
|
||||
| `world_briefing.xhtml` | `WorldBriefingChapter` | Wikipedia Current Events (§3.8), body from `world::render_xhtml` |
|
||||
| `colophon.xhtml` | `ColophonChapter` | Back matter: models, cost, counts |
|
||||
| `cover.svg` | `CoverSvg` | Typographic cover, rasterized with resvg + tiny-skia |
|
||||
| `style.css` | — | Standard-edition stylesheet, embedded as `stylesheet.css` |
|
||||
| `style-x4.css` | — | X4 stylesheet: no floats/flex/grid, no fonts, hyphenation on |
|
||||
|
||||
Conventions:
|
||||
|
||||
- Every content template `{% extends "base.xhtml" %}` and provides `title`.
|
||||
- Templates declare `escape = "html"` — `.xhtml`/`.svg` are not in askama's
|
||||
default escaper extension list. Escaping emits numeric character references
|
||||
(`&`), which are valid XML; only pre-sanitized markup uses `|safe`.
|
||||
- Markup that reaches `|safe` has gone through `ammonia` **and**
|
||||
`epub::images::to_xhtml` (void elements self-closed, ` ` → ` `) so
|
||||
the output parses as XML, as EPUB3 content documents must.
|
||||
- Entities other than the five XML built-ins are written as numeric references
|
||||
in the templates themselves (`·`, `👍`, …).
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>{{ title }}</title>
|
||||
<link rel="stylesheet" type="text/css" href="stylesheet.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="chapter {% block body_class %}text{% endblock %}">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,34 @@
|
||||
{% extends "base.xhtml" %}
|
||||
{% block body_class %}article{% endblock %}
|
||||
{% block content %}
|
||||
<div class="article-header">
|
||||
<h1 class="article-title">{{ article_title }}</h1>
|
||||
{% if let Some(line) = byline %}
|
||||
<p class="byline">{{ line }}</p>
|
||||
{% endif %}
|
||||
<p class="meta">{{ meta_line }}</p>
|
||||
{% if let Some(line) = social_line %}
|
||||
<p class="social">{{ line }}</p>
|
||||
{% endif %}
|
||||
{% if let Some(text) = summary %}
|
||||
<p class="summary">{{ text }}</p>
|
||||
{% endif %}
|
||||
{% if excerpt_only %}
|
||||
<p class="notice">(excerpt only — read online)</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<hr class="rule"/>
|
||||
<div class="article-body">
|
||||
{{ body_html|safe }}
|
||||
</div>
|
||||
<hr class="rule"/>
|
||||
<div class="article-footer">
|
||||
{% if let Some(links) = rating %}
|
||||
<p class="rating">Was this a good pick? <a href="{{ links.up_url }}">[ 👍 Yes ]</a> · <a href="{{ links.down_url }}">[ 👎 No ]</a></p>
|
||||
{% endif %}
|
||||
<p class="read-online"><a href="{{ read_online_url }}">Read online ↗</a></p>
|
||||
{% if let Some(href) = discussion_href %}
|
||||
<p class="see-discussion"><a href="{{ href }}">💬 Read the discussion</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,25 @@
|
||||
{% extends "base.xhtml" %}
|
||||
{% block body_class %}colophon{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Colophon</h1>
|
||||
<p>
|
||||
<em>The Daily EPUB</em> is assembled every morning from a personal Miniflux
|
||||
feed reader: entries are deduplicated, read in full, weighed against social
|
||||
proof, then scored, sectioned and introduced by a language model.
|
||||
</p>
|
||||
<dl class="colophon-facts">
|
||||
<dt class="fact-key">Issue</dt><dd class="fact-value">No. {{ issue_number }} · {{ display_date }}</dd>
|
||||
<dt class="fact-key">Generated</dt><dd class="fact-value">{{ generated_at }}</dd>
|
||||
<dt class="fact-key">Curation model</dt><dd class="fact-value">{{ model }}</dd>
|
||||
<dt class="fact-key">Entries considered</dt><dd class="fact-value">{{ entries_fetched }} from {{ feeds_seen }} feeds</dd>
|
||||
<dt class="fact-key">Candidates scored</dt><dd class="fact-value">{{ candidates }}</dd>
|
||||
<dt class="fact-key">Articles selected</dt><dd class="fact-value">{{ article_count }} across {{ section_count }} sections</dd>
|
||||
<dt class="fact-key">Words</dt><dd class="fact-value">{{ total_words }} · {{ reading_line }}</dd>
|
||||
<dt class="fact-key">Token cost</dt><dd class="fact-value">{{ cost_usd }}</dd>
|
||||
<dt class="fact-key">Generator</dt><dd class="fact-value">{{ generator_version }}</dd>
|
||||
</dl>
|
||||
<p class="attribution">
|
||||
Article text belongs to its authors and publications; excerpts and links are
|
||||
provided for personal reading. Comment excerpts belong to their posters.
|
||||
</p>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,25 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="{{ width }}" height="{{ height }}" viewBox="0 0 {{ width }} {{ height }}">
|
||||
<rect x="0" y="0" width="{{ width }}" height="{{ height }}" fill="#ffffff"/>
|
||||
<rect x="{{ margin }}" y="{{ margin }}" width="{{ inner_width }}" height="{{ inner_height }}"
|
||||
fill="none" stroke="#111111" stroke-width="{{ border }}"/>
|
||||
<text x="{{ center_x }}" y="{{ masthead_y }}" text-anchor="middle" fill="#111111"
|
||||
font-family="Georgia, 'Times New Roman', Times, serif" font-size="{{ masthead_size }}">The Daily EPUB</text>
|
||||
<line x1="{{ rule_x1 }}" y1="{{ rule_y }}" x2="{{ rule_x2 }}" y2="{{ rule_y }}" stroke="#111111" stroke-width="{{ border }}"/>
|
||||
<text x="{{ center_x }}" y="{{ weekday_y }}" text-anchor="middle" fill="#111111"
|
||||
font-family="Georgia, 'Times New Roman', Times, serif" font-size="{{ weekday_size }}">{{ weekday }}</text>
|
||||
<text x="{{ center_x }}" y="{{ date_y }}" text-anchor="middle" fill="#111111"
|
||||
font-family="Georgia, 'Times New Roman', Times, serif" font-size="{{ date_size }}">{{ long_date }}</text>
|
||||
<line x1="{{ rule_x1 }}" y1="{{ rule2_y }}" x2="{{ rule_x2 }}" y2="{{ rule2_y }}" stroke="#111111" stroke-width="{{ hairline }}"/>
|
||||
<text x="{{ center_x }}" y="{{ issue_y }}" text-anchor="middle" fill="#111111"
|
||||
font-family="Georgia, 'Times New Roman', Times, serif" font-size="{{ issue_size }}">No. {{ issue_number }}</text>
|
||||
<text x="{{ center_x }}" y="{{ stats_y }}" text-anchor="middle" fill="#111111"
|
||||
font-family="Georgia, 'Times New Roman', Times, serif" font-size="{{ stats_size }}">{{ stats_line }}</text>
|
||||
{% if !edition_tag.is_empty() %}
|
||||
<rect x="{{ badge_x }}" y="{{ badge_y }}" width="{{ badge_width }}" height="{{ badge_height }}"
|
||||
rx="{{ badge_radius }}" fill="#111111"/>
|
||||
<text x="{{ center_x }}" y="{{ badge_text_y }}" text-anchor="middle" fill="#ffffff"
|
||||
font-family="Georgia, 'Times New Roman', Times, serif" font-size="{{ badge_size }}">{{ edition_tag }}</text>
|
||||
{% endif %}
|
||||
<text x="{{ center_x }}" y="{{ footer_y }}" text-anchor="middle" fill="#111111"
|
||||
font-family="Georgia, 'Times New Roman', Times, serif" font-size="{{ footer_size }}">{{ footer }}</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,5 @@
|
||||
{% extends "base.xhtml" %}
|
||||
{% block body_class %}cover-page{% endblock %}
|
||||
{% block content %}
|
||||
<div class="cover-image"><img src="cover.png" alt="{{ alt }}"/></div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,8 @@
|
||||
{% extends "base.xhtml" %}
|
||||
{% block body_class %}discussion{% endblock %}
|
||||
{% block content %}
|
||||
<h1 class="discussion-title">{{ heading }}</h1>
|
||||
<p class="discussion-note">Selected threads, truncated for reading on e-ink.</p>
|
||||
{{ body_html|safe }}
|
||||
<p class="back-link"><a href="{{ article_href }}">↩ Back to the article</a></p>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,12 @@
|
||||
{% extends "base.xhtml" %}
|
||||
{% block body_class %}front-page{% endblock %}
|
||||
{% block content %}
|
||||
<h1 class="masthead">The Daily EPUB</h1>
|
||||
<p class="dateline">{{ display_date }} · No. {{ issue_number }}</p>
|
||||
<hr class="rule"/>
|
||||
<h2 class="kicker">From the Editor</h2>
|
||||
<div class="editorial">
|
||||
{{ body_html|safe }}
|
||||
</div>
|
||||
<p class="stats">{{ stats_line }}</p>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,20 @@
|
||||
{% extends "base.xhtml" %}
|
||||
{% block body_class %}in-this-issue{% endblock %}
|
||||
{% block content %}
|
||||
<h1>In This Issue</h1>
|
||||
<p class="stats">{{ stats_line }}</p>
|
||||
{% for section in sections %}
|
||||
<h2 class="index-section">{{ section.name }}</h2>
|
||||
<ul class="index-list">
|
||||
{% for entry in section.entries %}
|
||||
<li class="index-entry">
|
||||
<p class="index-title"><a href="{{ entry.href }}">{{ entry.title }}</a></p>
|
||||
<p class="index-meta">{{ entry.source }} · {{ entry.reading_minutes }} min read</p>
|
||||
{% if !entry.summary.is_empty() %}
|
||||
<p class="index-summary">{{ entry.summary }}</p>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,9 @@
|
||||
{% extends "base.xhtml" %}
|
||||
{% block body_class %}section-page{% endblock %}
|
||||
{% block content %}
|
||||
<h1 class="section-title">{{ name }}</h1>
|
||||
<hr class="rule"/>
|
||||
{% if let Some(text) = intro %}
|
||||
<p class="section-intro">{{ text }}</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,188 @@
|
||||
/* Xteink X4 stylesheet (spec §3.10 "X4 edition").
|
||||
No floats, no flex, no grid, no embedded fonts, larger base font,
|
||||
generous line-height, hyphenation on. 480x800, 2-bit grayscale.
|
||||
|
||||
Selectors are `tag`, `.class` and `tag.class` only — the X4 firmware's CSS
|
||||
engine does not support descendant combinators, so a rule like
|
||||
`.comment-body p` is silently dropped on the device. Where a tag rule needs
|
||||
an exception, the `tag.class` override follows it immediately so the result
|
||||
is right whether the engine resolves by specificity or by source order. */
|
||||
|
||||
@page {
|
||||
margin: 0.4em;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: serif;
|
||||
font-size: 1.2em;
|
||||
line-height: 1.7;
|
||||
margin: 0 0.5em;
|
||||
text-align: left;
|
||||
hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
adobe-hyphenate: auto;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.chapter {
|
||||
page-break-before: always;
|
||||
break-before: page;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
font-weight: bold;
|
||||
line-height: 1.3;
|
||||
page-break-after: avoid;
|
||||
break-after: avoid;
|
||||
margin: 0.6em 0 0.35em 0;
|
||||
hyphens: none;
|
||||
-webkit-hyphens: none;
|
||||
}
|
||||
|
||||
h1 { font-size: 1.35em; }
|
||||
h2 { font-size: 1.15em; }
|
||||
h3, h4 { font-size: 1em; }
|
||||
|
||||
p {
|
||||
margin: 0 0 0.6em 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000000;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
hr.rule {
|
||||
border: 0;
|
||||
border-top: 1px solid #000000;
|
||||
margin: 0.7em 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.cover-page {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.masthead {
|
||||
font-size: 1.6em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dateline,
|
||||
.stats,
|
||||
.meta,
|
||||
.social,
|
||||
.index-meta,
|
||||
.discussion-note,
|
||||
.attribution,
|
||||
.comment-meta {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.dateline,
|
||||
.stats {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-page {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.5em;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.section-intro,
|
||||
.summary,
|
||||
.byline {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 0 0 0.5em 1em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.index-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.index-entry {
|
||||
margin: 0 0 0.8em 0;
|
||||
}
|
||||
|
||||
.index-title,
|
||||
.index-meta,
|
||||
.index-summary {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0.6em 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
figcaption,
|
||||
.image-caption,
|
||||
.image-placeholder {
|
||||
font-size: 0.85em;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
font-family: monospace;
|
||||
font-size: 0.85em;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
hyphens: none;
|
||||
-webkit-hyphens: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid #666666;
|
||||
padding: 0.15em 0.3em;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0.5em 0 0.5em 0.3em;
|
||||
padding-left: 0.5em;
|
||||
border-left: 2px solid #666666;
|
||||
}
|
||||
|
||||
blockquote.comment {
|
||||
margin: 0.4em 0;
|
||||
padding-left: 0.5em;
|
||||
border-left: 2px solid #666666;
|
||||
}
|
||||
|
||||
blockquote.reply {
|
||||
border-left: 1px solid #999999;
|
||||
}
|
||||
|
||||
p.comment-line {
|
||||
margin: 0 0 0.35em 0;
|
||||
}
|
||||
|
||||
dt.fact-key {
|
||||
font-weight: bold;
|
||||
margin-top: 0.35em;
|
||||
}
|
||||
|
||||
dd.fact-value {
|
||||
margin: 0 0 0 0.8em;
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/* Standard-edition stylesheet (spec §3.10 "CSS").
|
||||
Serif body, grayscale only, page-break-before on chapters,
|
||||
blockquote-indent comment styling. Tuned for e-ink readers. */
|
||||
|
||||
@page {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Georgia, "Times New Roman", Times, serif;
|
||||
font-size: 1em;
|
||||
line-height: 1.5;
|
||||
margin: 0 1em;
|
||||
text-align: left;
|
||||
widows: 2;
|
||||
orphans: 2;
|
||||
}
|
||||
|
||||
.chapter {
|
||||
page-break-before: always;
|
||||
break-before: page;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
font-weight: normal;
|
||||
line-height: 1.25;
|
||||
page-break-after: avoid;
|
||||
break-after: avoid;
|
||||
margin: 0.8em 0 0.4em 0;
|
||||
}
|
||||
|
||||
h1 { font-size: 1.5em; }
|
||||
h2 { font-size: 1.25em; }
|
||||
h3 { font-size: 1.1em; }
|
||||
h4 { font-size: 1em; font-style: italic; }
|
||||
|
||||
p {
|
||||
margin: 0 0 0.7em 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000000;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
hr.rule {
|
||||
border: 0;
|
||||
border-top: 1px solid #000000;
|
||||
margin: 0.9em 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* --- cover ------------------------------------------------------------- */
|
||||
|
||||
.cover-page {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.cover-image img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
/* --- front page -------------------------------------------------------- */
|
||||
|
||||
.masthead {
|
||||
font-size: 2.1em;
|
||||
text-align: center;
|
||||
letter-spacing: 0.02em;
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
|
||||
.dateline {
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
font-variant: small-caps;
|
||||
margin-bottom: 0.6em;
|
||||
}
|
||||
|
||||
.kicker {
|
||||
font-variant: small-caps;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats {
|
||||
font-size: 0.85em;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
/* --- in this issue ----------------------------------------------------- */
|
||||
|
||||
.index-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.index-entry {
|
||||
margin: 0 0 0.9em 0;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
.index-title {
|
||||
margin: 0;
|
||||
font-size: 1.05em;
|
||||
}
|
||||
|
||||
.index-meta {
|
||||
margin: 0;
|
||||
font-size: 0.8em;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
.index-summary {
|
||||
margin: 0.2em 0 0 0;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* --- sections and articles --------------------------------------------- */
|
||||
|
||||
.section-page {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 2em;
|
||||
font-variant: small-caps;
|
||||
margin-top: 2.5em;
|
||||
}
|
||||
|
||||
.section-intro {
|
||||
font-style: italic;
|
||||
margin: 0 1.5em;
|
||||
}
|
||||
|
||||
.article-title {
|
||||
font-size: 1.6em;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
||||
.byline {
|
||||
margin: 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.meta, .social {
|
||||
margin: 0;
|
||||
font-size: 0.8em;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
.summary {
|
||||
margin: 0.5em 0 0 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.notice {
|
||||
font-size: 0.85em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.article-body img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.article-body figure {
|
||||
margin: 0.8em 0;
|
||||
text-align: center;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
.article-body figcaption,
|
||||
.image-caption {
|
||||
font-size: 0.8em;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image-placeholder {
|
||||
font-size: 0.85em;
|
||||
font-style: italic;
|
||||
color: #444444;
|
||||
}
|
||||
|
||||
.article-body blockquote {
|
||||
margin: 0.6em 0 0.6em 1em;
|
||||
padding-left: 0.6em;
|
||||
border-left: 2px solid #999999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.article-body pre,
|
||||
.article-body code {
|
||||
font-family: "DejaVu Sans Mono", "Courier New", monospace;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.article-body pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
border-left: 2px solid #cccccc;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.article-body table {
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.article-body td,
|
||||
.article-body th {
|
||||
border: 1px solid #999999;
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
|
||||
.article-footer {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.rating a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* --- discussion chapters (§3.7) ---------------------------------------- */
|
||||
|
||||
.discussion-note {
|
||||
font-size: 0.8em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.discussion-source {
|
||||
font-variant: small-caps;
|
||||
font-size: 1.1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
blockquote.comment {
|
||||
border-left: 2px solid #888888;
|
||||
margin: 0.5em 0 0.5em 0;
|
||||
padding-left: 0.7em;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
blockquote.comment blockquote.comment {
|
||||
border-left: 1px solid #aaaaaa;
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
|
||||
.comment-meta {
|
||||
font-size: 0.78em;
|
||||
font-variant: small-caps;
|
||||
margin: 0 0 0.15em 0;
|
||||
}
|
||||
|
||||
.comment-body p {
|
||||
margin: 0 0 0.4em 0;
|
||||
}
|
||||
|
||||
/* --- world briefing and colophon --------------------------------------- */
|
||||
|
||||
.world-body ul {
|
||||
margin: 0 0 0.6em 1.1em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.world-body li {
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.attribution {
|
||||
font-size: 0.8em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.colophon-facts dt {
|
||||
font-variant: small-caps;
|
||||
margin-top: 0.4em;
|
||||
}
|
||||
|
||||
.colophon-facts dd {
|
||||
margin: 0 0 0 1em;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% extends "base.xhtml" %}
|
||||
{% block body_class %}world-briefing{% endblock %}
|
||||
{% block content %}
|
||||
<h1>World Briefing</h1>
|
||||
<p class="dateline">{{ display_date }}</p>
|
||||
<hr class="rule"/>
|
||||
<div class="world-body">
|
||||
{{ body_html|safe }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user