Give the other pages somewhere to go, and move the stylesheets under src/main

MARKETING, not decoration — each of these was a page that stopped:

- The HOMEPAGE never mentioned catering. A customer found the page by reading the nav. It now has a
  section with a photo, what the three tables cover, and a button.
- PRODUCTS was a title and a grid. It gets a line saying what it is ("cakes and decorated cookies are
  made to order, so most of what follows started as somebody describing what they wanted") and a closing
  band with somewhere to go — an order, or the catering page.
- CONTACT was a form and nothing else. The things somebody on that page actually wants — when we're open,
  where we are, the phone number — were on the homepage and in the footer but not there. Now beside the
  form, with a card pointing at catering.
- OUR STORY got the shop's own front as a band and an ending, so the story leads somewhere.
- The FOOTER gained the opening hours, which is what people come to a bakery's site for and is now on
  every page.

CLEANED UP ON THE WAY. The hours and the address were about to exist in three places, so they are one
fragment (fragments/visit.html) that takes the classes that vary by where it sits — cream in the footer,
sage on white in a card. A shop that changes its Saturday hours should change them once.

STYLES MOVED to src/main/styles, with the rest of the app's source, and split by what each file is for:
theme.css (the fonts and the palette — names, nothing drawn), base.css (bare elements), components.css
(the site's own classes), admin.css (the admin's controls), and site.css as the entry that imports them in
cascade order and declares what Tailwind scans. It was one grab-bag file called tokens.css in a folder at
the repo root. node_modules has to stay beside them — Tailwind resolves `@import "tailwindcss"` by walking
up from the CSS file — so the whole npm project moved together, and the pom, .gitignore and README moved
with it.

53 tests green, `mvn package` builds the stylesheet from its new home, and every class used on all five
pages resolves in the compiled CSS (185/159/175/148/140, none missing).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
2026-07-26 17:56:00 -05:00
co-authored by Claude Opus 5
parent 5ab6febb4c
commit 8d54db7c30
19 changed files with 370 additions and 218 deletions
+50
View File
@@ -0,0 +1,50 @@
/*
* The admin's controls.
*
* Component classes rather than utility strings repeated through the markup, because the admin is made
* of forms: there are dozens of buttons on a page and each one is its own <form>. The public pages keep
* their utilities inline — they each look different — but "a button in the admin" should be one decision
* in one place. These are the same buttons the React screen had, from the same class strings it composed.
*
* @utility, not `.btn { @apply … }` in a components layer: Tailwind v4 will only let you @apply a class
* it knows as a utility, and only a registered utility can take a variant — which `file:btn-secondary`
* on the photo pickers needs.
*/
@utility btn {
@apply inline-flex items-center justify-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium
transition-colors;
}
@utility btn-primary {
@apply btn bg-bakery-600 text-white hover:bg-bakery-700
disabled:opacity-40 disabled:cursor-not-allowed;
}
@utility btn-secondary {
@apply btn border border-bakery-300 text-bakery-800 hover:bg-bakery-100
disabled:opacity-40 disabled:cursor-not-allowed;
}
@utility btn-danger {
@apply btn text-red-700 hover:bg-red-50 disabled:opacity-40 disabled:cursor-not-allowed;
}
/* Square, for the arrows and the crosses — a row of these is how anything gets reordered. */
@utility btn-icon {
@apply inline-flex items-center justify-center w-7 h-7 rounded-md border border-bakery-300
text-bakery-700 hover:bg-bakery-100 transition-colors
disabled:opacity-30 disabled:cursor-not-allowed;
}
@utility field {
@apply w-full rounded-md border border-bakery-300 bg-white px-3 py-2 text-sm
focus:border-bakery-500 focus:outline-none focus:ring-1 focus:ring-bakery-500;
}
@utility card {
@apply rounded-lg border border-bakery-200 bg-white p-4 shadow-sm;
}
@utility card-heading {
@apply font-adbhashitha text-xl text-bakery-800;
}
+40
View File
@@ -0,0 +1,40 @@
/*
* Element defaults: the page background, the body type, the fade-in, and the two small resets the site
* needs. Anything with a class name belongs in components.css instead.
*/
html {
scroll-behavior: smooth;
/* Nothing on this site is meant to scroll sideways. */
overflow-x: hidden;
}
body {
background-color: var(--color-bakery-50);
color: var(--color-bakery-900);
font-family: var(--font-sans);
overflow-x: hidden;
opacity: 0;
animation: fadeIn 0.5s ease-in forwards;
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
body { animation: none; opacity: 1; }
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
::selection {
background-color: var(--color-bakery-300);
color: var(--color-bakery-900);
}
+60
View File
@@ -0,0 +1,60 @@
/*
* The public site's own classes — the ones that would be unreadable as a string of utilities in every
* template that needs them. The admin's controls are in admin.css.
*/
/* In @layer components, matching the old site. That ordering matters: a utility like `px-4` — which
the markup applies alongside `container` on nearly every section — has to win over this, or every
gutter on the site silently widens. */
@layer components {
.container {
@apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8;
}
/*
* The two branch marks of the wordmark, as masks rather than inline SVG.
*
* They have to take their colour from the surrounding text — sage in the header, cream on the hero
* and in the footer — which an <img> cannot do. React solved that by inlining them through svgr,
* but these files are 31 KB each and the lockup appears up to three times on a page: inlining them
* in server-rendered HTML would add ~190 KB to every response. A mask over `currentColor` keeps the
* tinting, keeps the files cacheable, and costs two requests once.
*/
.mark {
background-color: currentColor;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-size: contain;
mask-size: contain;
}
.mark-r {
-webkit-mask-image: url('/images/logo_R.svg');
mask-image: url('/images/logo_R.svg');
}
.mark-l {
-webkit-mask-image: url('/images/logo_L.svg');
mask-image: url('/images/logo_L.svg');
}
/*
* The photo strip on a product card scrolls, but a scrollbar across the bottom of a photo is not part
* of the design. Hiding it is safe here because the strip is not the only way through the photos: it
* snaps, it takes arrow keys, and the arrows and dots are on top of it.
*/
.no-scrollbar {
scrollbar-width: none;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
}
/* The mobile menu is a <details>; its default disclosure triangle would sit next to the hamburger. */
summary {
list-style: none;
}
summary::-webkit-details-marker {
display: none;
}
+1202
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
{
"name": "itsthevine-styles",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"build": "tailwindcss -i site.css -o ../../../target/classes/static/css/site.css --minify",
"build:css": "tailwindcss -i site.css -o ../../../target/classes/static/css/site.css --minify",
"watch": "tailwindcss -i site.css -o ../../../target/classes/static/css/site.css --watch"
},
"devDependencies": {
"@tailwindcss/cli": "4.3.3",
"tailwindcss": "4.3.3"
}
}
+25
View File
@@ -0,0 +1,25 @@
/*
* The site's one stylesheet, compiled by the Tailwind CLI into target/classes/static/css.
*
* It lives under src/main with the rest of the app's source, and it has to stay in the same directory as
* package.json: Tailwind resolves `@import "tailwindcss"` by walking up from the CSS file looking for
* node_modules. There is no bundler and no framework here — the site and the admin are both
* server-rendered HTML, and this is the whole of the asset pipeline.
*
* Imported in cascade order: names, then bare elements, then classes.
*/
@import "tailwindcss";
@import "./theme.css";
@import "./base.css";
@import "./components.css";
@import "./admin.css";
/*
* Where Tailwind looks for class names. Every template, and gallery.js — the product-card arrows are
* created in script, so their classes are only written down there. A utility exists in the output only if
* Tailwind saw it in one of these files, which is why a class name must never be assembled from pieces at
* runtime.
*/
@source "../resources/templates";
@source "../resources/static/js";
+45
View File
@@ -0,0 +1,45 @@
/*
* The brand: three typefaces and the sage-and-cream palette. Nothing here draws anything — these are the
* names the rest of the CSS and every template are written in.
*
* Fonts are served by Spring from /fonts rather than bundled, so the stylesheet can name a stable URL and
* the browser caches one copy across the whole site.
*/
@font-face {
font-family: 'Raleway';
src: url('/fonts/raleway-latin.woff2') format('woff2');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'AdBhashitha';
src: url('/fonts/AdBhashitha.woff') format('woff');
font-display: swap;
}
@font-face {
font-family: 'LeJour Script';
src: url('/fonts/LeJour-Script.woff') format('woff');
font-display: swap;
}
@theme {
/* Sage & Cream. 500 is the signature sage; 600+ are the darker tones that white text can actually
sit on (500 on white is only 3.6:1 — too low). */
--color-bakery-50: #faf7f0;
--color-bakery-100: #f0efe3;
--color-bakery-200: #dde0cc;
--color-bakery-300: #c3cbae;
--color-bakery-400: #a2ae8b;
--color-bakery-500: #7c8b6b;
--color-bakery-600: #5f6f52;
--color-bakery-700: #4a5740;
--color-bakery-800: #37412f;
--color-bakery-900: #232b1e;
--font-sans: 'Raleway', ui-sans-serif, system-ui, sans-serif;
--font-adbhashitha: 'AdBhashitha', ui-serif, Georgia, serif;
--font-lejour: 'LeJour Script', ui-serif, Georgia, cursive;
}