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:
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>
|
||||
Reference in New Issue
Block a user