Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 196 additions & 13 deletions data/samples/html/dark_dev.html
Original file line number Diff line number Diff line change
@@ -1,25 +1,208 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Dark developer blog — terminal-inspired notes for night owls" />
<title>Dev Notes</title>
<meta name="description" content="Dark developer blog" />
<style>
body { font-family: ui-monospace, Consolas, monospace; background: #0b1220; color: #e2e8f0; }
header { background: #1e293b; padding: 1.5rem; border-bottom: 2px solid #38bdf8; }
a { color: #38bdf8; }
.sidebar { background: #111827; padding: 1rem; }
nav a { margin-right: 1rem; color: #7dd3fc; }
:root {
--bg: #0b1220;
--surface: #111827;
--panel: #1e293b;
--border: #334155;
--text: #e2e8f0;
--muted: #94a3b8;
--accent: #38bdf8;
--accent-soft: #7dd3fc;
--green: #4ade80;
--amber: #fbbf24;
--pink: #f472b6;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: ui-monospace, "SF Mono", "Cascadia Code", Consolas, monospace;
background:
radial-gradient(circle at 1px 1px, rgba(56, 189, 248, 0.08) 1px, transparent 0) 0 0 / 22px 22px,
var(--bg);
color: var(--text);
line-height: 1.6;
}
header {
background: var(--panel);
padding: 1.75rem 2rem;
border-bottom: 2px solid var(--accent);
}
header .prompt { color: var(--green); font-size: 0.8rem; margin: 0 0 0.35rem; }
header h1 {
margin: 0;
font-size: clamp(1.5rem, 3vw, 2rem);
letter-spacing: -0.02em;
}
header p { margin: 0.4rem 0 0; color: var(--muted); font-size: 0.95rem; }
nav.menu {
background: #0f172a;
padding: 0.75rem 2rem;
border-bottom: 1px solid var(--border);
}
nav.menu a {
color: var(--accent-soft);
margin-right: 1.25rem;
text-decoration: none;
font-size: 0.9rem;
}
nav.menu a:hover,
nav.menu a.active { color: #fff; border-bottom: 1px solid var(--accent); }
.layout {
display: flex;
gap: 1.5rem;
max-width: 1040px;
margin: 1.5rem auto;
padding: 0 1.25rem 2.5rem;
}
main { flex: 1; min-width: 0; }
aside.sidebar {
width: 240px;
flex-shrink: 0;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 1rem 1.1rem;
height: fit-content;
}
aside.sidebar h3 {
margin: 0 0 0.75rem;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
}
aside.sidebar ul { margin: 0; padding: 0; list-style: none; }
aside.sidebar li { margin-bottom: 0.45rem; font-size: 0.85rem; color: var(--accent-soft); }
article.post {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 1.25rem 1.35rem;
margin-bottom: 1.25rem;
}
article.post h2 {
margin: 0 0 0.5rem;
font-size: 1.15rem;
color: var(--accent);
}
.meta { color: var(--muted); font-size: 0.78rem; margin-bottom: 0.85rem; }
.tags { margin: 0.85rem 0 0; }
.tag {
display: inline-block;
font-size: 0.72rem;
padding: 0.15rem 0.5rem;
border-radius: 999px;
margin-right: 0.35rem;
border: 1px solid var(--border);
color: var(--accent-soft);
background: #0f172a;
}
.tag.go { color: var(--accent); }
.tag.rust { color: var(--amber); }
.tag.py { color: var(--green); }
.tag.ts { color: var(--pink); }
pre.code {
background: #020617;
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.9rem 1rem;
overflow-x: auto;
font-size: 0.82rem;
color: #cbd5e1;
margin: 0.85rem 0 0;
}
pre.code .kw { color: var(--pink); }
pre.code .fn { color: var(--accent); }
pre.code .str { color: var(--green); }
a { color: var(--accent); }
footer {
text-align: center;
padding: 1.75rem;
color: var(--muted);
font-size: 0.85rem;
border-top: 1px solid var(--border);
background: #020617;
}
@media (max-width: 760px) {
.layout { flex-direction: column; }
aside.sidebar { width: 100%; }
header, nav.menu { padding-left: 1.25rem; padding-right: 1.25rem; }
}
</style>
</head>
<body>
<header><h1>Dev Notes</h1></header>
<nav class="menu"><a href="/">Home</a><a href="/posts">Posts</a><a href="/about">About</a></nav>
<div style="display:flex;gap:1rem">
<header>
<p class="prompt">~/blog $ cat README.md</p>
<h1>Dev Notes</h1>
<p>Ship small PRs. Keep the feedback loop tight. Debug in the dark.</p>
</header>
<nav class="menu">
<a class="active" href="/">Home</a>
<a href="/posts">Posts</a>
<a href="/snippets">Snippets</a>
<a href="/about">About</a>
</nav>
<div class="layout">
<main>
<article><h2>Ship small PRs</h2><p>Keep changes reviewable and tested.</p></article>
<article class="post">
<h2>Ship small PRs</h2>
<p class="meta">2026-07-28 · 4 min read</p>
<p>Keep changes reviewable and tested. Prefer a focused diff over a weekend mega-merge that nobody can reason about.</p>
<pre class="code"><span class="kw">fn</span> <span class="fn">main</span>() {
println!(<span class="str">"reviewable &gt; clever"</span>);
}</pre>
<div class="tags">
<span class="tag rust">rust</span>
<span class="tag">ci</span>
</div>
</article>
<article class="post">
<h2>Typed boundaries beat tribal knowledge</h2>
<p class="meta">2026-07-12 · 6 min read</p>
<p>Document contracts in types and tests. When onboarding gets expensive, your interfaces were never clear enough.</p>
<pre class="code"><span class="kw">export function</span> <span class="fn">claim</span>(issue: <span class="str">number</span>) {
<span class="kw">return</span> { status: <span class="str">"claimed"</span>, issue };
}</pre>
<div class="tags">
<span class="tag ts">typescript</span>
<span class="tag py">python</span>
</div>
</article>
<article class="post">
<h2>Night-shift checklist</h2>
<p class="meta">2026-06-30 · 3 min read</p>
<p>Reproduce once. Fix once. Leave a regression test. Push before sunrise.</p>
<div class="tags">
<span class="tag go">go</span>
<span class="tag">ops</span>
</div>
</article>
</main>
<aside class="sidebar"><h3>Tags</h3><p>python, go, ci</p></aside>
<aside class="sidebar">
<h3>Tags</h3>
<ul>
<li>python</li>
<li>go</li>
<li>rust</li>
<li>ci</li>
<li>typescript</li>
</ul>
<h3 style="margin-top:1.25rem">Status</h3>
<ul>
<li>tests: green</li>
<li>deploy: ready</li>
</ul>
</aside>
</div>
<footer><p>Built for night owls</p></footer>
<footer>
<p>Built for night owls · Dev Notes © 2026</p>
</footer>
</body>
</html>
Binary file added docs/screenshots/dark_dev-desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/dark_dev-mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/demo-dark_dev.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion src/bloggereasy/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def demo_cmd(
template_for = {
"portfolio.html": "portfolio",
"news_portal.html": "news",
"dark_dev.html": "dark",
"dark_dev.html": "dark_dev",
}
for path in samples:
out = root / f"{path.stem}.xml"
Expand Down Expand Up @@ -329,6 +329,7 @@ def _validate_widgets(widgets: str) -> None:
def _sample_for_template(template: str) -> Path:
sample_map = {
"dark": "dark_dev.html",
"dark_dev": "dark_dev.html",
"docs": "docs_blog.html",
"food_recipe": "food_recipe.html",
"from-image": "portfolio.html",
Expand Down
4 changes: 2 additions & 2 deletions src/bloggereasy/gui/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ def _pick_sample(self) -> None:
mapping = {
"portfolio.html": "portfolio",
"news_portal.html": "news",
"dark_dev.html": "dark",
"dark_dev.html": "dark_dev",
"magazine.html": "magazine",
}
self.template.setCurrentText(mapping.get(self._html_path.name, "simple"))
Expand Down Expand Up @@ -672,7 +672,7 @@ def run_demo_batch(self) -> None:
template_for = {
"portfolio.html": "portfolio",
"news_portal.html": "news",
"dark_dev.html": "dark",
"dark_dev.html": "dark_dev",
"magazine.html": "magazine",
}
for path in samples:
Expand Down
8 changes: 7 additions & 1 deletion src/bloggereasy/theme/presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@
"dense": False,
"accent": "#0055aa",
},
"dark_dev": {
"layout_hint": "two-column",
"dark": True,
"dense": False,
"accent": "#38bdf8",
},
}


Expand All @@ -77,7 +83,7 @@ def apply_preset(structure: PageStructure | dict, template: str) -> dict:
out["features"] = feats
if preset.get("dark"):
out = apply_dark_variant(out)
if preset.get("accent") and not preset.get("dark"):
if preset.get("accent"):
colors = dict(out.get("colors") or {})
colors["primary"] = preset["accent"]
out["colors"] = colors
Expand Down
56 changes: 56 additions & 0 deletions tests/test_theme_dark_dev.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Theme pack sample: dark_dev (Fixes #24).

Verifies the dark_dev preset generates valid Blogger XML from the
self-contained HTML fixture, containing a b:skin block and a Blog widget.
"""

from __future__ import annotations

from pathlib import Path

from bloggereasy.integrations.sdk import generate_from_html
from bloggereasy.theme.presets import PRESETS
from bloggereasy.theme.validate import validate_theme_file

SAMPLES = Path(__file__).resolve().parents[1] / "data" / "samples" / "html"


def test_dark_dev_preset_registered() -> None:
assert "dark_dev" in PRESETS
assert PRESETS["dark_dev"]["dark"] is True
assert PRESETS["dark_dev"]["accent"] == "#38bdf8"
assert PRESETS["dark_dev"]["layout_hint"] == "two-column"


def test_dark_dev_sample_exists() -> None:
src = SAMPLES / "dark_dev.html"
assert src.is_file()
html = src.read_text(encoding="utf-8")
assert "Dev Notes" in html
assert "#0b1220" in html or "#0B1220" in html
assert "sidebar" in html


def test_gen_html_dark_dev_produces_skin_and_blog_widget(tmp_path: Path) -> None:
src = SAMPLES / "dark_dev.html"
out = tmp_path / "dark_dev.xml"
result = generate_from_html(src, out, template="dark_dev")

assert result["validation"]["ok"], result["validation"]
assert out.exists()

structure = result["structure"]
assert structure["layout"] == "two-column"
assert structure.get("features", {}).get("dark") is True

xml = out.read_text(encoding="utf-8")
assert "xmlns:b=" in xml
assert "b:skin" in xml
assert "type='Blog'" in xml or 'type="Blog"' in xml
# dark_dev accent + dark surface should land in the skin
assert "38bdf8" in xml
assert "#0f172a" in xml or "0f172a" in xml
assert "Template: dark_dev" in xml

v = validate_theme_file(out)
assert v["ok"] is True
6 changes: 3 additions & 3 deletions tests/test_theme_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
def test_dark_and_magazine_generate_valid_xml(tmp_path: Path) -> None:
samples = Path("data/samples/html")
cases = [
("dark_dev.html", "dark"),
("dark_dev.html", "dark_dev"),
("magazine.html", "magazine"),
("contact_card.html", "simple"),
("resume_cv.html", "simple"),
Expand All @@ -26,8 +26,8 @@ def test_dark_and_magazine_generate_valid_xml(tmp_path: Path) -> None:
xml = out.read_text(encoding="utf-8")
assert "b:skin" in xml or "skin" in xml.lower()
assert "Blog" in xml
# dark preset should push dark-ish background into skin
if tmpl == "dark":
# dark / dark_dev presets should push dark-ish background into skin
if tmpl in {"dark", "dark_dev"}:
assert "#0f172a" in xml or "0f172a" in xml
v = validate_theme_file(out)
assert v["ok"] is True