Archived
Catering, and a pure Java/Spring site: Thymeleaf front to back #10
@@ -55,13 +55,18 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!--/* One list of links, worn two ways — so the phone and the desktop can't end up offering different
|
||||
pages. */-->
|
||||
<!--/*
|
||||
One list of links, worn two ways — so the phone and the desktop can't end up offering different pages.
|
||||
|
||||
The page you are on is marked: aria-current for a screen reader, and a sage rule under the word for
|
||||
everyone else. A four-page site can get away without it, right up until someone clicks Catering, lands
|
||||
on a page of prices, and has no idea whether the click worked.
|
||||
*/-->
|
||||
<th:block th:fragment="links(itemClass)">
|
||||
<a href="/products" th:class="${itemClass}">Our Products</a>
|
||||
<a href="/catering" th:class="${itemClass}">Catering</a>
|
||||
<a href="/history" th:class="${itemClass}">Our Story</a>
|
||||
<a href="/contact" th:class="${itemClass}">Contact</a>
|
||||
<a th:each="item : ${ {{'/products','Our Products'},{'/catering','Catering'},{'/history','Our Story'},{'/contact','Contact'}} }"
|
||||
th:href="${item[0]}" th:text="${item[1]}"
|
||||
th:aria-current="${path == item[0] ? 'page' : null}"
|
||||
th:class="${itemClass} + (${path == item[0]} ? ' is-here' : '')">Our Products</a>
|
||||
</th:block>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
<head th:replace="~{fragments/head :: head(${title}, ${description}, ${path})}"></head>
|
||||
<body>
|
||||
<div class="min-h-screen bg-bakery-50 flex flex-col">
|
||||
<!--/* Hidden until it has focus: the first thing a keyboard reaches, so the nav isn't a toll gate on
|
||||
every page. */-->
|
||||
<a href="#content" class="skip-link">Skip to the page</a>
|
||||
<div th:replace="~{fragments/header :: header}"></div>
|
||||
<main class="flex-grow">
|
||||
<main id="content" class="flex-grow">
|
||||
<div th:replace="${content}"></div>
|
||||
</main>
|
||||
<div th:replace="~{fragments/footer :: footer}"></div>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="measure space-y-12">
|
||||
<section>
|
||||
<h2 class="h3 text-bakery-900 mb-4">How it started</h2>
|
||||
<p class="text-bakery-800 leading-relaxed">
|
||||
<p class="opening-para text-bakery-800 leading-relaxed">
|
||||
Morissa Bennett opened The Vine in 2024, at 215 E Main Street, in the middle of downtown
|
||||
Princeville. The plan was not complicated. Bake it ourselves, sell it ourselves, and keep
|
||||
enough tables that nobody feels rushed out the door.
|
||||
@@ -42,6 +42,14 @@
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<figure>
|
||||
<img th:src="${photos.of('gallery/Cinnamon Rolls.webp')}"
|
||||
alt="Cinnamon rolls, iced, on the counter at The Vine"
|
||||
width="1200" height="800" loading="lazy"
|
||||
class="photo w-full h-64 md:h-80 object-cover">
|
||||
<figcaption class="mt-3 label text-bakery-500 text-center">The cinnamon rolls, most mornings</figcaption>
|
||||
</figure>
|
||||
|
||||
<section>
|
||||
<h2 class="h3 text-bakery-900 mb-4">The cakes are the fun part</h2>
|
||||
<p class="text-bakery-800 leading-relaxed">
|
||||
@@ -53,6 +61,14 @@
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<figure>
|
||||
<img th:src="${photos.of('gallery/Cakes.webp')}"
|
||||
alt="A decorated cake made to order at The Vine"
|
||||
width="1200" height="800" loading="lazy"
|
||||
class="photo w-full h-64 md:h-80 object-cover">
|
||||
<figcaption class="mt-3 label text-bakery-500 text-center">Made to order, whatever the week is celebrating</figcaption>
|
||||
</figure>
|
||||
|
||||
<section>
|
||||
<h2 class="h3 text-bakery-900 mb-4">Around town</h2>
|
||||
<p class="text-bakery-800 leading-relaxed">
|
||||
|
||||
@@ -200,3 +200,37 @@
|
||||
@utility band {
|
||||
@apply py-14 md:py-20;
|
||||
}
|
||||
|
||||
/*
|
||||
* THE PAGE YOU ARE ON, in the nav. A rule under the word rather than a colour change: the nav is already
|
||||
* sage on cream, and darkening it further reads as a hover, not as "you are here".
|
||||
*/
|
||||
@utility is-here {
|
||||
@apply text-bakery-900;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--color-bakery-500);
|
||||
text-decoration-thickness: 2px;
|
||||
text-underline-offset: 8px;
|
||||
}
|
||||
|
||||
/*
|
||||
* The keyboard's way past the nav. Off-screen until focused, then a normal-looking button in the corner —
|
||||
* the pattern only works if it becomes visible, which is the half everyone forgets.
|
||||
*/
|
||||
@utility skip-link {
|
||||
@apply sr-only;
|
||||
&:focus {
|
||||
@apply not-sr-only fixed left-4 top-4 z-50 pill-sage;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* An opening paragraph set like a printed one. Used once, on the story — a drop cap on every page would
|
||||
* be a costume rather than a voice.
|
||||
*/
|
||||
@utility opening-para {
|
||||
&::first-letter {
|
||||
@apply font-adbhashitha text-6xl text-bakery-700 float-left mr-3 mt-1;
|
||||
line-height: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,24 @@ class SiteControllerTest {
|
||||
mvc.perform(get("/products")).andExpect(content().string(containsString("href=\"/catering\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void theNavMarksThePageYouAreOn() throws Exception {
|
||||
// Generated from the path the controller set, so a wrong model attribute would mark nothing at
|
||||
// all — and nothing at all looks exactly like a page that simply has no active link.
|
||||
mvc.perform(get("/catering"))
|
||||
.andExpect(content().string(containsString("href=\"/catering\" aria-current=\"page\"")));
|
||||
mvc.perform(get("/products"))
|
||||
.andExpect(content().string(containsString("href=\"/products\" aria-current=\"page\"")))
|
||||
.andExpect(content().string(not(containsString("href=\"/catering\" aria-current"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyPageOffersTheKeyboardAWayPastTheNav() throws Exception {
|
||||
mvc.perform(get("/"))
|
||||
.andExpect(content().string(containsString("href=\"#content\"")))
|
||||
.andExpect(content().string(containsString("id=\"content\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aCateringButtonStartsTheEnquiryOffAboutThatTable() throws Exception {
|
||||
mvc.perform(get("/contact").param("about", "Weddings"))
|
||||
|
||||
Reference in New Issue
Block a user