Archived
Catering, and a pure Java/Spring site: Thymeleaf front to back #10
@@ -3,27 +3,11 @@
|
||||
<body>
|
||||
<div th:fragment="content">
|
||||
|
||||
<!--/*
|
||||
A short photographic band rather than the plain cream header the other pages use. This is the one page
|
||||
that is selling something, and the same treatment as the homepage hero — blurred photo, sage wash —
|
||||
carries the brand rather than inventing a new look for it.
|
||||
*/-->
|
||||
<section class="band relative flex items-center bg-bakery-900 text-white overflow-hidden">
|
||||
<img th:src="${photos.of('gallery/Cakes.webp')}" alt=""
|
||||
class="absolute inset-0 w-full h-full object-cover blur-[3px] scale-110">
|
||||
<div class="absolute inset-0 bg-bakery-900/80"></div>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-bakery-900 via-bakery-800/60 to-bakery-900/80"></div>
|
||||
|
||||
<div class="relative container w-full min-w-0">
|
||||
<div class="measure text-center">
|
||||
<p class="eyebrow text-bakery-200">Order ahead</p>
|
||||
<h1 class="h1 mt-3 text-bakery-50">Goodie boxes & catering</h1>
|
||||
<p class="lede mt-5 text-bakery-100">
|
||||
Boxes for the office, packages for a party, and cakes and desserts for a wedding.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div th:replace="~{fragments/opening :: photographed(
|
||||
'Order ahead',
|
||||
'Goodie boxes & catering',
|
||||
'Boxes for the office, packages for a party, and cakes and desserts for a wedding.',
|
||||
'gallery/Cakes.webp')}"></div>
|
||||
|
||||
<div class="bg-bakery-50">
|
||||
<div class="container pt-14 pb-16 md:pt-16 md:pb-24">
|
||||
@@ -33,11 +17,7 @@
|
||||
mind, and we will bake to it.
|
||||
</p>
|
||||
|
||||
<!--/* The branch from the wordmark, as a divider. It is a mask over currentColor, so it takes the
|
||||
colour of the text around it — see .mark in tokens.css. */-->
|
||||
<div class="mt-10 flex justify-center" aria-hidden="true">
|
||||
<span class="mark mark-r w-12 h-12 text-bakery-400"></span>
|
||||
</div>
|
||||
<div class="mt-10" th:replace="~{fragments/opening :: divider}"></div>
|
||||
|
||||
<div class="mt-12 max-w-6xl mx-auto space-y-16 md:space-y-20">
|
||||
|
||||
@@ -67,7 +47,7 @@
|
||||
: (${#lists.size(table.tiers) == 2} ? 'sm:grid-cols-2 max-w-3xl mx-auto'
|
||||
: 'sm:grid-cols-2 lg:grid-cols-3')">
|
||||
<article th:each="tier, t : ${table.tiers}"
|
||||
class="relative flex flex-col overflow-hidden panel-lift">
|
||||
class="relative flex flex-col overflow-hidden panel">
|
||||
<!--/* The wordmark's branch, at watermark weight. */-->
|
||||
<span class="panel-mark -top-3 -right-4 w-24 h-24 rotate-12" aria-hidden="true"></span>
|
||||
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
<body>
|
||||
<div th:fragment="content" class="min-h-screen bg-bakery-50">
|
||||
|
||||
<header class="page-head">
|
||||
<h1 class="h1 text-bakery-900">Contact us</h1>
|
||||
<p class="mt-4 text-bakery-800 max-w-xl mx-auto leading-relaxed">
|
||||
Ask about a cake, a date, or an order for a crowd. We read everything that comes in.
|
||||
</p>
|
||||
</header>
|
||||
<div th:replace="~{fragments/opening :: plain(
|
||||
'Say hello',
|
||||
'Contact us',
|
||||
'Ask about a cake, a date, or an order for a crowd. We read everything that comes in.')}"></div>
|
||||
|
||||
<!--/*
|
||||
The form, and beside it the things somebody on this page is usually actually after: when we are open,
|
||||
|
||||
@@ -21,14 +21,19 @@
|
||||
<div class="flex items-center justify-between gap-2 h-20 md:h-24">
|
||||
<div th:replace="~{fragments/lockup :: small('text-bakery-700')}"></div>
|
||||
|
||||
<div class="hidden md:flex items-center gap-8">
|
||||
<nav class="flex items-center gap-8">
|
||||
<!--/*
|
||||
The status line is the first thing to go when the bar gets tight: at the width of a small laptop
|
||||
the wordmark and the nav were overlapping, because "Closed · opens Tuesday at 7:00am" is as wide as
|
||||
two nav items. It comes back at lg, and the mobile menu carries it at every width below md.
|
||||
*/-->
|
||||
<div class="hidden md:flex items-center gap-6 xl:gap-8">
|
||||
<nav class="flex items-center gap-6 xl:gap-8">
|
||||
<th:block th:replace="~{:: links('label text-bakery-700 hover:text-bakery-900 transition-colors')}"></th:block>
|
||||
</nav>
|
||||
<!--/* Worked out on the server from the same opening times the footer prints. */-->
|
||||
<p th:replace="~{fragments/visit :: open-now(
|
||||
${openNow.open} ? 'bg-bakery-500' : 'bg-bakery-300',
|
||||
'text-sm text-bakery-600 whitespace-nowrap')}"></p>
|
||||
'hidden xl:inline-flex text-sm text-bakery-600 whitespace-nowrap')}"></p>
|
||||
</div>
|
||||
|
||||
<details class="group md:hidden shrink-0">
|
||||
@@ -47,7 +52,7 @@
|
||||
<th:block th:replace="~{:: links('text-bakery-800 hover:text-bakery-600 transition-colors py-4 text-lg border-b border-bakery-100')}"></th:block>
|
||||
<p th:replace="~{fragments/visit :: open-now(
|
||||
${openNow.open} ? 'bg-bakery-500' : 'bg-bakery-300',
|
||||
'text-sm text-bakery-600 py-4')}"></p>
|
||||
'inline-flex text-sm text-bakery-600 py-4')}"></p>
|
||||
</nav>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<a th:fragment="small(tint)" href="/"
|
||||
th:class="'flex items-center gap-1.5 sm:gap-2 min-w-0 shrink ' + ${tint}"
|
||||
aria-label="The Vine Coffeehouse + Bakery, home">
|
||||
<th:block th:replace="~{:: inner('w-12 h-12 sm:w-14 sm:h-14', 'text-xl sm:text-2xl md:text-3xl', 'text-[0.6rem] sm:text-xs tracking-[0.18em]')}"></th:block>
|
||||
<th:block th:replace="~{:: inner('w-12 h-12 sm:w-14 sm:h-14', 'text-xl sm:text-2xl xl:text-3xl', 'text-[0.6rem] sm:text-xs tracking-[0.18em]')}"></th:block>
|
||||
</a>
|
||||
|
||||
<!--/* The hero, on the homepage — where a link back to "/" would be pointless. */-->
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<body>
|
||||
<!--/*
|
||||
How a page begins. Every page, the same three things in the same order: a small-caps line, the title in
|
||||
the display face, and one sentence saying what this is.
|
||||
|
||||
Two dresses for it. `photographed` is for the pages that are selling or telling — catering and the story
|
||||
— where a blurred photo under the sage wash does the work the homepage hero does. `plain` is for the
|
||||
pages you arrive at already knowing what you want, where a photograph would be in the way of the thing
|
||||
you came for: the catalogue, the contact form.
|
||||
|
||||
Before this the four openings were four different pieces of markup that happened to look similar.
|
||||
*/-->
|
||||
|
||||
<section th:fragment="photographed(eyebrow, title, lede, image)"
|
||||
class="band relative flex items-center bg-bakery-900 text-white overflow-hidden">
|
||||
<img th:src="${photos.of(image)}" alt=""
|
||||
class="absolute inset-0 w-full h-full object-cover blur-[3px] scale-110">
|
||||
<!--/* Sage wash rather than a neutral black scrim — the tint is the identity. */-->
|
||||
<div class="absolute inset-0 bg-bakery-900/80"></div>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-bakery-900 via-bakery-800/60 to-bakery-900/80"></div>
|
||||
|
||||
<div class="relative container w-full min-w-0">
|
||||
<div class="measure text-center">
|
||||
<p class="eyebrow text-bakery-200" th:text="${eyebrow}">Order ahead</p>
|
||||
<h1 class="h1 mt-3 text-bakery-50" th:text="${title}">Goodie boxes & catering</h1>
|
||||
<p class="lede mt-5 text-bakery-100" th:text="${lede}">One line about the page.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<header th:fragment="plain(eyebrow, title, lede)" class="page-head">
|
||||
<p class="eyebrow text-bakery-500" th:text="${eyebrow}">From the counter</p>
|
||||
<h1 class="h1 mt-3 text-bakery-900" th:text="${title}">Our products</h1>
|
||||
<p class="lede mt-4 measure text-bakery-800" th:text="${lede}">One line about the page.</p>
|
||||
</header>
|
||||
|
||||
<!--/* The branch from the wordmark, between one part of a page and the next. */-->
|
||||
<div th:fragment="divider" class="flex justify-center" aria-hidden="true">
|
||||
<span class="mark mark-r w-12 h-12 text-bakery-400"></span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -32,8 +32,11 @@
|
||||
</address>
|
||||
|
||||
<!--/* "Open until 2:00pm", or when it opens next. The one thing a visitor at 2:30 on a Sunday wants. */-->
|
||||
<!--/* The caller passes the display too (`inline-flex`, or `hidden lg:inline-flex`): setting it here as
|
||||
well would be two rules for one property, and the winner would depend on Tailwind's output order
|
||||
rather than on the markup. */-->
|
||||
<span th:fragment="open-now(dotClass, textClass)"
|
||||
th:class="'inline-flex items-center gap-2 ' + ${textClass}">
|
||||
th:class="'items-center gap-2 ' + ${textClass}">
|
||||
<span th:class="'h-2 w-2 rounded-full shrink-0 ' + ${dotClass}" aria-hidden="true"></span>
|
||||
<span th:text="${openNow.summary}">Open until 2:00pm</span>
|
||||
</span>
|
||||
|
||||
@@ -3,24 +3,11 @@
|
||||
<body>
|
||||
<div th:fragment="content" class="bg-bakery-50">
|
||||
|
||||
<!--/* The shop's own front, which is what the story is about. Same band as the catering page. */-->
|
||||
<section class="band relative flex items-center bg-bakery-900 text-white overflow-hidden">
|
||||
<img th:src="${photos.of('gallery/Outside.webp')}" alt=""
|
||||
class="absolute inset-0 w-full h-full object-cover object-bottom blur-[3px] scale-110">
|
||||
<div class="absolute inset-0 bg-bakery-900/80"></div>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-bakery-900 via-bakery-800/60 to-bakery-900/80"></div>
|
||||
<div class="relative container w-full min-w-0">
|
||||
<div class="measure text-center">
|
||||
<p class="eyebrow text-bakery-200">Since 2024</p>
|
||||
<h1 class="h1 mt-3 text-bakery-50">Our story</h1>
|
||||
<p class="lede mt-5 text-bakery-100">
|
||||
215 E Main Street, in the middle of downtown Princeville.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<header class="container pt-14 pb-2 md:pt-16 text-center"></header>
|
||||
<div th:replace="~{fragments/opening :: photographed(
|
||||
'Since 2024',
|
||||
'Our story',
|
||||
'215 E Main Street, in the middle of downtown Princeville.',
|
||||
'gallery/Outside.webp')}"></div>
|
||||
|
||||
<div class="container pb-16 md:pb-24">
|
||||
<div class="measure space-y-12">
|
||||
|
||||
@@ -48,18 +48,42 @@
|
||||
<h2 class="h2 text-center text-bakery-900 mb-12">What people come in for</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 md:gap-8">
|
||||
<div th:each="item : ${ {'Cinnamon Rolls', 'Sugar Cookies', 'Cakes'} }"
|
||||
class="group panel-lift overflow-hidden text-center hover:-translate-y-1">
|
||||
<div class="overflow-hidden">
|
||||
class="panel overflow-hidden text-center">
|
||||
<!--/* The filenames carry the spaces in these names; SitePhotos encodes them. */-->
|
||||
<img th:src="${photos.of('gallery/' + item + '.webp')}" th:alt="${item}" width="600" height="400" loading="lazy"
|
||||
class="w-full h-56 object-cover transition-transform duration-500 group-hover:scale-105">
|
||||
</div>
|
||||
class="w-full h-56 object-cover">
|
||||
<h3 class="panel-head border-b-0 border-t h4 md:text-2xl text-bakery-800 py-6" th:text="${item}">Cinnamon Rolls</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!--/*
|
||||
Ordering ahead. The catering page exists and the homepage said nothing about it — a customer only
|
||||
found it by reading the nav. Photo on one side, the offer on the other, and the three tables named so
|
||||
the link is worth following.
|
||||
*/-->
|
||||
<section class="section bg-bakery-100">
|
||||
<div class="container">
|
||||
<div class="grid md:grid-cols-2 gap-8 md:gap-12 items-center measure-wide">
|
||||
<img th:src="${photos.of('gallery/Sugar Cookies.webp')}" alt="Iced sugar cookies from The Vine"
|
||||
width="800" height="600" loading="lazy"
|
||||
class="photo w-full h-64 md:h-80 object-cover">
|
||||
<div>
|
||||
<h2 class="h2 text-bakery-900">Ordering for a crowd?</h2>
|
||||
<p class="mt-4 text-bakery-800 leading-relaxed">
|
||||
Goodie boxes for the office, packages for a party, and cakes and desserts for a wedding —
|
||||
with prices, sizes and what each one includes, so you can see where to start.
|
||||
</p>
|
||||
<a href="/catering"
|
||||
class="pill-sage mt-6">
|
||||
Goodie boxes & catering
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!--/* Our story */-->
|
||||
<section class="section bg-bakery-800 text-white">
|
||||
<div class="container">
|
||||
@@ -76,32 +100,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!--/*
|
||||
Ordering ahead. The catering page exists and the homepage said nothing about it — a customer only
|
||||
found it by reading the nav. Photo on one side, the offer on the other, and the three tables named so
|
||||
the link is worth following.
|
||||
*/-->
|
||||
<section class="section bg-bakery-50">
|
||||
<div class="container">
|
||||
<div class="grid md:grid-cols-2 gap-8 md:gap-12 items-center measure-wide">
|
||||
<img th:src="${photos.of('gallery/Sugar Cookies.webp')}" alt="Iced sugar cookies from The Vine"
|
||||
width="800" height="600" loading="lazy"
|
||||
class="photo w-full h-64 md:h-80 object-cover">
|
||||
<div>
|
||||
<h2 class="h2 text-bakery-900">Ordering for a crowd?</h2>
|
||||
<p class="mt-4 text-bakery-800 leading-relaxed">
|
||||
Goodie boxes for the office, packages for a party, and cakes and desserts for a wedding —
|
||||
with prices, sizes and what each one includes, so you can see where to start.
|
||||
</p>
|
||||
<a href="/catering"
|
||||
class="pill-sage mt-6">
|
||||
Goodie boxes & catering
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!--/* Visit us */-->
|
||||
<section id="visit" class="section bg-bakery-50 scroll-mt-24">
|
||||
<div class="container">
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
<body>
|
||||
<div th:fragment="content" class="min-h-screen bg-bakery-50">
|
||||
|
||||
<header class="page-head">
|
||||
<h1 class="h1 text-bakery-900">Our products</h1>
|
||||
<p class="mt-4 text-bakery-800 measure leading-relaxed">
|
||||
What comes out of the kitchen on Main Street. Cakes and decorated cookies are made to order, so
|
||||
most of what follows started as somebody describing what they wanted.
|
||||
</p>
|
||||
</header>
|
||||
<div th:replace="~{fragments/opening :: plain(
|
||||
'From the counter',
|
||||
'Our products',
|
||||
'What comes out of the kitchen on Main Street. Cakes and decorated cookies are made to order, so most of what follows started as somebody describing what they wanted.')}"></div>
|
||||
|
||||
<div class="container pb-16">
|
||||
<!--/*
|
||||
@@ -31,7 +28,7 @@
|
||||
responding. The only motion left is the shadow on hover. */-->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<div th:each="product : ${products}"
|
||||
class="group panel-lift overflow-hidden">
|
||||
class="panel overflow-hidden">
|
||||
<div class="relative w-full overflow-hidden">
|
||||
<div th:replace="~{:: gallery(${product})}"></div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user