Archived
The shop knows when it is open, and says so — to visitors and to Google
Three things a bakery's site should do that this one couldn't, all from the same fact. THE HOURS WERE MARKUP, so the only thing the site could do with them was print them. A visitor at half past two on a Sunday had to work out for themselves that the shop was shut. They are data now (Shop.WEEK), and `Hours` reads them two ways: as the list the footer and the contact page print — consecutive days with the same times collapsed into one line, the way a sign does it, instead of three hand-written rows — and as an answer. The header now says "Open until 2:00pm", or "Closed · opens Tuesday at 7:00am", worked out on the server in the shop's own timezone. Right now, on a Sunday evening, it says the latter. THE SHOP IS NOW DESCRIBED TO A SEARCH ENGINE: schema.org Bakery in the head, with the address, the phone number and those same opening times. For a shop whose customers find it by searching its town, that is the difference between a blue link and a listing that shows "Open ⋅ closes 2pm" — and it cannot drift from what the page says, because it is built from the same record. Serialised with Jackson rather than string-built, so the day somebody interpolates a name into it, it is not an injection. (Jackson 3 — `tools.jackson` — which is what Boot 4 ships; the fasterxml import does not compile.) A SHARED LINK NOW HAS A PICTURE. og:image, og:image:alt and a large summary card: it was a grey box with a title, which is what every link to this site has looked like in a message. AND IT PRINTS. The catering page is a price list and a bakery prints price lists — Ctrl-P now gives the prices on paper rather than a screenshot of a website: no chrome, no sage bands eating a cartridge, cards that don't split across a page break, and the buttons that only exist to be clicked marked `no-print`. Five new tests, all of them free of Spring, because this is a calculation over a constant: the week collapses as a sign would write it, the minute of opening counts as open and the minute of closing does not, and a Saturday afternoon is told when Tuesday starts. Also swept the last one-off styles the earlier pass missed — max-w-3xl/4xl, an inline letter-spacing the .h2 class already carries, and the mobile menu's link string. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
<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="max-w-3xl mx-auto text-center">
|
||||
<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">
|
||||
@@ -117,7 +117,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
<a th:href="|/contact?about=${#uris.escapeQueryParam(table.name)}|"
|
||||
class="pill-outline mt-6"
|
||||
class="pill-outline mt-6 no-print"
|
||||
th:text="|Ask about ${#strings.toLowerCase(table.name)}|">Ask about office boxes</a>
|
||||
</div>
|
||||
</section>
|
||||
@@ -134,7 +134,7 @@
|
||||
|
||||
<!--/* The terms that apply whichever table you were reading, and the one real call to action. On sage,
|
||||
like the homepage's story band, so the page ends rather than trailing off. */-->
|
||||
<section class="section bg-bakery-800 text-white">
|
||||
<section class="section bg-bakery-800 text-white no-print">
|
||||
<div class="container">
|
||||
<div class="measure text-center">
|
||||
<h2 class="h3">Before you order</h2>
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
<meta property="og:title" th:content="${title}">
|
||||
<meta property="og:description" th:content="${description}">
|
||||
<meta property="og:url" th:content="${baseUrl + (path == '/' ? '' : path)}">
|
||||
<!--/* Without an image a shared link is a grey box with a title. This is the storefront. */-->
|
||||
<meta property="og:image" th:content="${shareImage}">
|
||||
<meta property="og:image:alt" content="The Vine Coffeehouse + Bakery on Main Street, Princeville">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:locale" content="en_US">
|
||||
<meta name="keywords" content="bakery, coffeehouse, pastries, custom cakes, cinnamon rolls, paninis, Princeville IL">
|
||||
@@ -34,6 +38,11 @@
|
||||
<!--/* ?v= is the commit sha in a deployment. One hand-written stylesheet has no content hash in its
|
||||
name, so without it a returning visitor keeps the CSS they cached before the deploy. */-->
|
||||
<link rel="stylesheet" th:href="|/css/site.css?v=${build}|">
|
||||
|
||||
<!--/* The shop as a search engine reads it: a Bakery, its address, its phone number and its opening
|
||||
times, from the same record the page prints. It is how a local shop gets "Open ⋅ closes 2pm"
|
||||
next to its listing. Unescaped because it is JSON, written by Jackson — see StructuredData. */-->
|
||||
<script type="application/ld+json" th:utext="${structuredData}"></script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
|
||||
@@ -21,9 +21,15 @@
|
||||
<div class="flex items-center justify-between gap-2 h-20 md:h-24">
|
||||
<div th:replace="~{fragments/lockup :: small('text-bakery-700')}"></div>
|
||||
|
||||
<nav class="hidden md:flex items-center gap-8">
|
||||
<th:block th:replace="~{:: links('label text-bakery-700 hover:text-bakery-900 transition-colors')}"></th:block>
|
||||
</nav>
|
||||
<div class="hidden md:flex items-center gap-8">
|
||||
<nav class="flex items-center 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>
|
||||
</div>
|
||||
|
||||
<details class="group md:hidden shrink-0">
|
||||
<summary class="p-2 cursor-pointer list-none" aria-label="Menu">
|
||||
@@ -38,7 +44,10 @@
|
||||
</summary>
|
||||
<div class="absolute inset-x-0 top-full z-30 h-[calc(100dvh-5rem)] bg-bakery-50">
|
||||
<nav class="container flex flex-col">
|
||||
<th:block th:replace="~{:: links('text-bakery-800 hover:text-bakery-600 transition py-4 text-lg border-b border-bakery-100')}"></th:block>
|
||||
<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>
|
||||
</nav>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -2,40 +2,40 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<body>
|
||||
<!--/*
|
||||
The opening hours and the address, in one place.
|
||||
The opening times and the address, printed from the shop's own record rather than from markup.
|
||||
|
||||
They were on the homepage and in the footer already, and the moment the contact page wanted them too
|
||||
that became three copies of the one fact a customer most often comes here for. A shop that changes its
|
||||
Saturday hours should change them once.
|
||||
They were three hand-written lines repeated in three templates. Now `Hours` collapses the week into the
|
||||
lines a sign would show — consecutive days that keep the same times become one row — and the same data
|
||||
answers "are they open right now?" in the header and describes the shop to a search engine. A shop that
|
||||
changes its Saturday hours changes them in Shop.java, once.
|
||||
|
||||
Both fragments take the classes that vary by where they sit — cream text in the footer, sage on white
|
||||
in a card — so the content is shared without the styling being averaged into something that suits
|
||||
neither.
|
||||
Both fragments take the classes that vary by where they sit — cream in the footer, sage on white in a
|
||||
card — so the content is shared without the styling being averaged into something that suits neither.
|
||||
*/-->
|
||||
|
||||
<ul th:fragment="hours(rowClass)" class="space-y-3">
|
||||
<li th:class="'flex justify-between gap-4 ' + ${rowClass}">
|
||||
<span>Tuesday – Friday</span>
|
||||
<span class="font-medium whitespace-nowrap">7:00am – 2:00pm</span>
|
||||
</li>
|
||||
<li th:class="'flex justify-between gap-4 ' + ${rowClass}">
|
||||
<span>Saturday</span>
|
||||
<span class="font-medium whitespace-nowrap">7:00am – 12:00pm</span>
|
||||
</li>
|
||||
<li th:class="'flex justify-between gap-4 ' + ${rowClass}">
|
||||
<span>Sunday – Monday</span>
|
||||
<span class="font-medium">Closed</span>
|
||||
<li th:each="span : ${hours}" th:class="'flex justify-between gap-4 ' + ${rowClass}">
|
||||
<span th:text="${span.days}">Tuesday – Friday</span>
|
||||
<span class="font-medium whitespace-nowrap" th:text="${span.hours}">7:00am – 2:00pm</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<address th:fragment="address(linkClass)" class="not-italic space-y-3">
|
||||
<p>215 E Main Street<br>Princeville, IL 61559</p>
|
||||
<p th:text="${shopStreet}">215 E Main Street</p>
|
||||
<p th:text="${shopTown}">Princeville, IL 61559</p>
|
||||
<p>
|
||||
<a href="tel:+13097010660" th:class="${linkClass}">(309) 701-0660</a>
|
||||
<a th:href="|tel:${shopPhone}|" th:class="${linkClass}" th:text="${shopPhoneSpoken}">(309) 701-0660</a>
|
||||
</p>
|
||||
<p class="break-words">
|
||||
<a href="mailto:[email protected]" th:class="${linkClass}">[email protected]</a>
|
||||
<a th:href="|mailto:${shopEmail}|" th:class="${linkClass}" th:text="${shopEmail}">[email protected]</a>
|
||||
</p>
|
||||
</address>
|
||||
|
||||
<!--/* "Open until 2:00pm", or when it opens next. The one thing a visitor at 2:30 on a Sunday wants. */-->
|
||||
<span th:fragment="open-now(dotClass, textClass)"
|
||||
th:class="'inline-flex 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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<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="max-w-3xl mx-auto text-center">
|
||||
<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">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<!--/* w-full/min-w-0 keep this flex item from sizing to its max-content width and blowing out the
|
||||
page on narrow screens. */-->
|
||||
<div class="relative container w-full min-w-0">
|
||||
<div class="max-w-3xl mx-auto text-center">
|
||||
<div class="measure text-center">
|
||||
<div th:replace="~{fragments/lockup :: large('text-bakery-50 mb-10')}"></div>
|
||||
<p class="lede sm:text-xl text-bakery-100 mb-10">
|
||||
A coffeehouse and bakery in downtown Princeville, Illinois.
|
||||
@@ -56,9 +56,9 @@
|
||||
<!--/* Our story */-->
|
||||
<section class="section bg-bakery-800 text-white">
|
||||
<div class="container">
|
||||
<div class="max-w-3xl mx-auto text-center">
|
||||
<h2 class="h2 mb-8" style="letter-spacing: 0.01em">Our story</h2>
|
||||
<p class="text-base md:text-lg text-bakery-100 mb-8 leading-relaxed">
|
||||
<div class="measure text-center">
|
||||
<h2 class="h2 mb-8">Our story</h2>
|
||||
<p class="lede text-bakery-100 mb-8">
|
||||
Morissa Bennett opened The Vine in 2024. We bake in our own kitchen on Main Street:
|
||||
cinnamon rolls, cookies, custom cakes, sandwiches, paninis, and coffee.
|
||||
</p>
|
||||
@@ -99,7 +99,7 @@
|
||||
<section id="visit" class="section bg-bakery-50 scroll-mt-24">
|
||||
<div class="container">
|
||||
<h2 class="h2 text-center text-bakery-900 mb-12">Visit us</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-12 max-w-4xl mx-auto">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-12 measure-wide">
|
||||
<div class="panel p-6 md:p-8">
|
||||
<h2 class="h3 text-bakery-900 mb-6">Our hours</h2>
|
||||
<div th:replace="~{fragments/visit :: hours('text-bakery-800')}"></div>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</div>
|
||||
|
||||
<!--/* A catalogue with no way to order from it is a gallery. */-->
|
||||
<section class="section bg-bakery-800 text-white">
|
||||
<section class="section bg-bakery-800 text-white no-print">
|
||||
<div class="container">
|
||||
<div class="measure text-center">
|
||||
<h2 class="h3">Want one of these, or something else?</h2>
|
||||
|
||||
Reference in New Issue
Block a user