From ae3d6a4c0caad3626fc69693a6fdacd1e93bbd93 Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Wed, 26 Aug 2026 14:12:18 +0200 Subject: [PATCH] UX, tilgjengelighet og design etter gjennomgang MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WCAG: - Mørk modus: --on-accent-token gir mørk tekst på lys aksent (knapper, badges, valgt fane, skip-lenke) – var 1.8:1, nå ~11:1. - Editor-feltet har label og aria-describedby; forhåndsvisningen er ikke lenger aria-live (leste opp hele innlegget for hvert tastetrykk). - Bilder settes inn med alt-plassholder markert for overskriving, som eget avsnitt etter gjeldende linje. - Berøringsmål ≥ 44px (nav, verktøylinje, lenkeknapper, select). - Faner: roving tabindex + piltaster. Knapper med tekst i stedet for emoji, aria-keyshortcuts på fet/kursiv. h1 på forsiden. - code bryter lange stier (innstillinger rullet horisontalt på mobil). UX: - Ulagret-vakt dekker også tittelen, med synlig «Ulagrede endringer». - 413 ved for stor fil gir lesbar melding; brukernavn beholdes ved feil innlogging; full publiserings-URL vises i editoren; historikk- lenke i meta-linjen; norsk datoformat i historikken; passord-hint. Design: - Header med favicon, større brødtekst/overskrifter, smalere måltekst, bedre avstander i innlegg og liste, footer som flex uten løse «·», nøytral sekundærknapp, innrammet faresone, editor-textarea fyller panelet, valgt fane henger sammen med panelet. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01JcEy43fNYpwg6K6oTKakWR --- soapbox/admin.py | 9 ++- soapbox/static/admin/admin.css | 72 +++++++++++++--------- soapbox/static/admin/editor.js | 74 ++++++++++++++++------- soapbox/templates/admin/edit.html | 36 ++++++----- soapbox/templates/admin/history.html | 4 +- soapbox/templates/admin/login.html | 2 +- soapbox/templates/admin/password.html | 3 +- soapbox/templates/admin/settings.html | 4 +- soapbox/themes/green/static/style.css | 54 ++++++++++++----- soapbox/themes/green/templates/base.html | 4 +- soapbox/themes/green/templates/index.html | 2 +- tests/test_app.py | 10 +++ 12 files changed, 182 insertions(+), 92 deletions(-) diff --git a/soapbox/admin.py b/soapbox/admin.py index 7ed5d38..10efd7c 100644 --- a/soapbox/admin.py +++ b/soapbox/admin.py @@ -149,7 +149,12 @@ def owner_required(view): @bp.context_processor def _inject(): - return {"csrf": csrf_token(), "user": getattr(g, "user", None), "site": _cfg().site} + return { + "csrf": csrf_token(), + "user": getattr(g, "user", None), + "site": _cfg().site, + "cfg": _cfg(), + } @bp.route("/login", methods=["GET", "POST"]) @@ -477,5 +482,5 @@ def settings(): @owner_required def rebuild(): n = build_site(_cfg()) - flash(f"Bygde siden på nytt ({n} innlegg).", "ok") + flash(f"Bygde nettstedet på nytt ({n} innlegg).", "ok") return redirect(url_for("admin.settings")) diff --git a/soapbox/static/admin/admin.css b/soapbox/static/admin/admin.css index 696154c..4b102e7 100644 --- a/soapbox/static/admin/admin.css +++ b/soapbox/static/admin/admin.css @@ -1,56 +1,69 @@ -/* Admin-grensesnitt. Mobil først; editor og forhåndsvisning side om side fra 900px. */ +/* Admin-grensesnitt. Mobil først; editor og forhåndsvisning side om side fra 900px. + Berøringsmål ≥ 44px på alle knapper og lenker i navigasjon/verktøylinje. */ :root { --bg: #f4f8f3; --fg: #1c2a1e; --muted: #4d6350; --accent: #1f6b3a; --accent-strong: #154d29; - --surface: #fff; --border: #c9dccb; --code-bg: #e6efe6; --danger: #a12626; + --on-accent: #fff; --surface: #fff; --border: #c9dccb; --code-bg: #e6efe6; --danger: #a12626; } @media (prefers-color-scheme: dark) { :root { --bg: #101a12; --fg: #e4ede4; --muted: #a7bda9; --accent: #7fd39a; --accent-strong: #a9e6bb; - --surface: #182619; --border: #2f4532; --code-bg: #1e2f20; --danger: #ff8a8a; + --on-accent: #0f1f13; --surface: #182619; --border: #2f4532; --code-bg: #1e2f20; --danger: #ff8a8a; } } @media (prefers-reduced-motion: reduce) { * { transition: none !important; } } * { box-sizing: border-box; } body { margin: 0; font-family: system-ui, sans-serif; background: var(--bg); color: var(--fg); line-height: 1.5; } -.skip { position: absolute; left: -999px; } .skip:focus { left: 0; background: var(--accent); color: #fff; padding: .5rem; z-index: 10; } +.skip { position: absolute; left: -999px; } .skip:focus { left: 0; background: var(--accent); color: var(--on-accent); padding: .5rem; z-index: 10; } +.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } a { color: var(--accent); } :focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; } -.topbar { display: flex; flex-wrap: wrap; gap: .5rem 1rem; align-items: center; padding: .6rem 1rem; border-bottom: 3px solid var(--accent); } -.topbar nav { display: flex; flex-wrap: wrap; gap: .25rem .9rem; align-items: center; } -.brand { font-weight: 700; color: var(--accent-strong); text-decoration: none; font-size: 1.15rem; } +code { overflow-wrap: anywhere; } +.topbar { display: flex; flex-wrap: wrap; gap: 0 1rem; align-items: center; padding: .4rem 1rem; border-bottom: 3px solid var(--accent); } +.topbar nav { display: flex; flex-wrap: wrap; gap: 0 .5rem; align-items: center; } +.topbar nav a, .topbar nav button.link { display: inline-block; padding: .7rem .25rem; min-height: 44px; } +.brand { font-weight: 700; color: var(--accent-strong); text-decoration: none; font-size: 1.15rem; padding: .7rem 0; } main { max-width: 70rem; margin: 0 auto; padding: 1rem; } -h1 { font-size: 1.5rem; margin: .5rem 0; } +h1 { font-size: 1.75rem; line-height: 1.2; margin: .5rem 0; } +h2 { font-size: 1.25rem; margin: 1.5rem 0 .5rem; } .row { display: flex; flex-wrap: wrap; gap: .75rem; align-items: center; justify-content: space-between; } .grow { flex: 1 1 18rem; } .stack { display: flex; flex-direction: column; gap: .75rem; } .narrow { max-width: 28rem; } label { display: flex; flex-direction: column; gap: .25rem; font-weight: 600; } -input, textarea, button { font: inherit; } -input[type=text], input:not([type]), input[type=password], textarea { - padding: .6rem; border: 1px solid var(--border); border-radius: 4px; background: var(--surface); color: var(--fg); width: 100%; +.hint { font-weight: normal; color: var(--muted); font-size: .9rem; } +input, textarea, button, select { font: inherit; } +input[type=text], input:not([type]), input[type=password], textarea, select { + padding: .6rem; border: 1px solid var(--border); border-radius: 4px; background: var(--surface); color: var(--fg); width: 100%; min-height: 44px; } -button { padding: .55rem .9rem; border: 1px solid var(--accent); border-radius: 4px; background: var(--surface); color: var(--accent-strong); cursor: pointer; min-height: 44px; } -button.primary { background: var(--accent); color: #fff; border-color: var(--accent); } -button.link { border: none; background: none; padding: .25rem; min-height: auto; text-decoration: underline; color: var(--accent); } +button { + padding: .55rem .9rem; border: 1px solid var(--border); border-radius: 4px; + background: var(--surface); color: var(--accent-strong); cursor: pointer; min-height: 44px; +} +button.primary { background: var(--accent); color: var(--on-accent); border-color: var(--accent); font-weight: 600; } +button.link { border: none; background: none; padding: .6rem .25rem; text-decoration: underline; color: var(--accent); } button.danger { color: var(--danger); } .inline { display: inline; } .meta { color: var(--muted); font-size: .9rem; } -.badge { background: var(--accent); color: #fff; padding: .05rem .45rem; border-radius: 999px; font-size: .8rem; } +.badge { background: var(--accent); color: var(--on-accent); padding: .05rem .45rem; border-radius: 999px; font-size: .8rem; } .flash { padding: .6rem .9rem; border-radius: 4px; border-left: 4px solid var(--accent); background: var(--surface); } .flash.error { border-color: var(--danger); } -.posts, .users { list-style: none; padding: 0; } +.posts, .users { list-style: none; padding: 0; max-width: 48rem; } .posts li, .users li { padding: .6rem 0; border-bottom: 1px solid var(--border); display: flex; flex-wrap: wrap; gap: .5rem 1rem; align-items: baseline; } +.posts li a { padding: .4rem 0; display: inline-block; } /* Editor */ -.tabs { display: flex; gap: .25rem; margin-top: 1rem; } -.tabs button { border-radius: 4px 4px 0 0; border-bottom: none; } -.tabs button[aria-selected=true] { background: var(--accent); color: #fff; } -.panes section { border: 1px solid var(--border); background: var(--surface); padding: .5rem; border-radius: 0 4px 4px 4px; } +.tabs { display: flex; gap: .25rem; margin-top: 1rem; position: relative; z-index: 1; margin-bottom: -1px; } +.tabs button { border-radius: 4px 4px 0 0; } +.tabs button[aria-selected=true] { background: var(--surface); border-color: var(--accent); border-bottom-color: var(--surface); font-weight: 600; } +.panes section { border: 1px solid var(--border); background: var(--surface); padding: .5rem; border-radius: 0 4px 4px 4px; display: flex; flex-direction: column; } +.panes section[hidden] { display: none; } /* display:flex over ville ellers slått ut [hidden] */ .toolbar { display: flex; flex-wrap: wrap; gap: .3rem; margin-bottom: .5rem; } -.toolbar button { min-height: 40px; padding: .3rem .6rem; } -textarea#body { min-height: 50vh; resize: vertical; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 1rem; line-height: 1.5; } +.toolbar button { min-width: 44px; padding: .3rem .6rem; } +textarea#body { min-height: 50vh; flex: 1; resize: vertical; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 1rem; line-height: 1.5; } textarea#body.dragover { outline: 3px dashed var(--accent); } -.preview { min-height: 50vh; padding: 0 .5rem; overflow-wrap: anywhere; } +#upload-status { min-height: 1.5rem; margin: .5rem 0 0; } +.preview { flex: 1; min-height: 50vh; padding: 0 .5rem; overflow-wrap: anywhere; } +.preview:has(> .meta:only-child) { display: grid; place-items: center; } .preview img { max-width: 100%; height: auto; } .preview pre, .preview code { background: var(--code-bg); } .preview pre { padding: 1rem; overflow-x: auto; } @@ -61,18 +74,21 @@ textarea#body.dragover { outline: 3px dashed var(--accent); } .tabs { display: none; } .panes { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 1rem; } .panes section { border-radius: 4px; } - .panes section[hidden] { display: block; } + .panes section[hidden] { display: flex; } } +.editor ~ * { margin-top: 1.25rem; } .images ul { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); gap: 1rem; } .images li { display: flex; flex-direction: column; gap: .25rem; font-size: .85rem; } .images img { width: 100%; aspect-ratio: 4/3; object-fit: cover; border-radius: 4px; border: 1px solid var(--border); } -.danger-zone { margin-top: 2rem; border-top: 1px solid var(--border); padding-top: 1rem; } +.danger-zone { border: 1px solid var(--danger); border-radius: 4px; padding: .5rem 1rem; } pre.log { background: var(--code-bg); padding: 1rem; overflow-x: auto; font-size: .85rem; } dl.meta dt { font-weight: 600; margin-top: .5rem; } dl.meta dd { margin: 0; } -.history { padding-left: 1.2rem; } -.history li { padding: .4rem 0; border-bottom: 1px solid var(--border); } -.history li[aria-current] { font-weight: 600; } +.history { padding-left: 1.5rem; } +.history li { padding: .4rem .5rem; border-bottom: 1px solid var(--border); } +.history li[aria-current] { font-weight: 600; border-left: 4px solid var(--accent); } +.history li[aria-current]::marker { color: var(--accent); } .history .meta { display: block; font-weight: normal; } +.history li a { display: inline-block; padding: .3rem 0; } pre.diff { background: var(--code-bg); padding: 1rem; overflow-x: auto; font-size: .85rem; line-height: 1.4; } pre.diff .add { color: #14632c; background: #d8f2df; display: inline-block; width: 100%; } pre.diff .del { color: #9a1c1c; background: #f8dcdc; display: inline-block; width: 100%; } diff --git a/soapbox/static/admin/editor.js b/soapbox/static/admin/editor.js index 9d03a1f..7fef042 100644 --- a/soapbox/static/admin/editor.js +++ b/soapbox/static/admin/editor.js @@ -7,21 +7,37 @@ const ta = document.getElementById("body"); const preview = document.getElementById("preview"); const status = document.getElementById("upload-status"); + const dirtyStatus = document.getElementById("dirty-status"); const csrf = form.dataset.csrf; let dirty = false; - // ---- tabs (kun synlige på små skjermer) ---- - const tabs = form.querySelectorAll('[role="tab"]'); - tabs.forEach((tab) => - tab.addEventListener("click", () => { - tabs.forEach((t) => { - const on = t === tab; - t.setAttribute("aria-selected", on); - document.getElementById(t.getAttribute("aria-controls")).hidden = !on; - }); - if (tab.id === "tab-preview") renderPreview(); - }) - ); + function setDirty(v) { + dirty = v; + dirtyStatus.textContent = v ? "Ulagrede endringer" : ""; + } + + // ---- faner (kun synlige på små skjermer). Roving tabindex + piltaster + // i henhold til WAI-ARIA-mønsteret for tabs. ---- + const tabs = [...form.querySelectorAll('[role="tab"]')]; + function selectTab(tab) { + tabs.forEach((t) => { + const on = t === tab; + t.setAttribute("aria-selected", on); + t.tabIndex = on ? 0 : -1; + document.getElementById(t.getAttribute("aria-controls")).hidden = !on; + }); + if (tab.id === "tab-preview") renderPreview(); + } + tabs.forEach((tab, i) => { + tab.addEventListener("click", () => selectTab(tab)); + tab.addEventListener("keydown", (ev) => { + if (ev.key !== "ArrowRight" && ev.key !== "ArrowLeft") return; + ev.preventDefault(); + const next = tabs[(i + (ev.key === "ArrowRight" ? 1 : tabs.length - 1)) % tabs.length]; + selectTab(next); + next.focus(); + }); + }); // ---- forhåndsvisning ---- let timer = null; @@ -41,10 +57,10 @@ } } ta.addEventListener("input", () => { - dirty = true; clearTimeout(timer); timer = setTimeout(renderPreview, 400); }); + form.addEventListener("input", () => setDirty(true)); // også tittelfeltet renderPreview(); // ---- tekstinnsetting ---- @@ -54,7 +70,7 @@ ta.setRangeText(before + sel + after, s, e, "select"); ta.setSelectionRange(s + before.length, s + before.length + sel.length); ta.focus(); - ta.dispatchEvent(new Event("input")); + ta.dispatchEvent(new Event("input", { bubbles: true })); } function prefixLines(prefix) { const s = ta.selectionStart, e = ta.selectionEnd; @@ -63,7 +79,7 @@ const out = block.split("\n").map((l) => (l.startsWith(prefix) ? l.slice(prefix.length) : prefix + l)).join("\n"); ta.setRangeText(out, start, e, "end"); ta.focus(); - ta.dispatchEvent(new Event("input")); + ta.dispatchEvent(new Event("input", { bubbles: true })); } form.querySelectorAll(".toolbar button").forEach((b) => { b.addEventListener("click", () => { @@ -106,19 +122,33 @@ fd.append("file", file, file.name); try { const res = await fetch(form.dataset.uploadUrl, { method: "POST", headers: { "X-CSRF": csrf }, body: fd }); - const data = await res.json(); + if (res.status === 413) throw new Error("Filen er for stor (maks 32 MB)."); + const data = res.ok ? await res.json() : await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || res.statusText); - // Bilder skal stå på egen linje for å bli egne avsnitt i Markdown. - const before = ta.value.slice(0, ta.selectionStart); - const lead = before === "" || before.endsWith("\n\n") ? "" : before.endsWith("\n") ? "\n" : "\n\n"; - insert(lead + data.markdown + "\n", "", ""); - status.textContent = "Lastet opp " + data.filename + ". Husk å lagre."; + insertImage(data.filename); + status.textContent = "Lastet opp " + data.filename + ". Skriv en bildebeskrivelse i klammene, og husk å lagre."; } catch (e) { status.textContent = "Opplasting feilet: " + e.message; } } + function insertImage(filename) { + // Bildet settes inn som eget avsnitt etter linjen markøren står i, så vi + // aldri splitter markup midt i et ord eller en markering. Alt-teksten er + // en plassholder som blir markert, klar til å skrives over. + const lineEnd = ta.value.indexOf("\n", ta.selectionEnd); + const pos = lineEnd === -1 ? ta.value.length : lineEnd; + const before = ta.value.slice(0, pos); + const lead = before === "" ? "" : before.endsWith("\n") ? "\n" : "\n\n"; + const alt = "Beskriv bildet"; + const md = lead + "![" + alt + "](" + filename + ")\n"; + ta.setRangeText(md, pos, pos, "end"); + const altStart = pos + lead.length + 2; + ta.setSelectionRange(altStart, altStart + alt.length); + ta.focus(); + ta.dispatchEvent(new Event("input", { bubbles: true })); + } // ---- ikke mist tekst ved uhell ---- - form.addEventListener("submit", () => { dirty = false; }); + form.addEventListener("submit", () => setDirty(false)); window.addEventListener("beforeunload", (ev) => { if (dirty) { ev.preventDefault(); ev.returnValue = ""; } }); })(); diff --git a/soapbox/templates/admin/edit.html b/soapbox/templates/admin/edit.html index 265f7de..7cf2879 100644 --- a/soapbox/templates/admin/edit.html +++ b/soapbox/templates/admin/edit.html @@ -5,36 +5,39 @@ data-upload-url="{{ url_for('admin.upload', post_id=post.id) }}" data-csrf="{{ csrf }}">
- + - {% if post.draft %}utkast URL: /{{ post.url_path }} (følger tittelen) - {% else %}Publisert på /{{ post.url_path }}{% endif %} + {% if post.draft %}utkast Publiseres på {{ cfg.site_url }}/{{ post.url_path }} (følger tittelen) + {% else %}Publisert på {{ cfg.site_url }}/{{ post.url_path }} + · Historikk{% endif %}
- - + +
- + + +

Markdown. Dra inn, lim inn eller trykk «Bilde» for å laste opp bilder.

@@ -45,6 +48,7 @@ {% else %} {% endif %} + @@ -67,7 +71,7 @@ {% endif %} -

Historikk for dette innlegget

+{% if post.draft %}

Historikk for dette innlegget

{% endif %}
diff --git a/soapbox/templates/admin/history.html b/soapbox/templates/admin/history.html index a64318a..81464a1 100644 --- a/soapbox/templates/admin/history.html +++ b/soapbox/templates/admin/history.html @@ -7,9 +7,9 @@ {% if not entries %}

Ingen historikk ennå.

{% endif %}
    {% for e in entries %} - + {{ e.subject }} - · {{ e.author }} · {{ e.sha[:7] }} + · {{ e.author }} · {{ e.sha[:7] }} {% endfor %}
diff --git a/soapbox/templates/admin/login.html b/soapbox/templates/admin/login.html index e5d101c..2ef0d0a 100644 --- a/soapbox/templates/admin/login.html +++ b/soapbox/templates/admin/login.html @@ -5,7 +5,7 @@ - +
diff --git a/soapbox/templates/admin/password.html b/soapbox/templates/admin/password.html index d9b7c17..32186c3 100644 --- a/soapbox/templates/admin/password.html +++ b/soapbox/templates/admin/password.html @@ -5,7 +5,8 @@
- +
{% endblock %} diff --git a/soapbox/templates/admin/settings.html b/soapbox/templates/admin/settings.html index 18b415c..6ff95f1 100644 --- a/soapbox/templates/admin/settings.html +++ b/soapbox/templates/admin/settings.html @@ -21,11 +21,11 @@
Base-path
{{ cfg.base_path or '/' }}
Site-URL (for RSS)
{{ cfg.site_url }}
Tema
{{ cfg.theme }} (standard fra miljø: {{ cfg.default_theme }})
-
Innhold
{{ cfg.content_dir }} (git-repo)
+
Innholdsmappe
{{ cfg.content_dir }} (git-repo)
- +

Siste endringer i git

diff --git a/soapbox/themes/green/static/style.css b/soapbox/themes/green/static/style.css index 9cdea8e..e88088b 100644 --- a/soapbox/themes/green/static/style.css +++ b/soapbox/themes/green/static/style.css @@ -1,16 +1,19 @@ /* Tema: "grønt er skjønt". Alle farger er CSS-variabler; et nytt tema kan nøye seg med å overstyre :root. - Kontrast: tekst/bakgrunn ≥ 7:1 (AAA), lenker ≥ 4.5:1 på begge skjemaer. */ + Kontrast: tekst/bakgrunn ≥ 7:1 (AAA), lenker ≥ 4.5:1 på begge skjemaer. + --on-accent er tekstfargen oppå --accent og byttes i mørk modus, der + aksenten er lys. */ :root { --bg: #f4f8f3; --fg: #1c2a1e; --muted: #4d6350; --accent: #1f6b3a; --accent-strong: #154d29; + --on-accent: #ffffff; --surface: #ffffff; --border: #c9dccb; --code-bg: #e6efe6; - --max-width: 42rem; + --max-width: 40rem; } @media (prefers-color-scheme: dark) { :root { @@ -19,6 +22,7 @@ --muted: #a7bda9; --accent: #7fd39a; --accent-strong: #a9e6bb; + --on-accent: #0f1f13; --surface: #182619; --border: #2f4532; --code-bg: #1e2f20; @@ -34,13 +38,18 @@ body { background: var(--bg); color: var(--fg); font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; - line-height: 1.6; + font-size: 1.0625rem; + line-height: 1.65; } .skip { position: absolute; left: -999px; top: 0; - background: var(--accent); color: #fff; padding: .5rem 1rem; + background: var(--accent); color: var(--on-accent); padding: .5rem 1rem; } .skip:focus { left: 0; z-index: 10; } +.visually-hidden { + position: absolute; width: 1px; height: 1px; overflow: hidden; + clip: rect(0 0 0 0); white-space: nowrap; +} .site-header, main, .site-footer { max-width: var(--max-width); margin: 0 auto; @@ -48,28 +57,45 @@ body { } .site-header { border-bottom: 3px solid var(--accent); padding-top: 1.5rem; } .site-title { - font-size: 1.75rem; font-weight: 700; text-decoration: none; color: var(--accent-strong); + display: inline-flex; align-items: center; gap: .6rem; + font-size: 1.5rem; font-weight: 700; letter-spacing: -.01em; + text-decoration: none; color: var(--accent-strong); +} +.site-title::before { + content: ""; width: 2rem; height: 2rem; flex: none; + background: url("favicon.svg") center / contain no-repeat; } .site-description { margin: .25rem 0 0; color: var(--muted); } +main { padding-top: 1.5rem; } a { color: var(--accent); } a:hover { color: var(--accent-strong); } a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; } -h1 { font-size: 1.9rem; line-height: 1.2; margin: 1rem 0 .25rem; } -h2 { font-size: 1.35rem; margin: 1.25rem 0 .25rem; } -.meta { color: var(--muted); font-size: .9rem; margin: 0 0 1rem; } -.summary { padding: .5rem 0; border-bottom: 1px solid var(--border); } -.summary h2 a { text-decoration: none; } +h1 { font-size: 2.1rem; line-height: 1.15; letter-spacing: -.015em; margin: .5rem 0 .35rem; } +h2 { font-size: 1.4rem; line-height: 1.25; margin: 2rem 0 .5rem; } +.meta { color: var(--muted); font-size: .9rem; margin: 0 0 1.5rem; font-variant-numeric: tabular-nums; } +.summary { padding: 1.25rem 0; border-bottom: 1px solid var(--border); } +.summary h2 { margin: 0 0 .25rem; } +.summary h2 a { color: var(--accent-strong); text-decoration: none; } .summary h2 a:hover { text-decoration: underline; } -.body img { max-width: 100%; height: auto; border-radius: 4px; } +.summary .meta { margin: 0; } +.body p, .body ul, .body ol { margin: 0 0 1.1rem; } +.body img { max-width: 100%; height: auto; display: block; margin: 1.5rem 0; border-radius: 4px; } .body pre { background: var(--code-bg); padding: 1rem; overflow-x: auto; border-radius: 4px; } -.body code { background: var(--code-bg); padding: .1em .3em; border-radius: 3px; font-size: .95em; } +.body code { background: var(--code-bg); padding: .1em .3em; border-radius: 3px; font-size: .95em; overflow-wrap: anywhere; } .body pre code { background: none; padding: 0; } .body blockquote { margin: 1rem 0; padding: .25rem 1rem; border-left: 4px solid var(--accent); color: var(--muted); } .body table { border-collapse: collapse; width: 100%; display: block; overflow-x: auto; } .body th, .body td { border: 1px solid var(--border); padding: .4rem .6rem; text-align: left; } -.site-footer { border-top: 1px solid var(--border); color: var(--muted); font-size: .9rem; margin-top: 2rem; } -.colophon { margin: .5rem 0 0; font-size: .85rem; } +.site-footer { + border-top: 1px solid var(--border); color: var(--muted); font-size: .9rem; + margin-top: 3rem; padding-top: 1.25rem; + display: flex; flex-wrap: wrap; justify-content: space-between; gap: .5rem 1rem; +} +.site-footer nav a { text-decoration: none; display: inline-block; padding: .5rem 0; } +.site-footer nav a:hover { text-decoration: underline; } +.site-footer nav a + a::before { content: "·"; margin: 0 .5rem; color: var(--border); } +.colophon { margin: 0; padding: .5rem 0; } diff --git a/soapbox/themes/green/templates/base.html b/soapbox/themes/green/templates/base.html index 9fab7dd..3393fe8 100644 --- a/soapbox/themes/green/templates/base.html +++ b/soapbox/themes/green/templates/base.html @@ -20,9 +20,7 @@