Free
0
4
Crumbelle
By Luca Moretti
Crumbelle is a cheerful, checkerboard bakery template for pastry shops and dessert delivery, with a tilted cake hero, product boxes, an ingredients spotlight, a build-your-box configurator, a delivery checker and an FAQ.
Download Codebase
Commercial License<%# letstemplates: access=free category=food-restaurant %>
<!DOCTYPE html>
<html lang="en">
<head>
<script>
window.FontAwesomeConfig = { autoReplaceSvg: 'nest' };
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Crumbelle | Tailwind CSS Food & Restaurant Template</title>
<meta name="description" content="Crumbelle is a cheerful, checkerboard Tailwind CSS bakery template for pastry shops and dessert delivery, with a tilted cake hero, product boxes, an ingredients spotlight, a build-your-box configurator, a delivery checker and an FAQ.">
<meta name="keywords" content="Crumbelle, Tailwind CSS template, HTML website template, bakery template, pastry shop website, dessert delivery, cake shop, food and restaurant template, online bakery">
<meta name="author" content="Let's templates">
<link rel="canonical" href="">
<meta property="og:type" content="product">
<meta property="og:site_name" content="Let's templates">
<meta property="og:locale" content="en_US">
<meta property="og:url" content="">
<meta property="og:title" content="Crumbelle | Tailwind CSS Food & Restaurant Template">
<meta property="og:description" content="A cheerful, checkerboard Tailwind CSS bakery template with product boxes, a build-your-box configurator and a delivery checker.">
<meta property="og:image" content="">
<meta property="og:image:alt" content="Crumbelle template preview">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:url" content="">
<meta name="twitter:title" content="Crumbelle | Tailwind CSS Food & Restaurant Template">
<meta name="twitter:description" content="A cheerful, checkerboard Tailwind CSS bakery template with product boxes, a build-your-box configurator and a delivery checker.">
<meta name="twitter:image" content="">
<meta name="twitter:image:alt" content="Crumbelle template preview">
<script src="https://cdn.tailwindcss.com/3.4.17"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Chewy&family=Poppins:wght@400;500;600&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: { blush: '#fdecec', rose: { DEFAULT: '#f7a6a8', deep: '#ef7f86' }, mint: '#b2dcc7', pistachio: '#e1e8c4', peach: '#fcd2b4', ink: '#141414' },
fontFamily: { sans: ['Poppins', 'sans-serif'], display: ['Chewy', 'cursive'] }
}
}
}
</script>
<style>
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: 180ms ease-out both page-fade-out; }
::view-transition-new(root) { animation: 220ms ease-in both page-fade-in; }
@keyframes page-fade-out { to { opacity: 0; } }
@keyframes page-fade-in { from { opacity: 0; } }
html { scroll-behavior: smooth; }
[hidden] { display: none !important; }
.checker { background-color: #e1e8c4; background-image: conic-gradient(#d3e6c6 25%, transparent 0 50%, #d3e6c6 0 75%, transparent 0); background-size: 3.5rem 3.5rem; }
.checker-mint { background-color: #c6e4d2; background-image: conic-gradient(#e6efcf 25%, transparent 0 50%, #e6efcf 0 75%, transparent 0); background-size: 3.5rem 3.5rem; }
.btn-pop { box-shadow: 0 3px 0 #141414; transition: transform .15s, box-shadow .15s; }
.btn-pop:hover { transform: translateY(-2px); box-shadow: 0 5px 0 #141414; }
.btn-pop:active { transform: translateY(2px); box-shadow: 0 1px 0 #141414; }
.wave-top { clip-path: polygon(0 18%, 8% 12%, 20% 6%, 35% 4%, 50% 7%, 62% 11%, 72% 9%, 84% 4%, 93% 6%, 100% 10%, 100% 100%, 0 100%); }
.wiggle:hover { animation: wiggle .5s ease-in-out; }
@keyframes wiggle { 25% { transform: rotate(-3deg); } 75% { transform: rotate(3deg); } }
.reveal-ready [data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity .6s ease, transform .6s ease; transition-delay: var(--reveal-delay, 0ms); }
.reveal-ready [data-reveal].is-visible { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
.wiggle:hover { animation: none; }
::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}
</style>
</head>
<body class="bg-blush font-sans text-ink antialiased">
<% active_page = "index" %>
<% main_nav = { "about" => "About us", "pastries" => "Pastries", "contact" => "Contact" } %>
<% more_nav = { "delivery" => "Delivery", "order" => "Build a box" } %>
<!-- block:start navbar -->
<header class="relative z-40">
<nav class="mx-auto flex max-w-6xl items-center justify-between gap-6 px-5 py-5 md:px-8" aria-label="Main">
<a href="index.html" class="font-display text-3xl leading-none">Crumbelle</a>
<div class="hidden items-center gap-7 text-sm lg:flex">
<ul class="flex items-center gap-7">
<% main_nav.each do |slug, label| %>
<% active = active_page == slug %>
<li><a href="<%= slug %>.html" <%= 'aria-current="page"'.html_safe if active %> class="<%= active ? 'font-semibold underline decoration-rose-deep decoration-2 underline-offset-8' : 'hover:text-rose-deep' %>"><%= label %></a></li>
<% end %>
<li class="relative" data-dropdown>
<button type="button" data-dropdown-toggle aria-expanded="false" aria-controls="more-menu" class="flex items-center gap-1.5 <%= 'font-semibold' if more_nav.key?(active_page) %> hover:text-rose-deep">More <i class="fa-solid fa-chevron-down text-[10px]" aria-hidden="true"></i></button>
<ul id="more-menu" class="absolute right-0 top-full mt-3 hidden w-44 rounded-xl border border-ink bg-white p-2 shadow-[0_4px_0_#141414]">
<% more_nav.each do |slug, label| %>
<li><a href="<%= slug %>.html" <%= 'aria-current="page"'.html_safe if active_page == slug %> class="block rounded-lg px-3 py-2 hover:bg-blush <%= 'bg-blush font-semibold' if active_page == slug %>"><%= label %></a></li>
<% end %>
</ul>
</li>
</ul>
<a href="login.html" <%= 'aria-current="page"'.html_safe if active_page == "login" %> class="btn-pop rounded-md border border-ink bg-white px-4 py-1.5">Login</a>
<a href="order.html" class="btn-pop rounded-md border border-ink bg-rose px-4 py-1.5">Order</a>
</div>
<button type="button" data-menu-toggle aria-controls="mobile-menu" aria-expanded="false" aria-label="Open menu" class="btn-pop grid h-11 w-11 place-items-center rounded-md border border-ink bg-white lg:hidden"><i class="fa-solid fa-bars" aria-hidden="true"></i></button>
</nav>
<div id="mobile-menu" class="hidden px-5 pb-6 lg:hidden">
<ul class="grid gap-1 rounded-2xl border border-ink bg-white p-3 text-lg shadow-[0_4px_0_#141414]">
<% { "index" => "Home" }.merge(main_nav).merge(more_nav).merge("login" => "Login").each do |slug, label| %>
<% active = active_page == slug %>
<li><a href="<%= slug %>.html" <%= 'aria-current="page"'.html_safe if active %> class="block rounded-lg px-4 py-2.5 <%= active ? 'bg-rose font-semibold' : 'hover:bg-blush' %>"><%= label %></a></li>
<% end %>
</ul>
</div>
</header>
<!-- block:end navbar -->
<!-- block:start hero -->
<section class="relative overflow-hidden">
<div class="checker absolute inset-y-0 right-0 hidden w-[44%] [clip-path:polygon(22%_0,100%_0,100%_100%,0_100%)] lg:block" aria-hidden="true"></div>
<div class="relative mx-auto grid max-w-6xl items-center gap-12 px-5 pb-20 pt-8 md:px-8 lg:min-h-[36rem] lg:grid-cols-[1.1fr_1fr] lg:pb-24">
<div>
<h1 class="font-display text-6xl leading-[0.95] md:text-7xl lg:text-[5.5rem]">Warm <span class="text-rose-deep">pastries</span>,<br><span class="text-rose-deep">quick</span> delivery</h1>
<p class="mt-6 max-w-md text-[15px] leading-relaxed">Crumbelle bakes the best-loved classics with real ingredients you can trust. Order now and taste what proper baking feels like.</p>
<div class="mt-8 flex gap-3">
<a href="order.html" class="btn-pop rounded-md border border-ink bg-rose px-6 py-2.5 font-medium">Order</a>
<a href="about.html" class="btn-pop rounded-md border border-ink bg-white px-6 py-2.5 font-medium">Learn</a>
</div>
</div>
<div class="relative">
<div class="checker absolute -inset-4 rounded-3xl lg:hidden" aria-hidden="true"></div>
<img src="https://images.unsplash.com/photo-1578985545062-69928b1d9587?auto=format&fit=crop&w=1100&q=80" alt="Layered chocolate cake with glossy drip and piped cream rosettes" class="wiggle relative aspect-[5/4] w-full rotate-3 rounded-[2rem] border border-ink object-cover shadow-[0_6px_0_#141414] lg:ml-6">
<span class="absolute -bottom-5 left-2 -rotate-6 rounded-full border border-ink bg-white px-4 py-2 font-display text-xl shadow-[0_3px_0_#141414] md:left-0">Baked at 5am today</span>
</div>
</div>
</section>
<!-- block:end hero -->
<!-- block:start about -->
<section class="wave-top relative bg-mint pb-24 pt-28 md:pt-36">
<div class="mx-auto max-w-6xl px-5 md:px-8">
<div class="mx-auto max-w-2xl text-center" data-reveal>
<h2 class="font-display text-5xl md:text-6xl">Baked <span class="text-white">properly</span>, every time</h2>
<p class="mt-5 leading-relaxed">We start with real butter, flour, eggs and time. No shortcuts, no premixes, just the patient craft of real baking. Every pastry tastes like the ones you remember from a grandparent's kitchen.</p>
</div>
<div class="mt-14 grid gap-6 md:grid-cols-2">
<div class="grid gap-6">
<img src="https://images.unsplash.com/photo-1586985289688-ca3cf47d3e6e?auto=format&fit=crop&w=1000&q=80" alt="Baker piping chocolate rosettes onto a layered cake" class="aspect-[4/3] w-full rounded-xl border border-ink object-cover" loading="lazy" data-reveal>
<img src="https://images.unsplash.com/photo-1556911073-a517e752729c?auto=format&fit=crop&w=1000&q=80" alt="Baker preparing fresh ingredients in a bright kitchen" class="aspect-[4/3] w-full rounded-xl border border-ink object-cover md:aspect-[4/5]" loading="lazy" data-reveal>
</div>
<div class="grid gap-6">
<img src="https://images.unsplash.com/photo-1486887396153-fa416526c108?auto=format&fit=crop&w=1000&q=80" alt="Hands holding a freshly baked country loaf" class="aspect-[4/3] w-full rounded-xl border border-ink object-cover md:aspect-[4/5]" loading="lazy" data-reveal style="--reveal-delay: 120ms">
<img src="https://images.unsplash.com/photo-1534432182912-63863115e106?auto=format&fit=crop&w=1000&q=80" alt="Trays of pastries and cakes laid out on the counter" class="aspect-[4/3] w-full rounded-xl border border-ink object-cover" loading="lazy" data-reveal style="--reveal-delay: 120ms">
</div>
</div>
</div>
</section>
<!-- block:end about -->
<% products = [
["Butter croissant", "Box of 6", "$18", "1555507036-ab1f4038808a", "Two golden croissants dusted with sugar"],
["Fudge brownie", "Box of 9", "$16", "1606313564200-e75d5e30476c", "Stack of fudgy chocolate brownies"],
["Cinnamon roll", "Box of 6", "$20", "1509365465985-25d11c17e812", "Cinnamon rolls dusted with sugar on a dark tray"],
["Lemon meringue tart", "Whole tart", "$24", "1519915028121-7d3463d20b13", "Lemon tart topped with toasted meringue"],
["Cherry lattice pie", "Two mini pies", "$15", "1464305795204-6f5bbfc7fb81", "Two cherry lattice pies with fresh berries"],
["Confetti cupcakes", "Box of 4", "$17", "1519869325930-281384150729", "Vanilla cupcakes with swirled frosting and sprinkles"]
] %>
<!-- block:start products -->
<section class="bg-blush">
<div class="mx-auto max-w-6xl px-5 py-20 md:px-8 md:py-24">
<div class="flex flex-wrap items-end justify-between gap-6">
<div class="max-w-xl">
<h2 class="font-display text-5xl md:text-6xl">Crowd <span class="text-rose-deep">favourites</span></h2>
<p class="mt-4 leading-relaxed">The bakes that keep our regulars coming back, made fresh each day and ready when you are.</p>
</div>
<a href="pastries.html" class="btn-pop rounded-md border border-ink bg-white px-5 py-2 text-sm font-medium">Browse</a>
</div>
<ul class="mt-12 grid gap-8 sm:grid-cols-2 lg:grid-cols-3">
<% products.each_with_index do |(name, size, price, photo, alt), index| %>
<li data-reveal style="--reveal-delay: <%= (index % 3) * 100 %>ms">
<a href="pastries.html" class="group block">
<span class="block overflow-hidden rounded-xl border border-ink"><img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=800&q=80" alt="<%= alt %>" class="aspect-square w-full object-cover transition duration-500 group-hover:scale-105" loading="lazy"></span>
<span class="mt-3 block rounded-xl border border-ink bg-rose px-4 py-3 transition group-hover:-translate-y-1">
<span class="block font-medium"><%= name %></span>
<span class="block text-xs"><%= size %></span>
<span class="mt-2 block text-xl font-semibold"><%= price %></span>
</span>
</a>
</li>
<% end %>
</ul>
</div>
</section>
<!-- block:end products -->
<!-- block:start process -->
<section class="bg-blush pb-24">
<div class="mx-auto max-w-6xl px-5 md:px-8">
<h2 class="text-center font-display text-4xl md:text-5xl">Oven to doorstep in three steps</h2>
<ol class="mt-12 grid border border-ink md:grid-cols-3">
<% [["fa-fire-burner", "Bake", "Every pastry leaves the oven golden and still warm.", "bg-rose"], ["fa-box-open", "Pack", "We box them gently so nothing arrives squashed.", "bg-mint"], ["fa-truck-fast", "Ship", "Your treats turn up quickly, fresh and ready to enjoy.", "bg-pistachio"]].each_with_index do |(icon, title, text, tone), index| %>
<li class="<%= tone %> px-6 py-8 text-center <%= 'border-t border-ink md:border-l md:border-t-0' unless index.zero? %>">
<span class="mx-auto grid h-16 w-16 place-items-center rounded-full bg-white text-xl" aria-hidden="true"><i class="fa-solid <%= icon %>"></i></span>
<h3 class="mt-4 font-display text-3xl"><%= title %></h3>
<p class="mx-auto mt-3 max-w-[16rem] text-sm leading-relaxed"><%= text %></p>
</li>
<% end %>
</ol>
</div>
</section>
<!-- block:end process -->
<% promises = [["fa-egg", "Butter, flour, eggs. That's the list", "Plus salt and patience. Nothing else goes in."], ["fa-rotate-left", "Fresh, or your money back", "If it isn't perfect when it lands, we refund you. No questions."], ["fa-truck", "Baked today, sent today", "Out of the oven at dawn and on its way by lunch."], ["fa-ban", "No mystery ingredients", "No preservatives, no fillers, just good baking done right."]] %>
<!-- block:start features -->
<section class="checker">
<div class="mx-auto max-w-6xl px-5 py-20 md:px-8 md:py-24">
<div class="mx-auto max-w-2xl text-center">
<h2 class="font-display text-5xl md:text-6xl">What goes in is what you taste</h2>
<p class="mt-5 leading-relaxed">We bake in the open. Every ingredient is one you'd keep in your own cupboard, and we're happy to tell you exactly what they are.</p>
</div>
<div class="mt-14 grid items-center gap-6 lg:grid-cols-[1fr_1.3fr_1fr]">
<% left, right = promises.each_slice(2).to_a %>
<% [left, nil, right].each do |group| %>
<% if group %>
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-1">
<% group.each do |icon, title, text| %>
<div class="rounded-2xl border border-ink bg-white/70 p-6 text-center backdrop-blur" data-reveal>
<span class="mx-auto grid h-14 w-14 place-items-center rounded-full border border-ink text-lg" aria-hidden="true"><i class="fa-solid <%= icon %>"></i></span>
<h3 class="mt-4 font-display text-2xl leading-tight"><%= title %></h3>
<p class="mt-2 text-sm leading-relaxed"><%= text %></p>
</div>
<% end %>
</div>
<% else %>
<div class="relative mx-auto w-full max-w-sm">
<img src="https://images.unsplash.com/photo-1621303837174-89787a7d4729?auto=format&fit=crop&w=900&q=80" alt="Pink drip cake topped with an ice cream cone on a white cake stand" class="aspect-square w-full rounded-full border border-ink object-cover shadow-[0_6px_0_#141414]" loading="lazy">
<span class="absolute -right-2 top-6 rotate-12 rounded-full border border-ink bg-rose px-4 py-2 font-display text-xl">100% real</span>
</div>
<% end %>
<% end %>
</div>
</div>
</section>
<!-- block:end features -->
<% quotes = [["They taste like they came from the corner bakery, not a delivery box.", "Sofia Lindqvist", "Regular customer", 2], ["Quick delivery, and the croissants were still warm when the box opened.", "Daniel Mensah", "Breakfast lover", 3], ["Finally a bakery I can trust. Short ingredient lists, brilliant baking.", "Aiko Tanaka", "Home baker", 4]] %>
<!-- block:start reviews -->
<section class="bg-blush">
<div class="mx-auto max-w-6xl px-5 py-20 md:px-8 md:py-24">
<h2 class="font-display text-5xl md:text-6xl">Kind <span class="text-rose-deep">words</span></h2>
<p class="mt-3">Real people, real breakfasts with Crumbelle.</p>
<div class="mt-12 grid gap-10 md:grid-cols-3">
<% quotes.each_with_index do |(quote, name, role, avatar), index| %>
<figure data-reveal style="--reveal-delay: <%= index * 120 %>ms">
<p class="text-mint" aria-label="Five stars"><% 5.times do %><i class="fa-solid fa-star" aria-hidden="true"></i><% end %></p>
<blockquote class="mt-4 font-display text-2xl leading-snug">“<%= quote %>”</blockquote>
<figcaption class="mt-5 flex items-center gap-3">
<img src="https://storage.googleapis.com/uxpilot-auth.appspot.com/avatars/avatar-<%= avatar %>.jpg" alt="Portrait of <%= name %>" class="h-12 w-12 rounded-full object-cover" loading="lazy">
<span><span class="block text-sm font-semibold"><%= name %></span><span class="text-sm"><%= role %></span></span>
</figcaption>
</figure>
<% end %>
</div>
</div>
</section>
<!-- block:end reviews -->
<section class="wave-top bg-white pb-20 pt-28 md:pb-24">
<div class="mx-auto max-w-6xl px-5 md:px-8">
<h2 class="font-display text-5xl md:text-6xl">Made for the <span class="text-rose-deep">way you live</span></h2>
<p class="mt-4 max-w-xl leading-relaxed">You're busy. So we ship quickly and pack cleverly, and fresh baking fits into your week without the fuss.</p>
<div class="mt-12 grid gap-6 md:grid-cols-3">
<% [["Two-day delivery almost everywhere", "Order in the morning, enjoy fresh pastries by the next afternoon.", "1526367790999-0150786686a2", "Courier cycling a delivery bag down a narrow street", "bg-pistachio", "delivery"], ["Packaging that's kind to the planet", "Our boxes are fully compostable, because good baking shouldn't cost the earth.", "1530610476181-d83430b64dcd", "Fresh croissants piled on a café counter", "bg-mint", "about"], ["Subscribe and save on every box", "A fresh box every week at a price that actually makes sense.", "1556910103-1c02745aae4d", "Couple cooking together in a home kitchen", "bg-peach", "order"]].each do |title, text, photo, alt, tone, href| %>
<a href="<%= href %>.html" class="group flex flex-col overflow-hidden rounded-2xl border border-ink <%= tone %> transition hover:-translate-y-1 hover:shadow-[0_6px_0_#141414]" data-reveal>
<img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=800&q=80" alt="<%= alt %>" class="aspect-[16/10] w-full object-cover" loading="lazy">
<span class="flex-1 p-7">
<span class="block font-display text-3xl leading-tight"><%= title %></span>
<span class="mt-4 block text-sm leading-relaxed"><%= text %></span>
</span>
</a>
<% end %>
</div>
</div>
</section>
<% faqs = [["How fresh is everything?", "Everything is baked the morning it ships. Nothing is made days ahead, so you get it at its best, usually within 24 hours of baking."], ["How long does delivery take?", "Most orders arrive in two to three working days in insulated packaging, and every order comes with tracking."], ["Do you bake gluten-free?", "Not yet, though we're testing recipes. For now everything contains wheat flour, and we're careful about cross-contamination."], ["What if my box arrives damaged?", "Tell us within 24 hours and we'll replace it or refund you. We pack with care, and we stand behind every box."], ["Can I pause or cancel a subscription?", "Any time, with no penalty. Pause or cancel from your account page whenever you like."]] %>
<!-- block:start faq -->
<section class="bg-white">
<div class="mx-auto max-w-3xl px-5 pb-20 md:px-8 md:pb-24">
<div class="text-center">
<h2 class="font-display text-6xl text-rose-deep">Questions</h2>
<p class="mt-3">Answers about freshness, delivery and what we bake.</p>
</div>
<div class="mt-10 border-t border-ink/15" data-accordion>
<% faqs.each_with_index do |(question, answer), index| %>
<div class="border-b border-ink/15">
<h3>
<button type="button" data-accordion-trigger aria-expanded="<%= index.zero? %>" aria-controls="faq-<%= index %>" class="flex w-full items-center justify-between gap-6 py-5 text-left font-semibold">
<%= question %>
<i class="fa-solid fa-chevron-down shrink-0 text-xs transition-transform" aria-hidden="true"></i>
</button>
</h3>
<div id="faq-<%= index %>" <%= 'hidden' unless index.zero? %> class="pb-5 leading-relaxed"><%= answer %></div>
</div>
<% end %>
</div>
<div class="mt-16 text-center">
<h3 class="font-display text-4xl">Still wondering about something?</h3>
<p class="mx-auto mt-3 max-w-sm">We're happy to talk about what we bake and how it reaches you.</p>
<a href="contact.html" class="btn-pop mt-6 inline-block rounded-md border border-ink bg-white px-6 py-2.5 font-medium">Contact</a>
</div>
</div>
</section>
<!-- block:end faq -->
<!-- block:start cta -->
<section class="grid md:grid-cols-2">
<div class="checker flex flex-col justify-center px-5 py-16 md:px-12 lg:px-[max(3rem,calc((100vw-72rem)/2+2rem))]">
<h2 class="font-display text-5xl leading-tight md:text-6xl">Hungry for something fresh?</h2>
<p class="mt-5 max-w-md text-lg">Order today and taste the difference real baking makes.</p>
<div class="mt-8 flex gap-3">
<a href="order.html" class="btn-pop rounded-md border border-ink bg-rose px-6 py-2.5 font-medium">Order</a>
<a href="pastries.html" class="btn-pop rounded-md border border-ink bg-white px-6 py-2.5 font-medium">Learn</a>
</div>
</div>
<img src="https://images.unsplash.com/photo-1495147466023-ac5c588e2e94?auto=format&fit=crop&w=1200&q=80" alt="Rows of small fruit tarts topped with berries and kiwi" class="aspect-[4/3] h-full w-full object-cover md:aspect-auto" loading="lazy">
</section>
<!-- block:end cta -->
<!-- block:start footer -->
<footer class="bg-mint">
<div class="mx-auto max-w-6xl px-5 py-16 md:px-8">
<div class="grid gap-10 rounded-2xl border border-ink bg-rose p-8 md:grid-cols-[1.4fr_1fr_1fr] md:p-12">
<div>
<a href="index.html" class="font-display text-4xl">Crumbelle</a>
<p class="mt-6 text-sm font-semibold">Location</p>
<p class="text-sm">14 Mill Lane, Bath BA1 2JX</p>
<p class="mt-4 text-sm font-semibold">Reach us</p>
<p class="text-sm"><a href="tel:+441225000418" class="underline">01225 000 418</a><br><a href="mailto:hello@crumbelle.co" class="underline">hello@crumbelle.co</a></p>
<ul class="mt-6 flex gap-4 text-lg">
<% [["fa-facebook", "Facebook", "https://www.facebook.com"], ["fa-instagram", "Instagram", "https://www.instagram.com"], ["fa-tiktok", "TikTok", "https://www.tiktok.com"], ["fa-youtube", "YouTube", "https://www.youtube.com"]].each do |icon, label, url| %>
<li><a href="<%= url %>" aria-label="<%= label %>" class="transition hover:text-white"><i class="fa-brands <%= icon %>" aria-hidden="true"></i></a></li>
<% end %>
</ul>
</div>
<ul class="grid content-start gap-3 text-sm font-semibold">
<% [["about", "Our story"], ["about", "The craft"], ["about", "Quality promise"], ["about", "Meet the team"], ["order", "Ordering"]].each do |slug, label| %><li><a href="<%= slug %>.html" class="hover:underline"><%= label %></a></li><% end %>
</ul>
<ul class="grid content-start gap-3 text-sm font-semibold">
<% [["pastries", "Menu"], ["delivery", "Delivery"], ["delivery", "Track an order"], ["contact", "Support"], ["login", "Your account"]].each do |slug, label| %><li><a href="<%= slug %>.html" class="hover:underline"><%= label %></a></li><% end %>
</ul>
</div>
<div class="mt-8 flex flex-wrap items-center justify-between gap-4 text-sm">
<p>© <span data-current-year>2026</span> Crumbelle Bakery</p>
<ul class="flex flex-wrap gap-6 underline">
<li><a href="contact.html">Terms of service</a></li>
<li><a href="contact.html">Cookie settings</a></li>
<li><a href="contact.html">Privacy policy</a></li>
</ul>
</div>
</div>
</footer>
<!-- block:end footer -->
<script>
document.querySelectorAll('[data-menu-toggle]').forEach((button) => {
const menu = document.getElementById(button.getAttribute('aria-controls'));
if (!menu) return;
button.addEventListener('click', () => {
const open = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!open));
menu.classList.toggle('hidden', open);
});
});
document.querySelectorAll('[data-dropdown]').forEach((dropdown) => {
const toggle = dropdown.querySelector('[data-dropdown-toggle]');
const menu = document.getElementById(toggle.getAttribute('aria-controls'));
const set = (open) => { toggle.setAttribute('aria-expanded', String(open)); menu.classList.toggle('hidden', !open); };
toggle.addEventListener('click', () => set(toggle.getAttribute('aria-expanded') !== 'true'));
document.addEventListener('click', (event) => { if (!dropdown.contains(event.target)) set(false); });
dropdown.addEventListener('keydown', (event) => { if (event.key === 'Escape') { set(false); toggle.focus(); } });
});
document.querySelectorAll('[data-accordion]').forEach((accordion) => {
accordion.querySelectorAll('[data-accordion-trigger]').forEach((trigger) => {
const panel = document.getElementById(trigger.getAttribute('aria-controls'));
const icon = () => trigger.querySelector('svg') || trigger.querySelector('i');
const sync = () => { const open = trigger.getAttribute('aria-expanded') === 'true'; panel.hidden = !open; icon()?.classList.toggle('rotate-180', open); };
trigger.addEventListener('click', () => { trigger.setAttribute('aria-expanded', String(trigger.getAttribute('aria-expanded') !== 'true')); sync(); });
sync();
});
});
document.querySelectorAll('[data-tabs]').forEach((tablist) => {
const tabs = [...tablist.querySelectorAll('[role="tab"]')];
const select = (tab) => tabs.forEach((other) => {
const active = other === tab;
other.setAttribute('aria-selected', String(active));
other.tabIndex = active ? 0 : -1;
document.getElementById(other.getAttribute('aria-controls')).hidden = !active;
});
tabs.forEach((tab, index) => {
tab.addEventListener('click', () => select(tab));
tab.addEventListener('keydown', (event) => {
const step = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[event.key];
if (!step) return;
event.preventDefault();
const next = tabs[(index + step + tabs.length) % tabs.length];
next.focus();
select(next);
});
});
});
document.querySelectorAll('[data-box-builder]').forEach((builder) => {
const size = Number(builder.dataset.boxSize);
const summary = builder.querySelector('[data-box-summary]');
const count = builder.querySelector('[data-box-count]');
const total = builder.querySelector('[data-box-total]');
const bar = builder.querySelector('[data-box-bar]');
const submit = builder.querySelector('[data-box-submit]');
const slots = [...builder.querySelectorAll('[data-box-slot]')];
const update = () => {
const items = [...builder.querySelectorAll('[data-box-item]')];
let pieces = 0;
let price = 0;
const fill = [];
items.forEach((item) => {
const qty = Number(item.querySelector('[data-qty]').textContent);
pieces += qty;
price += qty * Number(item.dataset.price);
for (let i = 0; i < qty; i += 1) fill.push(item.dataset.emoji);
item.querySelector('[data-step="-1"]').disabled = qty === 0;
});
items.forEach((item) => { item.querySelector('[data-step="1"]').disabled = pieces >= size; });
slots.forEach((slot, i) => { slot.textContent = fill[i] || ''; slot.classList.toggle('is-filled', Boolean(fill[i])); });
count.textContent = pieces;
total.textContent = price.toFixed(2);
bar.style.width = `${(pieces / size) * 100}%`;
summary.textContent = pieces === size ? 'Your box is full. Ready when you are!' : `Add ${size - pieces} more to fill your box.`;
submit.disabled = pieces !== size;
};
builder.addEventListener('click', (event) => {
const button = event.target.closest('[data-step]');
if (!button) return;
const qty = button.closest('[data-box-item]').querySelector('[data-qty]');
qty.textContent = Math.max(0, Number(qty.textContent) + Number(button.dataset.step));
update();
});
builder.addEventListener('submit', (event) => {
event.preventDefault();
builder.querySelector('[data-form-status]').textContent = 'Lovely! Your box is saved. Pick a delivery day at checkout.';
});
update();
});
document.querySelectorAll('[data-postcode-check]').forEach((form) => {
const status = form.querySelector('[data-form-status]');
form.addEventListener('submit', (event) => {
event.preventDefault();
const value = String(form.querySelector('[name="postcode"]').value || '').trim().toUpperCase();
if (!value) { status.textContent = 'Type your postcode first.'; return; }
const area = value.replace(/[^A-Z]/g, '').slice(0, 2);
const local = ['BA', 'BS', 'SN', 'GL'].includes(area);
status.textContent = local ? `Good news: we deliver to ${value} the next day, Tuesday to Saturday.` : `We deliver to ${value} in two to three working days.`;
});
});
document.querySelectorAll('[data-flip]').forEach((card) => {
card.addEventListener('click', () => card.setAttribute('aria-pressed', String(card.getAttribute('aria-pressed') !== 'true')));
});
document.querySelectorAll('[data-contact-form]').forEach((form) => {
form.addEventListener('submit', (event) => {
event.preventDefault();
const status = form.querySelector('[data-form-status]');
if (!form.checkValidity()) {
status.textContent = form.dataset.error;
form.querySelector(':invalid')?.focus();
return;
}
status.textContent = form.dataset.success;
form.reset();
});
});
document.querySelectorAll('[data-current-year]').forEach((el) => { el.textContent = new Date().getFullYear(); });
(() => {
const items = document.querySelectorAll('[data-reveal]');
const skip = navigator.webdriver || !('IntersectionObserver' in window) || matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!items.length || skip) return;
document.documentElement.classList.add('reveal-ready');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
});
}, { rootMargin: '0px 0px -10% 0px' });
items.forEach((item) => observer.observe(item));
})();
</script>
</body>
</html>
Unlimited access to all templates
Unlock our entire library of premium UI kits, dashboards, and landing pages. Renews automatically, cancel anytime.
$20
/ 3 months
or $60 / year
Get Unlimited Access