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
184 changes: 156 additions & 28 deletions data/samples/html/travel_journal.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,166 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Coastal Escape — Travel Story</title>
<meta name="description" content="Travel journal sample for BloggerEasy" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Travel journal sample for BloggerEasy — hero-led diary layout" />
<title>Coastal Escape — Travel Journal</title>
<style>
body { margin:0; font-family: Georgia, serif; color:#1c1917; background:#faf7f2; }
.hero { height:42vh; min-height:260px; background:
linear-gradient(180deg,rgba(15,23,42,0.15),rgba(15,23,42,0.55)),
linear-gradient(120deg,#0ea5e9,#0369a1 55%,#0f766e); display:flex; align-items:flex-end; }
.hero-inner { max-width:720px; margin:0 auto; padding:2rem 1.25rem; color:#fff; width:100%; }
.hero h1 { margin:0; font-size:clamp(1.8rem,4vw,2.6rem); font-weight:600; }
.hero p { margin:0.4rem 0 0; opacity:0.9; }
article { max-width:720px; margin:0 auto; padding:2rem 1.25rem 3.5rem; line-height:1.7; }
article h2 { font-size:1.25rem; margin-top:1.75rem; }
.meta { font-family: system-ui,sans-serif; color:#78716c; font-size:0.9rem; margin-bottom:1.5rem; }
footer { text-align:center; color:#a8a29e; font-family: system-ui,sans-serif; font-size:0.85rem; padding:2rem; }
:root {
--ink: #1c1917;
--muted: #78716c;
--paper: #faf7f2;
--card: #ffffff;
--accent: #0ea5e9;
--accent-deep: #0369a1;
--sea: #0f766e;
--line: #e7e5e4;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: Georgia, "Times New Roman", serif;
color: var(--ink);
background: var(--paper);
line-height: 1.7;
}
.hero {
min-height: 44vh;
display: flex;
align-items: flex-end;
color: #fff;
background:
linear-gradient(180deg, rgba(15, 23, 42, 0.12), rgba(15, 23, 42, 0.62)),
linear-gradient(120deg, var(--accent), var(--accent-deep) 55%, var(--sea));
}
.hero-inner {
width: 100%;
max-width: 720px;
margin: 0 auto;
padding: 2.5rem 1.25rem 2rem;
}
.hero .eyebrow {
font-family: system-ui, -apple-system, sans-serif;
text-transform: uppercase;
letter-spacing: 0.14em;
font-size: 0.72rem;
opacity: 0.85;
margin: 0 0 0.65rem;
}
.hero h1 {
margin: 0;
font-size: clamp(1.9rem, 4.5vw, 2.75rem);
font-weight: 600;
letter-spacing: -0.02em;
line-height: 1.15;
}
.hero p {
margin: 0.55rem 0 0;
font-size: 1.05rem;
opacity: 0.92;
max-width: 36ch;
}
nav.menu {
background: var(--card);
border-bottom: 1px solid var(--line);
padding: 0.85rem 1.25rem;
text-align: center;
font-family: system-ui, -apple-system, sans-serif;
}
nav.menu a {
color: var(--muted);
text-decoration: none;
margin: 0 0.9rem;
font-size: 0.82rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
}
nav.menu a:hover { color: var(--accent-deep); }
main {
max-width: 720px;
margin: 0 auto;
padding: 2rem 1.25rem 3.5rem;
}
.meta {
font-family: system-ui, -apple-system, sans-serif;
color: var(--muted);
font-size: 0.88rem;
margin: 0 0 1.5rem;
}
article.post {
background: var(--card);
border: 1px solid var(--line);
border-radius: 4px;
padding: 1.5rem 1.35rem;
margin-bottom: 1.35rem;
}
article.post h2 {
margin: 0 0 0.35rem;
font-size: 1.3rem;
font-weight: 600;
color: #0f2a4a;
}
article.post .date {
font-family: system-ui, -apple-system, sans-serif;
color: var(--muted);
font-size: 0.82rem;
font-style: italic;
margin-bottom: 0.85rem;
}
article.post p { margin: 0 0 0.85rem; }
article.post p:last-child { margin-bottom: 0; }
.note {
border-left: 3px solid var(--accent);
padding: 0.15rem 0 0.15rem 1rem;
color: #44403c;
font-style: italic;
}
footer {
text-align: center;
color: #a8a29e;
font-family: system-ui, -apple-system, sans-serif;
font-size: 0.85rem;
padding: 2rem 1rem 2.5rem;
border-top: 1px solid var(--line);
}
</style>
</head>
<body>
<div class="hero"><div class="hero-inner">
<h1>Three days on the blue coast</h1>
<p>Wind, salt, and a notebook full of sketches</p>
</div></div>
<article>
<p class="meta">Travel · Sample journal · BloggerEasy</p>
<p>We arrived at dusk as the fishing boats returned. The harbor smelled of citrus and diesel,
and every doorway seemed to open toward the sea.</p>
<h2>What to keep in a theme</h2>
<p>Long-form travel posts need a clear hero, readable body type, and gentle section breaks.
This fixture is meant to convert cleanly into Blogger XML.</p>
<h2>Packing notes</h2>
<p>Light layers, a spare battery, and patience for golden hour. Leave room in the bag for a shell
or two — not for the shelf, for the story.</p>
</article>
<header class="hero">
<div class="hero-inner">
<p class="eyebrow">Field notes · Mediterranean coast</p>
<h1>Three days on the blue coast</h1>
<p>Wind, salt, and a notebook full of sketches</p>
</div>
</header>
<nav class="menu">
<a href="/">Journal</a>
<a href="/routes">Routes</a>
<a href="/maps">Maps</a>
<a href="/about">About</a>
</nav>
<main>
<p class="meta">Travel · Sample journal · BloggerEasy travel_journal</p>
<article class="post">
<h2>Harbor at dusk</h2>
<div class="date">June 14, 2026</div>
<p>We arrived as the fishing boats returned. The harbor smelled of citrus and diesel,
and every doorway seemed to open toward the sea.</p>
<p class="note">Pack light layers, a spare battery, and patience for golden hour.</p>
</article>
<article class="post">
<h2>What a theme should keep</h2>
<div class="date">June 15, 2026</div>
<p>Long-form travel posts need a clear hero, readable body type, and gentle section breaks.
This fixture converts cleanly into Blogger XML with a skin and Blog widget.</p>
</article>
<article class="post">
<h2>Shells for the story</h2>
<div class="date">June 16, 2026</div>
<p>Leave room in the bag for a shell or two — not for the shelf, for the story.
Tomorrow we take the cliff path north and write until the ink runs dry.</p>
</article>
</main>
<footer>Demo HTML · BloggerEasy travel_journal</footer>
</body>
</html>
Binary file added docs/screenshots/demo-travel_journal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/bloggereasy/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ def _sample_for_template(template: str) -> Path:
"portfolio": "portfolio.html",
"portfolio_photo": "portfolio_photo.html",
"simple": "minimal_blog.html",
"travel_journal": "travel_journal.html",
}
if template not in PRESETS:
console.print(
Expand Down
6 changes: 6 additions & 0 deletions 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",
},
"travel_journal": {
"layout_hint": "single-column",
"dark": False,
"dense": False,
"accent": "#0ea5e9",
},
}


Expand Down
50 changes: 50 additions & 0 deletions tests/test_theme_travel_journal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Theme pack sample: travel_journal (Fixes #28).

Verifies the travel_journal 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_travel_journal_preset_registered() -> None:
assert "travel_journal" in PRESETS
assert PRESETS["travel_journal"]["accent"] == "#0ea5e9"
assert PRESETS["travel_journal"]["layout_hint"] == "single-column"


def test_travel_journal_sample_exists() -> None:
assert (SAMPLES / "travel_journal.html").is_file()


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

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

# Hero-led journal stays single-column for readable diary flow
assert result["structure"]["layout"] != "two-column"
title = (result["structure"].get("title") or "").lower()
assert "coast" in title or "travel" in title or "journal" in title or "escape" in title

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
# travel_journal accent should be pushed into the skin
assert "0ea5e9" in xml
assert "Template: travel_journal" in xml

v = validate_theme_file(out)
assert v["ok"] is True