Cookbook — Rustic / Farmhouse Cookbook landing
A cozy farmhouse cookbook landing page built with warm cream, sage, and clay tones, a serif-and-sans editorial pairing, and CSS-gradient food photography. It pairs a handwritten overline hero and pre-order CTA with featured recipe cards, a from-our-kitchen story strip, seasonal categories, and a validated newsletter sign-up. Vanilla JavaScript powers reveal-on-scroll animations, a recipe-of-the-day rotator, and toast feedback, all responsive from phone to desktop.
MCP
Code
:root {
/* warm cookbook base */
--cream: #faf6ef;
--paper: #fffdf8;
--ink: #2b2622;
--ink-2: #5c534a;
--muted: #8a7f73;
--tomato: #d6452b;
--tomato-d: #b8351e;
--saffron: #e8a33d;
--sage: #7c8a6b;
--clay: #c8775a;
--line: rgba(43, 38, 34, 0.12);
--line-2: rgba(43, 38, 34, 0.2);
--ok: #3f8f5f;
--warn: #d98a2b;
--danger: #c8412b;
/* themed override: cream + sage + clay, kraft, cozy handmade */
--kraft: #d9c7a8;
--kraft-d: #c4ac86;
--accent: var(--sage);
--accent-2: var(--clay);
--r-sm: 8px;
--r-md: 14px;
--r-lg: 22px;
--sh-1: 0 1px 2px rgba(43, 38, 34, 0.1);
--sh-2: 0 10px 30px rgba(43, 38, 34, 0.1);
--serif: "Fraunces", Georgia, serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
font-family: var(--sans);
color: var(--ink);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: var(--cream);
/* textured paper background */
background-image:
radial-gradient(circle at 18% 12%, rgba(124, 138, 107, 0.07), transparent 30%),
radial-gradient(circle at 84% 22%, rgba(200, 119, 90, 0.07), transparent 32%),
repeating-linear-gradient(
45deg,
rgba(43, 38, 34, 0.012) 0 2px,
transparent 2px 6px
);
}
h1,
h2,
h3,
h4 {
font-family: var(--serif);
font-weight: 600;
line-height: 1.12;
margin: 0;
letter-spacing: -0.01em;
}
p {
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}
img {
max-width: 100%;
}
:focus-visible {
outline: 3px solid var(--clay);
outline-offset: 3px;
border-radius: 4px;
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.skip-link {
position: absolute;
left: -999px;
top: 0;
z-index: 200;
background: var(--ink);
color: var(--paper);
padding: 10px 16px;
border-radius: 0 0 var(--r-sm) 0;
}
.skip-link:focus {
left: 0;
}
.overline {
font-family: var(--serif);
font-style: italic;
font-size: 1.05rem;
color: var(--clay);
letter-spacing: 0.01em;
margin-bottom: 0.5rem;
}
/* ---------- buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-family: var(--sans);
font-weight: 600;
font-size: 0.95rem;
padding: 0.72rem 1.3rem;
border-radius: 999px;
background: var(--sage);
color: #fff;
border: 1px solid transparent;
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease;
box-shadow: var(--sh-1);
}
.btn:hover {
background: #6c7a5c;
transform: translateY(-2px);
box-shadow: var(--sh-2);
}
.btn:active {
transform: translateY(0);
}
.btn-lg {
padding: 0.9rem 1.7rem;
font-size: 1.02rem;
}
.btn-sm {
padding: 0.5rem 1rem;
font-size: 0.88rem;
}
.btn-ghost {
background: transparent;
color: var(--ink);
border: 1px solid var(--line-2);
box-shadow: none;
}
.btn-ghost:hover {
background: rgba(124, 138, 107, 0.1);
color: var(--ink);
}
/* ---------- header / nav ---------- */
.site-header {
position: sticky;
top: 0;
z-index: 100;
backdrop-filter: blur(8px);
background: rgba(255, 253, 248, 0.82);
border-bottom: 1px solid var(--line);
}
.nav {
max-width: 1140px;
margin: 0 auto;
padding: 0.85rem 1.4rem;
display: flex;
align-items: center;
gap: 1.4rem;
}
.brand {
display: inline-flex;
align-items: center;
gap: 0.55rem;
font-family: var(--serif);
font-weight: 600;
font-size: 1.18rem;
}
.brand-mark {
font-size: 1.4rem;
}
.nav-links {
list-style: none;
display: flex;
gap: 1.4rem;
margin: 0 auto 0 1rem;
padding: 0;
}
.nav-links a {
font-weight: 500;
color: var(--ink-2);
padding-bottom: 2px;
border-bottom: 2px solid transparent;
transition: color 0.2s, border-color 0.2s;
}
.nav-links a:hover {
color: var(--ink);
border-color: var(--clay);
}
/* ---------- layout helpers ---------- */
.section {
max-width: 1140px;
margin: 0 auto;
padding: 4.5rem 1.4rem;
}
.section-head {
max-width: 640px;
margin-bottom: 2.4rem;
}
.section-head h2 {
font-size: clamp(1.9rem, 4vw, 2.7rem);
margin-top: 0.2rem;
}
.section-sub {
color: var(--ink-2);
margin-top: 0.7rem;
font-size: 1.05rem;
}
/* ---------- hero ---------- */
.hero {
max-width: 1140px;
margin: 0 auto;
padding: 4rem 1.4rem 3rem;
display: grid;
grid-template-columns: 1.05fr 1fr;
gap: 3rem;
align-items: center;
}
.hero h1 {
font-size: clamp(2.4rem, 5.4vw, 4rem);
margin: 0.3rem 0 0;
}
.hero h1 em {
color: var(--clay);
font-style: italic;
}
.lede {
margin-top: 1.2rem;
font-size: 1.15rem;
color: var(--ink-2);
max-width: 30rem;
}
.hero-actions {
display: flex;
gap: 0.9rem;
margin-top: 1.8rem;
flex-wrap: wrap;
}
.hero-stats {
list-style: none;
display: flex;
gap: 2rem;
padding: 0;
margin: 2.4rem 0 0;
flex-wrap: wrap;
}
.hero-stats li {
display: flex;
flex-direction: column;
}
.hero-stats strong {
font-family: var(--serif);
font-size: 1.7rem;
color: var(--ink);
}
.hero-stats span {
font-size: 0.85rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.06em;
}
/* photo frames as "food photography" */
.hero-photo {
position: relative;
}
.photo-frame {
position: relative;
border-radius: var(--r-lg);
border: 1px solid var(--line);
box-shadow: var(--sh-2);
overflow: hidden;
min-height: 360px;
padding: 1rem;
/* kraft tape corners */
}
.photo-frame::after {
content: "";
position: absolute;
inset: 10px;
border: 2px dashed rgba(255, 253, 248, 0.5);
border-radius: calc(var(--r-lg) - 8px);
pointer-events: none;
}
.photo-table {
background:
radial-gradient(circle at 30% 28%, rgba(214, 69, 43, 0.55), transparent 26%),
radial-gradient(circle at 68% 40%, rgba(232, 163, 61, 0.5), transparent 30%),
radial-gradient(circle at 50% 78%, rgba(124, 138, 107, 0.55), transparent 36%),
radial-gradient(circle at 80% 80%, rgba(200, 119, 90, 0.5), transparent 28%),
linear-gradient(135deg, #e4c79f, #cda978);
}
.photo-kitchen {
min-height: 420px;
background:
radial-gradient(circle at 26% 32%, rgba(124, 138, 107, 0.6), transparent 30%),
radial-gradient(circle at 74% 30%, rgba(200, 119, 90, 0.55), transparent 30%),
radial-gradient(circle at 50% 82%, rgba(232, 163, 61, 0.45), transparent 38%),
linear-gradient(160deg, #d9c7a8, #b89b73);
}
.photo-tag {
position: absolute;
left: 1rem;
bottom: 1rem;
font-family: var(--serif);
font-style: italic;
font-size: 0.92rem;
color: var(--ink);
background: rgba(255, 253, 248, 0.85);
padding: 0.3rem 0.8rem;
border-radius: 999px;
box-shadow: var(--sh-1);
}
.float-emoji {
position: absolute;
font-size: 2.6rem;
filter: drop-shadow(0 6px 10px rgba(43, 38, 34, 0.25));
animation: bob 5s ease-in-out infinite;
}
.float-emoji.e1 {
top: 12%;
left: 14%;
}
.float-emoji.e2 {
top: 22%;
right: 16%;
animation-delay: 0.8s;
}
.float-emoji.e3 {
bottom: 20%;
left: 26%;
animation-delay: 1.6s;
}
@keyframes bob {
0%, 100% {
transform: translateY(0) rotate(-3deg);
}
50% {
transform: translateY(-10px) rotate(3deg);
}
}
/* recipe of the day card */
.rotd {
position: absolute;
right: -10px;
bottom: -28px;
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 1rem 1.2rem;
box-shadow: var(--sh-2);
max-width: 240px;
}
.rotd-kicker {
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--clay);
font-weight: 600;
}
.rotd-title {
font-size: 1.2rem;
margin: 0.3rem 0;
}
.rotd-meta {
font-size: 0.85rem;
color: var(--muted);
}
.rotd-dots {
display: flex;
gap: 6px;
margin-top: 0.7rem;
}
.rotd-dots .dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--line-2);
transition: background 0.3s, transform 0.3s;
}
.rotd-dots .dot.active {
background: var(--sage);
transform: scale(1.3);
}
/* ---------- recipe cards ---------- */
.recipe-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1.4rem;
}
.recipe-card {
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r-md);
overflow: hidden;
box-shadow: var(--sh-1);
transition: transform 0.2s ease, box-shadow 0.25s ease;
display: flex;
flex-direction: column;
}
.recipe-card:hover {
transform: translateY(-5px);
box-shadow: var(--sh-2);
}
.card-photo {
height: 150px;
display: grid;
place-items: center;
position: relative;
}
.card-photo span {
font-size: 3rem;
filter: drop-shadow(0 6px 8px rgba(43, 38, 34, 0.2));
}
.ph-tomato {
background:
radial-gradient(circle at 35% 30%, rgba(214, 69, 43, 0.6), transparent 45%),
linear-gradient(135deg, #e8b894, #d68a5e);
}
.ph-sage {
background:
radial-gradient(circle at 65% 35%, rgba(124, 138, 107, 0.7), transparent 50%),
linear-gradient(135deg, #c7cdb0, #9aa583);
}
.ph-clay {
background:
radial-gradient(circle at 40% 40%, rgba(200, 119, 90, 0.65), transparent 50%),
linear-gradient(135deg, #e3c19f, #c89a6e);
}
.ph-saffron {
background:
radial-gradient(circle at 50% 30%, rgba(232, 163, 61, 0.7), transparent 50%),
linear-gradient(135deg, #efd6a4, #d9b06a);
}
.card-body {
padding: 1.1rem 1.2rem 1.4rem;
}
.card-tag {
display: inline-block;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 600;
color: var(--sage);
margin-bottom: 0.4rem;
}
.card-body h3 {
font-size: 1.22rem;
margin-bottom: 0.3rem;
}
.card-meta {
font-size: 0.82rem;
color: var(--muted);
margin-bottom: 0.6rem;
}
.card-desc {
font-size: 0.92rem;
color: var(--ink-2);
}
/* ---------- story strip ---------- */
.story {
display: grid;
grid-template-columns: 1fr 1.1fr;
gap: 3rem;
align-items: center;
}
.story-copy h2 {
font-size: clamp(1.7rem, 3.6vw, 2.4rem);
margin: 0.3rem 0 1rem;
}
.story-copy p {
color: var(--ink-2);
margin-bottom: 0.9rem;
}
.dropcap::first-letter {
font-family: var(--serif);
font-size: 3.4rem;
font-weight: 700;
float: left;
line-height: 0.8;
padding: 0.1rem 0.6rem 0 0;
color: var(--clay);
}
.signature {
font-family: var(--serif);
font-style: italic;
font-size: 1.15rem;
color: var(--ink);
}
/* ---------- seasons ---------- */
.season-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1.4rem;
}
.season-card {
border-radius: var(--r-md);
padding: 1.6rem 1.3rem;
border: 1px solid var(--line);
box-shadow: var(--sh-1);
transition: transform 0.2s ease, box-shadow 0.25s ease;
position: relative;
overflow: hidden;
}
.season-card:hover {
transform: translateY(-4px);
box-shadow: var(--sh-2);
}
.season-card h3 {
font-size: 1.4rem;
margin: 0.6rem 0 0.2rem;
}
.season-card p {
font-size: 0.9rem;
color: var(--ink-2);
}
.season-emoji {
font-size: 2rem;
}
.season-count {
display: inline-block;
margin-top: 0.9rem;
font-size: 0.78rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--clay);
}
.s-spring {
background: linear-gradient(160deg, #e8efdc, #d4e0c0);
}
.s-summer {
background: linear-gradient(160deg, #f6ddc7, #efc6a4);
}
.s-autumn {
background: linear-gradient(160deg, #f0d9bb, #dcb98a);
}
.s-winter {
background: linear-gradient(160deg, #e3ddd2, #cdc3b2);
}
/* ---------- newsletter ---------- */
.newsletter {
text-align: center;
}
.news-inner {
max-width: 620px;
margin: 0 auto;
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 3rem 2rem;
box-shadow: var(--sh-2);
position: relative;
/* dashed hand-drawn-ish border accent */
}
.news-inner::before {
content: "";
position: absolute;
inset: 12px;
border: 2px dashed var(--line);
border-radius: calc(var(--r-lg) - 8px);
pointer-events: none;
}
.news-deco {
font-size: 1.8rem;
margin-bottom: 0.4rem;
}
.news-inner h2 {
font-size: clamp(1.6rem, 3.4vw, 2.3rem);
margin: 0.2rem 0;
}
.news-form {
margin-top: 1.6rem;
}
.field {
display: flex;
gap: 0.6rem;
justify-content: center;
flex-wrap: wrap;
}
.field input {
flex: 1 1 240px;
max-width: 320px;
padding: 0.85rem 1.1rem;
border-radius: 999px;
border: 1px solid var(--line-2);
background: var(--cream);
font: inherit;
color: var(--ink);
}
.field input::placeholder {
color: var(--muted);
}
.field input.invalid {
border-color: var(--danger);
background: rgba(200, 65, 43, 0.06);
}
.field-hint {
margin-top: 0.8rem;
font-size: 0.85rem;
color: var(--muted);
min-height: 1.2em;
}
.field-hint.error {
color: var(--danger);
}
.field-hint.ok {
color: var(--ok);
}
/* ---------- footer ---------- */
.site-footer {
background: var(--ink);
color: #e9e2d7;
margin-top: 2rem;
}
.footer-inner {
max-width: 1140px;
margin: 0 auto;
padding: 3.4rem 1.4rem 2rem;
display: grid;
grid-template-columns: 1.4fr 2fr;
gap: 2.4rem;
}
.footer-brand .brand-name {
font-family: var(--serif);
font-size: 1.3rem;
}
.footer-brand p {
margin-top: 0.8rem;
color: #b9b0a3;
font-size: 0.92rem;
max-width: 22rem;
}
.footer-cols {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.4rem;
}
.footer-cols h4 {
font-family: var(--sans);
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--saffron);
margin-bottom: 0.8rem;
}
.footer-cols a {
display: block;
color: #cfc7ba;
padding: 0.25rem 0;
font-size: 0.92rem;
transition: color 0.2s;
}
.footer-cols a:hover {
color: #fff;
}
.footer-base {
border-top: 1px solid rgba(255, 255, 255, 0.12);
text-align: center;
padding: 1.4rem;
font-size: 0.85rem;
color: #9a9082;
}
/* ---------- toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 140%);
background: var(--ink);
color: var(--paper);
padding: 0.85rem 1.4rem;
border-radius: 999px;
font-size: 0.95rem;
font-weight: 500;
box-shadow: var(--sh-2);
z-index: 300;
opacity: 0;
transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.3, 1), opacity 0.35s;
pointer-events: none;
}
.toast.show {
transform: translate(-50%, 0);
opacity: 1;
}
/* ---------- reveal on scroll ---------- */
.reveal {
opacity: 0;
transform: translateY(22px);
transition: opacity 0.7s ease, transform 0.7s ease;
}
.reveal.in {
opacity: 1;
transform: none;
}
/* ---------- responsive ---------- */
@media (max-width: 980px) {
.recipe-row {
grid-template-columns: repeat(2, 1fr);
}
.season-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 720px) {
.nav-links {
display: none;
}
.hero {
grid-template-columns: 1fr;
padding-bottom: 4rem;
}
.story {
grid-template-columns: 1fr;
}
.rotd {
position: static;
margin-top: 1.4rem;
max-width: none;
}
.footer-inner {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) {
.recipe-row,
.season-grid {
grid-template-columns: 1fr;
}
.footer-cols {
grid-template-columns: 1fr 1fr;
}
}
@media (prefers-reduced-motion: reduce) {
.reveal {
opacity: 1;
transform: none;
transition: none;
}
.float-emoji {
animation: none;
}
html {
scroll-behavior: auto;
}
}// The Copper Kettle — farmhouse cookbook landing
// Vanilla JS: reveal-on-scroll, recipe-of-the-day rotator, newsletter validation + toast.
(function () {
"use strict";
/* ---------- toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer = null;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 3200);
}
/* ---------- reveal on scroll ---------- */
var revealEls = Array.prototype.slice.call(document.querySelectorAll(".reveal"));
var prefersReduced =
window.matchMedia &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (prefersReduced || !("IntersectionObserver" in window)) {
revealEls.forEach(function (el) {
el.classList.add("in");
});
} else {
var io = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add("in");
io.unobserve(entry.target);
}
});
},
{ threshold: 0.12, rootMargin: "0px 0px -8% 0px" }
);
revealEls.forEach(function (el) {
io.observe(el);
});
}
/* ---------- recipe of the day rotator ---------- */
var recipes = [
{ title: "Rosemary Hearth Bread", meta: "45 min · serves 6 · easy" },
{ title: "Sunday Tomato Galette", meta: "55 min · serves 4 · medium" },
{ title: "White Bean & Sage Pot", meta: "1 hr 10 min · serves 6 · easy" },
{ title: "Brown-Butter Pear Crostata", meta: "50 min · serves 8 · medium" },
{ title: "Cider-Braised Lamb Shanks", meta: "3 hr 20 min · serves 4 · medium" },
{ title: "Lemon-Roasted Carrots", meta: "35 min · serves 4 · easy" }
];
var rotdTitle = document.querySelector('[data-rotd="title"]');
var rotdMeta = document.querySelector('[data-rotd="meta"]');
var rotdDots = document.querySelector("[data-rotd-dots]");
var rotdIndex = 0;
var rotdTimer = null;
function buildDots() {
if (!rotdDots) return;
rotdDots.innerHTML = "";
recipes.forEach(function (_, i) {
var dot = document.createElement("span");
dot.className = "dot" + (i === rotdIndex ? " active" : "");
rotdDots.appendChild(dot);
});
}
function renderRotd() {
var r = recipes[rotdIndex];
if (rotdTitle) rotdTitle.textContent = r.title;
if (rotdMeta) rotdMeta.textContent = r.meta;
if (rotdDots) {
var dots = rotdDots.querySelectorAll(".dot");
for (var i = 0; i < dots.length; i++) {
dots[i].classList.toggle("active", i === rotdIndex);
}
}
}
function nextRotd() {
rotdIndex = (rotdIndex + 1) % recipes.length;
renderRotd();
}
if (rotdTitle) {
// start on a pseudo-random recipe so it feels like "today's" pick
rotdIndex = new Date().getDate() % recipes.length;
buildDots();
renderRotd();
if (!prefersReduced) {
rotdTimer = setInterval(nextRotd, 4500);
}
// tapping the card advances and pauses auto-rotation briefly
var rotdCard = rotdTitle.closest(".rotd");
if (rotdCard) {
rotdCard.style.cursor = "pointer";
rotdCard.setAttribute("tabindex", "0");
rotdCard.setAttribute("role", "button");
rotdCard.setAttribute("aria-label", "Recipe of the day — tap for another");
var advance = function () {
clearInterval(rotdTimer);
nextRotd();
if (!prefersReduced) rotdTimer = setInterval(nextRotd, 4500);
};
rotdCard.addEventListener("click", advance);
rotdCard.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
advance();
}
});
}
}
/* ---------- newsletter form ---------- */
var form = document.getElementById("newsForm");
var emailInput = document.getElementById("email");
var hint = document.getElementById("emailHint");
var emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function setHint(text, state) {
if (!hint) return;
hint.textContent = text;
hint.classList.remove("error", "ok");
if (state) hint.classList.add(state);
}
if (form && emailInput) {
emailInput.addEventListener("input", function () {
if (emailInput.classList.contains("invalid") && emailRe.test(emailInput.value.trim())) {
emailInput.classList.remove("invalid");
emailInput.setAttribute("aria-invalid", "false");
setHint("Looks good — hit send.", null);
}
});
form.addEventListener("submit", function (e) {
e.preventDefault();
var value = emailInput.value.trim();
if (!value) {
emailInput.classList.add("invalid");
emailInput.setAttribute("aria-invalid", "true");
setHint("Pop your email in first, please.", "error");
emailInput.focus();
return;
}
if (!emailRe.test(value)) {
emailInput.classList.add("invalid");
emailInput.setAttribute("aria-invalid", "true");
setHint("Hmm, that email doesn't look quite right.", "error");
emailInput.focus();
return;
}
emailInput.classList.remove("invalid");
emailInput.setAttribute("aria-invalid", "false");
setHint("You're on the list — check your inbox on Sunday.", "ok");
toast("Welcome to the table 🌿 A recipe is on its way.");
form.reset();
});
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>The Copper Kettle — Farmhouse Cookbook</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,500;0,9..144,600;0,9..144,700;1,9..144,500&family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="site-header" role="banner">
<nav class="nav" aria-label="Primary">
<a class="brand" href="#top">
<span class="brand-mark" aria-hidden="true">🥖</span>
<span class="brand-name">The Copper Kettle</span>
</a>
<ul class="nav-links">
<li><a href="#recipes">Recipes</a></li>
<li><a href="#story">Our Kitchen</a></li>
<li><a href="#seasons">Seasons</a></li>
<li><a href="#newsletter">Letters</a></li>
</ul>
<a class="btn btn-sm" href="#newsletter">Get the cookbook</a>
</nav>
</header>
<main id="main">
<!-- HERO -->
<section class="hero" id="top" aria-labelledby="hero-title">
<div class="hero-copy reveal">
<p class="overline">From a little farmhouse kitchen</p>
<h1 id="hero-title">
Slow food, <em>warm hands,</em><br />and a table that always has room.
</h1>
<p class="lede">
A cookbook of unhurried, seasonal suppers — sourdough crusts, jammy
tomatoes, herbs picked at the back door. Recipes meant to be cooked
twice and loved forever.
</p>
<div class="hero-actions">
<a class="btn btn-lg" href="#newsletter">Get the cookbook</a>
<a class="btn btn-ghost btn-lg" href="#recipes">Browse recipes</a>
</div>
<ul class="hero-stats" aria-label="Highlights">
<li><strong>120+</strong><span>tested recipes</span></li>
<li><strong>4</strong><span>seasons of menus</span></li>
<li><strong>20 yrs</strong><span>round the table</span></li>
</ul>
</div>
<div class="hero-photo reveal" aria-hidden="true">
<div class="photo-frame photo-table">
<span class="float-emoji e1">🍅</span>
<span class="float-emoji e2">🌿</span>
<span class="float-emoji e3">🥖</span>
<span class="photo-tag">harvest table, Sunday noon</span>
</div>
<!-- Recipe of the day card -->
<article class="rotd" aria-live="polite" aria-label="Recipe of the day">
<p class="rotd-kicker">Recipe of the day</p>
<h3 class="rotd-title" data-rotd="title">Rosemary Hearth Bread</h3>
<p class="rotd-meta" data-rotd="meta">45 min · serves 6 · easy</p>
<div class="rotd-dots" data-rotd-dots aria-hidden="true"></div>
</article>
</div>
</section>
<!-- FEATURED RECIPES -->
<section class="section" id="recipes" aria-labelledby="recipes-title">
<div class="section-head reveal">
<p class="overline">Pulled from the pantry</p>
<h2 id="recipes-title">Featured recipes</h2>
<p class="section-sub">The ones we keep coming back to, splattered pages and all.</p>
</div>
<div class="recipe-row">
<article class="recipe-card reveal">
<div class="card-photo ph-tomato" aria-hidden="true"><span>🍅</span></div>
<div class="card-body">
<p class="card-tag">Mains</p>
<h3>Sunday Tomato Galette</h3>
<p class="card-meta">55 min · serves 4</p>
<p class="card-desc">A flaky rye crust folded around slow-roasted heirloom tomatoes and thyme.</p>
</div>
</article>
<article class="recipe-card reveal">
<div class="card-photo ph-sage" aria-hidden="true"><span>🥣</span></div>
<div class="card-body">
<p class="card-tag">Soups</p>
<h3>White Bean & Sage Pot</h3>
<p class="card-meta">1 hr 10 min · serves 6</p>
<p class="card-desc">Creamy cannellini simmered low with garlic, sage and a parmesan rind.</p>
</div>
</article>
<article class="recipe-card reveal">
<div class="card-photo ph-clay" aria-hidden="true"><span>🥧</span></div>
<div class="card-body">
<p class="card-tag">Bakes</p>
<h3>Brown-Butter Pear Crostata</h3>
<p class="card-meta">50 min · serves 8</p>
<p class="card-desc">Honeyed pears, a nubbly oat crumble and a whisper of cardamom.</p>
</div>
</article>
<article class="recipe-card reveal">
<div class="card-photo ph-saffron" aria-hidden="true"><span>🍋</span></div>
<div class="card-body">
<p class="card-tag">Sides</p>
<h3>Lemon-Roasted Carrots</h3>
<p class="card-meta">35 min · serves 4</p>
<p class="card-desc">Garden carrots blistered with honey, lemon and a fistful of dill.</p>
</div>
</article>
</div>
</section>
<!-- STORY STRIP -->
<section class="section story" id="story" aria-labelledby="story-title">
<div class="story-photo reveal" aria-hidden="true">
<div class="photo-frame photo-kitchen">
<span class="float-emoji e1">🧄</span>
<span class="float-emoji e2">🥕</span>
<span class="photo-tag">the old enamel stove</span>
</div>
</div>
<div class="story-copy reveal">
<p class="overline">From our kitchen</p>
<h2 id="story-title">We cook the way we were taught — slowly, and with the windows open.</h2>
<p class="dropcap">
Three generations have stirred pots at this same scrubbed table. The
recipes here aren't fussy; they're the kind you learn by smell and
repetition — a pinch more salt, another splash of the good oil, a loaf
left to rise overnight while the house sleeps.
</p>
<p>
Every dish in the book has been cooked on a weeknight, fed to neighbours,
and earned its splatter. We hope they earn a few of yours, too.
</p>
<p class="signature">— Mara & the Copper Kettle table</p>
</div>
</section>
<!-- SEASONS -->
<section class="section" id="seasons" aria-labelledby="seasons-title">
<div class="section-head reveal">
<p class="overline">Cook with the calendar</p>
<h2 id="seasons-title">Seasonal categories</h2>
<p class="section-sub">What's good right now, sorted by the light outside.</p>
</div>
<div class="season-grid">
<a class="season-card s-spring reveal" href="#newsletter">
<span class="season-emoji" aria-hidden="true">🌱</span>
<h3>Spring</h3>
<p>Peas, radishes & first herbs</p>
<span class="season-count">24 recipes</span>
</a>
<a class="season-card s-summer reveal" href="#newsletter">
<span class="season-emoji" aria-hidden="true">🍅</span>
<h3>Summer</h3>
<p>Tomatoes, stone fruit & grills</p>
<span class="season-count">38 recipes</span>
</a>
<a class="season-card s-autumn reveal" href="#newsletter">
<span class="season-emoji" aria-hidden="true">🍂</span>
<h3>Autumn</h3>
<p>Squash, apples & slow roasts</p>
<span class="season-count">31 recipes</span>
</a>
<a class="season-card s-winter reveal" href="#newsletter">
<span class="season-emoji" aria-hidden="true">🔥</span>
<h3>Winter</h3>
<p>Braises, broths & warm bread</p>
<span class="season-count">27 recipes</span>
</a>
</div>
</section>
<!-- NEWSLETTER -->
<section class="section newsletter" id="newsletter" aria-labelledby="news-title">
<div class="news-inner reveal">
<div class="news-deco" aria-hidden="true">✉️ 🌿</div>
<p class="overline">Letters from the kitchen</p>
<h2 id="news-title">A new recipe in your inbox, once a week.</h2>
<p class="section-sub">No spam — just one tested recipe, a seasonal note, and the occasional jam mishap.</p>
<form class="news-form" id="newsForm" novalidate>
<div class="field">
<label class="visually-hidden" for="email">Email address</label>
<input
type="email"
id="email"
name="email"
placeholder="[email protected]"
autocomplete="email"
aria-describedby="emailHint"
required
/>
<button class="btn btn-lg" type="submit">Send me recipes</button>
</div>
<p class="field-hint" id="emailHint" role="status" aria-live="polite">We'll never share your address.</p>
</form>
</div>
</section>
</main>
<footer class="site-footer" role="contentinfo">
<div class="footer-inner">
<div class="footer-brand">
<span class="brand-mark" aria-hidden="true">🥖</span>
<span class="brand-name">The Copper Kettle</span>
<p>Slow food from a little farmhouse kitchen, written down at last.</p>
</div>
<nav class="footer-cols" aria-label="Footer">
<div>
<h4>The Book</h4>
<a href="#recipes">Recipes</a>
<a href="#seasons">Seasonal menus</a>
<a href="#story">Our story</a>
</div>
<div>
<h4>Pantry</h4>
<a href="#newsletter">Newsletter</a>
<a href="#top">Pre-order</a>
<a href="#top">Gift a copy</a>
</div>
<div>
<h4>Say hello</h4>
<a href="#top">Instagram</a>
<a href="#top">Email us</a>
<a href="#top">Visit the farm</a>
</div>
</nav>
</div>
<p class="footer-base">© 2026 The Copper Kettle. Made with butter & patience.</p>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Rustic / Farmhouse Cookbook landing
A warm, handmade landing page for a fictional farmhouse cookbook, “The Copper Kettle.” The hero pairs a handwritten-style overline, a serif headline, and a “Get the cookbook” call to action with a gradient “harvest table” photo frame, floating food emoji, and a small recipe-of-the-day card. Below it sit a featured-recipes row, a “from our kitchen” story strip with a drop cap and signature, four seasonal category cards, a newsletter sign-up, and a homely footer.
The mood is cream, sage, and clay: textured paper background, dashed hand-drawn-ish frames, kraft tones, and an editorial Fraunces / Inter type pairing. All food imagery is pure CSS — layered radial gradients standing in for food photography plus tasteful emoji accents — with no images or external libraries.
Interactions are vanilla JavaScript: an IntersectionObserver reveals sections on scroll (and
respects prefers-reduced-motion), the recipe-of-the-day card rotates through dishes on a timer
and on tap/keyboard, and the newsletter form validates the email inline with an aria-live hint
and a confirmation toast. Layouts collapse to a single column by ~720px and the design meets
WCAG AA contrast with visible focus states.
Illustrative UI only — recipes & nutrition data are fictional, not dietary advice.