feat: phase 1 public site skeleton — layout, routes, CSS, logo pipeline
Ship base Jinja layout (header/nav/main/footer with skip link and aria-current), mobile-first single-file CSS using the ROADMAP palette tokens, and four public routes: /, /about, /contact, /shop. Blog index renders via a stable PostService.list_published() stub returning [] — Phase 2 only swaps the body. About is static placeholder copy, /contact ships an inert form plus a mailto: link driven by ADMIN_CONTACT_EMAIL, /shop shows a "Coming soon" card. Adds a Pillow-based scripts/generate_static_assets.py producing resized logo PNG + WebP, multi-size favicon.ico, and a 180x180 apple-touch-icon on a cream background. Outputs committed for a reproducible build. Also ship docs/MANUAL_TESTING.md with per-route / responsive / a11y / static- asset checklists, and mark Phase 1 complete in docs/ROADMAP.md.
This commit is contained in:
42
app/templates/public/about.html
Normal file
42
app/templates/public/about.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{#
|
||||
About page — static placeholder copy. Head Hen will replace this via
|
||||
the Phase 4 admin CMS, so the prose below is deliberately generic and
|
||||
free of lorem ipsum. Per CLAUDE.md, the physical address is not shown
|
||||
anywhere on the site — only the town name.
|
||||
#}
|
||||
{% extends "public/base.html" %}
|
||||
|
||||
{% block title %}About — Chicken Babies R Us{% endblock %}
|
||||
{% block meta_description %}About Chicken Babies R Us — a small family farm in Morrison, Tennessee raising chickens, ducks, and geese.{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article class="page-article">
|
||||
<header class="page-article__header">
|
||||
<h1 class="page-article__title">About the farm</h1>
|
||||
</header>
|
||||
|
||||
<p>
|
||||
Chicken Babies R Us is a small family farm tucked into the rolling
|
||||
hills of Morrison, Tennessee. What started as a handful of chicks
|
||||
in a backyard brooder has grown into a flock of chickens, ducks, and
|
||||
geese that keep us busy (and entertained) year round.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The operation is run by Head Hen — the chief wrangler, egg
|
||||
gatherer, waterfowl-whisperer, and unofficial chicken photographer.
|
||||
She handles the day-to-day care of the birds and does most of the
|
||||
writing you'll find on this site. Expect updates on hatching plans,
|
||||
new arrivals, the occasional coop mishap, and whatever the geese
|
||||
decided to get into this week.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We're a hobby farm at heart, not a commercial one, which means we
|
||||
can take the time to know our birds and raise them the way we think
|
||||
they ought to be raised. If you're curious about what we've got
|
||||
going on — or just want to say hello — pop over to the
|
||||
contact page.
|
||||
</p>
|
||||
</article>
|
||||
{% endblock %}
|
||||
121
app/templates/public/base.html
Normal file
121
app/templates/public/base.html
Normal file
@@ -0,0 +1,121 @@
|
||||
{#
|
||||
Base layout for every public page.
|
||||
|
||||
Child templates override the following blocks:
|
||||
- title : the contents of <title>
|
||||
- meta_description : contents of <meta name="description">
|
||||
- content : the page body inside <main>
|
||||
|
||||
Design notes:
|
||||
- Semantic landmarks (<header>, <nav>, <main>, <footer>) for a11y.
|
||||
- Skip-link is the first focusable element so keyboard users can jump
|
||||
past the header.
|
||||
- aria-current="page" is applied to the active nav link by comparing
|
||||
the `active_nav` context variable the route passed us.
|
||||
- The mobile nav toggle uses addEventListener only — no inline event
|
||||
handlers — so we stay CSP-nonce-compatible when Phase 6 adds the
|
||||
strict CSP middleware.
|
||||
#}<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}Chicken Babies R Us{% endblock %}</title>
|
||||
<meta name="description" content="{% block meta_description %}Small-farm fresh eggs and happy birds, raised in Morrison, Tennessee.{% endblock %}">
|
||||
{# Self-hosted favicon + apple touch icon — no third-party CDNs. #}
|
||||
<link rel="icon" href="{{ url_for('static', path='img/favicon.ico') }}" sizes="any">
|
||||
<link rel="apple-touch-icon" href="{{ url_for('static', path='img/apple-touch-icon.png') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='css/site.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
{# Skip link: hidden until focused. First focusable element on the page. #}
|
||||
<a class="skip-link" href="#main-content">Skip to main content</a>
|
||||
|
||||
<header class="site-header">
|
||||
<div class="wrap site-header__wrap">
|
||||
<a class="site-header__brand" href="/" aria-label="Chicken Babies R Us home">
|
||||
{# WebP with PNG fallback — generated by scripts/generate_static_assets.py. #}
|
||||
<picture>
|
||||
<source srcset="{{ url_for('static', path='img/logo.webp') }}" type="image/webp">
|
||||
<img src="{{ url_for('static', path='img/logo.png') }}"
|
||||
alt="Chicken Babies R Us"
|
||||
height="48"
|
||||
class="site-header__logo">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
{# The mobile toggle button — script below attaches a click handler
|
||||
that flips aria-expanded and toggles .is-open on the nav. #}
|
||||
<button type="button"
|
||||
class="site-nav__toggle"
|
||||
id="nav-toggle"
|
||||
aria-controls="primary-nav"
|
||||
aria-expanded="false">
|
||||
<span class="visually-hidden">Toggle navigation</span>
|
||||
<span class="site-nav__toggle-bar" aria-hidden="true"></span>
|
||||
<span class="site-nav__toggle-bar" aria-hidden="true"></span>
|
||||
<span class="site-nav__toggle-bar" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<nav class="site-nav" id="primary-nav" aria-label="Primary">
|
||||
<ul class="site-nav__list">
|
||||
<li class="site-nav__item">
|
||||
<a href="/"
|
||||
class="site-nav__link{% if active_nav == 'home' %} is-active{% endif %}"
|
||||
{% if active_nav == 'home' %}aria-current="page"{% endif %}>Home</a>
|
||||
</li>
|
||||
<li class="site-nav__item">
|
||||
<a href="/about"
|
||||
class="site-nav__link{% if active_nav == 'about' %} is-active{% endif %}"
|
||||
{% if active_nav == 'about' %}aria-current="page"{% endif %}>About</a>
|
||||
</li>
|
||||
<li class="site-nav__item">
|
||||
<a href="/contact"
|
||||
class="site-nav__link{% if active_nav == 'contact' %} is-active{% endif %}"
|
||||
{% if active_nav == 'contact' %}aria-current="page"{% endif %}>Contact</a>
|
||||
</li>
|
||||
<li class="site-nav__item">
|
||||
<a href="/shop"
|
||||
class="site-nav__link nav--muted{% if active_nav == 'shop' %} is-active{% endif %}"
|
||||
{% if active_nav == 'shop' %}aria-current="page"{% endif %}>Shop</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="main-content" class="site-main" tabindex="-1">
|
||||
<div class="wrap">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="wrap site-footer__wrap">
|
||||
<p class="site-footer__tag">
|
||||
Chicken Babies R Us · Morrison, Tennessee
|
||||
</p>
|
||||
<p class="site-footer__legal">
|
||||
© {{ now_year or 2026 }} Chicken Babies R Us. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{# Mobile nav toggle. Tiny and CSP-friendly: no inline handlers, no JS
|
||||
framework. Phase 6's CSP will be compatible with moving this into an
|
||||
external file + nonce if we grow; for now the inline block stays. #}
|
||||
<script>
|
||||
(function () {
|
||||
"use strict";
|
||||
var toggle = document.getElementById("nav-toggle");
|
||||
var nav = document.getElementById("primary-nav");
|
||||
if (!toggle || !nav) { return; }
|
||||
toggle.addEventListener("click", function () {
|
||||
var expanded = toggle.getAttribute("aria-expanded") === "true";
|
||||
toggle.setAttribute("aria-expanded", expanded ? "false" : "true");
|
||||
nav.classList.toggle("is-open");
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
85
app/templates/public/contact.html
Normal file
85
app/templates/public/contact.html
Normal file
@@ -0,0 +1,85 @@
|
||||
{#
|
||||
Contact page — Phase 1 version.
|
||||
|
||||
The form is deliberately inert: no `method`, no `action`, all inputs
|
||||
and the submit button carry the `disabled` attribute. A muted note
|
||||
explains the form is coming soon; if `ADMIN_CONTACT_EMAIL` is set in
|
||||
the environment we render a `mailto:` link above the form so visitors
|
||||
still have a way to reach the farm.
|
||||
|
||||
Phase 5 replaces this template with a working POST handler, hCaptcha,
|
||||
honeypot, and rate limiting.
|
||||
|
||||
Context:
|
||||
- contact_email : str | None (from settings.admin_contact_email)
|
||||
- active_nav : "contact"
|
||||
#}
|
||||
{% extends "public/base.html" %}
|
||||
|
||||
{% block title %}Contact — Chicken Babies R Us{% endblock %}
|
||||
{% block meta_description %}Get in touch with Chicken Babies R Us.{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article class="page-article">
|
||||
<header class="page-article__header">
|
||||
<h1 class="page-article__title">Get in touch</h1>
|
||||
</header>
|
||||
|
||||
<p>
|
||||
We'd love to hear from you — questions about the birds,
|
||||
availability, or just to say hi.
|
||||
</p>
|
||||
|
||||
{% if contact_email %}
|
||||
<p class="contact-mailto">
|
||||
The easiest way to reach us right now is email:
|
||||
<a href="mailto:{{ contact_email }}">{{ contact_email }}</a>.
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="contact-mailto contact-mailto--muted">
|
||||
A direct email address will be posted here soon.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<p class="contact-form__note" role="note">
|
||||
Secure contact form coming soon.
|
||||
</p>
|
||||
|
||||
{# action="" and no method = form cannot submit. Every input is
|
||||
disabled so screen readers and the keyboard both respect the
|
||||
"not-yet-available" state. #}
|
||||
<form class="contact-form" action="" aria-describedby="contact-form-note" novalidate>
|
||||
<div class="contact-form__field">
|
||||
<label for="contact-name">Name</label>
|
||||
<input type="text"
|
||||
id="contact-name"
|
||||
name="name"
|
||||
autocomplete="name"
|
||||
disabled>
|
||||
</div>
|
||||
|
||||
<div class="contact-form__field">
|
||||
<label for="contact-email">Email</label>
|
||||
<input type="email"
|
||||
id="contact-email"
|
||||
name="email"
|
||||
autocomplete="email"
|
||||
disabled>
|
||||
</div>
|
||||
|
||||
<div class="contact-form__field">
|
||||
<label for="contact-message">Message</label>
|
||||
<textarea id="contact-message"
|
||||
name="message"
|
||||
rows="6"
|
||||
disabled></textarea>
|
||||
</div>
|
||||
|
||||
<div class="contact-form__actions">
|
||||
<button type="submit" class="btn btn--primary" disabled>
|
||||
Send message
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
{% endblock %}
|
||||
35
app/templates/public/home.html
Normal file
35
app/templates/public/home.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{#
|
||||
Home page / blog index.
|
||||
|
||||
Receives:
|
||||
- posts : list[PostSummary] (empty in Phase 1)
|
||||
- active_nav : str "home"
|
||||
#}
|
||||
{% extends "public/base.html" %}
|
||||
|
||||
{% block title %}Chicken Babies R Us — Home{% endblock %}
|
||||
{% block meta_description %}Updates from Chicken Babies R Us — a small family farm in Morrison, Tennessee.{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="page-intro">
|
||||
<h1 class="page-intro__title">Welcome to Chicken Babies R Us</h1>
|
||||
<p class="page-intro__lede">
|
||||
A tiny family farm in Morrison, Tennessee. Follow along for updates
|
||||
on our flock, hatching plans, and whatever Head Hen is up to this week.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="post-list" aria-label="Latest posts">
|
||||
{% if posts %}
|
||||
{% for post in posts %}
|
||||
{% include "public/partials/_post_card.html" %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{# Empty-state copy. Phase 2 seeds a welcome post so this state only
|
||||
ever shows up in unseeded dev databases and tests. #}
|
||||
<div class="post-list__empty">
|
||||
<p>No posts yet — check back soon!</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
26
app/templates/public/partials/_post_card.html
Normal file
26
app/templates/public/partials/_post_card.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{#
|
||||
Single blog card. Rendered once per PostSummary in the home-page loop.
|
||||
|
||||
Expects the loop variable `post` in scope with:
|
||||
- post.slug (str)
|
||||
- post.title (str)
|
||||
- post.published_at (datetime)
|
||||
- post.excerpt (str)
|
||||
|
||||
The post detail page does not exist yet (Phase 2 adds it), but we link
|
||||
to /posts/<slug> anyway so the card markup is final. Phase 2 will
|
||||
register the route; until then the link 404s, which is acceptable
|
||||
because the post list itself is empty in Phase 1.
|
||||
#}
|
||||
<article class="post-card">
|
||||
<header class="post-card__header">
|
||||
<h2 class="post-card__title">
|
||||
<a href="/posts/{{ post.slug }}">{{ post.title }}</a>
|
||||
</h2>
|
||||
<time class="post-card__date"
|
||||
datetime="{{ post.published_at.isoformat() }}">
|
||||
{{ post.published_at.strftime("%B %-d, %Y") }}
|
||||
</time>
|
||||
</header>
|
||||
<p class="post-card__excerpt">{{ post.excerpt }}</p>
|
||||
</article>
|
||||
32
app/templates/public/shop.html
Normal file
32
app/templates/public/shop.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{#
|
||||
Shop placeholder. Phase 7 replaces this with a real Stripe-backed
|
||||
catalog. For now the page itself is the "disabled" UI; the nav link
|
||||
uses the `nav--muted` class to hint that it isn't fully live.
|
||||
#}
|
||||
{% extends "public/base.html" %}
|
||||
|
||||
{% block title %}Shop — Chicken Babies R Us{% endblock %}
|
||||
{% block meta_description %}Our farm shop is coming soon — eggs, chicks, and waterfowl.{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article class="page-article">
|
||||
<header class="page-article__header">
|
||||
<h1 class="page-article__title">Shop</h1>
|
||||
</header>
|
||||
|
||||
<section class="shop-card" aria-label="Shop status">
|
||||
<h2 class="shop-card__title">Coming soon</h2>
|
||||
<p class="shop-card__body">
|
||||
We're getting the farm shop ready. Soon you'll be able to order
|
||||
eating eggs, fertile hatching eggs, day-old chicks, and a small
|
||||
selection of waterfowl (ducks and geese) when available. Pickup
|
||||
will be local to Morrison; we'll share details here when the
|
||||
shop goes live.
|
||||
</p>
|
||||
<p class="shop-card__body">
|
||||
In the meantime, if you're looking for something specific, the
|
||||
contact page is the best way to reach us.
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user