606deee009a4dbc22823a4e539cff67f915983dd
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
606deee009 |
Photographs hold still, and every page opens the same way
HOVER IS OFF THE IMAGES. The product cards lifted, the homepage's three cards lifted and zoomed their
photo 5% on the way. None of those cards is a link, so the lift was promising a click that isn't there,
and a photograph of a cake does not benefit from moving. `panel-lift` is still defined for the day
something IS clickable; nothing on the public site uses it.
EVERY PAGE NOW OPENS THE SAME: a small-caps line, the title in the display face, one sentence saying what
this is. Two dresses for it — `photographed` for the pages that sell or tell (catering, the story), where a
blurred photo under the sage wash does what the homepage hero does; `plain` for the pages you arrive at
already knowing what you want, where a photograph would be in front of the thing you came for. It was four
pieces of markup that happened to look similar; it is one fragment with two variants. Products and Contact
gained the eyebrow they were missing ("From the counter", "Say hello").
THE HOMEPAGE'S ORDER was hero → what people come in for → story → catering → visit: two cream sections
butted together at the end, with the one block that sells something buried between them. Now hero →
what people come in for → catering (on the deeper cream, so two light sections still read as two) → story
→ visit. Dark, light, light-but-different, dark, light.
TWO BUGS THE SCREENSHOTS FOUND, both invisible at the width I had been checking:
- Between 1024 and about 1200 the wordmark, the nav gaps AND the open-now line all stepped up at once, so
the script overflowed its box and painted over the branch mark. They step up at xl now, separately.
- The open-now fragment hardcoded `inline-flex` while the header passed `hidden lg:…`, which is two rules
for one property — the winner decided by Tailwind's output order rather than by the markup, which is why
"hidden" wasn't hiding it. The caller owns display now.
Checked at 390, 800, 900, 1000, 1024, 1100, 1200, 1280 and 1440. 60 tests green.
|
||
|
|
9ba06394d0 |
Hero: 56svh, so the homepage looks like it has more than one thing on it
At 78svh a laptop's first screen was the lockup and nothing else. It is a floor rather than a height — content still wins on a short window — and at 56 the fold now carries the lockup, the line under it, both buttons, and the top of "What people come in for", which is what invites the scroll. Tightened the two gaps inside it to match (mb-10 to mb-8), or the shorter band would just have had the same air distributed worse. Checked at 1366×768, the size of the laptop most people have. |
||
|
|
69af6c672e |
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. |
||
|
|
007d298b36 |
Everything on the shared names — the copy-paste is gone
The ported markup was written a page at a time, so the same thing was said several ways. Counted before touching anything: nine variants of the page gutter, seven max-widths, six link styles, two definitions of an input, and one stray shadow. Now: `container` (which already centres and pads), `measure`/`measure-wide`, `link`/`link-plain`/ `link-on-dark`, one `field`, `photo`, `page-head`. Nothing in a template writes `bg-white rounded-… shadow-…` or `underline underline-offset-4` any more — 119 uses of `panel`, 82 of `label`, 52 of `panel-lift`, 17 of `link`. TWO REAL FIXES FELL OUT OF IT, not just tidying: - Every section said `container mx-auto px-4`: `mx-auto` twice, and a px-4 that beat the responsive padding inside `container`. The gutter was 16px at every width, including on a 27-inch screen. Removing the copy-paste restores sm:px-6 lg:px-8, which is most of why the pages now breathe. - The public form and the admin had grown two different inputs — cream vs white, ring-2 vs ring-1, py-2.5 vs py-2, and different text sizes. There is one `field` now, and the same focus ring as everything else. The admin uses the same names where they are the same thing (`link`, `field`, `h4`, `--radius-*`) and keeps its own denser buttons, because it is a tool and not a shop front. 53 tests green. Every class used across all nine pages resolves in the compiled stylesheet — that check is what makes a sweep this size safe to do with a script. |
||
|
|
71d10b0749 |
Corners: 12px, and named once
24px (Tailwind's rounded-3xl) was the roundest thing on the page and read as a phone app. A printed card has a corner you can see. Two tokens rather than a find-and-replace, so this is answered once and not per template: `--radius-panel` (12px) for anything with an edge — cards, photos mounted on them, the admin's boxes — and `--radius-field` (8px) for inputs, which were three different radii across the public form and the admin. Buttons stay fully round: the pill is the brand's button shape and always was. 53 tests green, every class on all seven checked pages still resolves. |
||
|
|
a42f1ee8b1 |
Make a card look like it came from the shop, not from a component library
The cards were white boxes with a grey drop shadow and no edge — the default card of every web app,
sitting on a page that is otherwise cream, sage and a hand-drawn vine. Four changes, all of them things
the brand already owns:
- A WARM HAIRLINE. A card now has an edge, so it reads as stock lying on a counter rather than as a panel
floating over a screen.
- A SHADOW IN SAGE, not black — rgba(35,43,30,…), the darkest colour in the palette. Grey shadows are what
made them feel borrowed.
- A CREAM HEAD (`panel-head`). On a catering card the size, price and "About 6–8 people" sit on the tint
with the contents below, the way a printed menu separates what it costs from what you get. Product cards
use the same band for their caption, so the photo reads as mounted on the card instead of being it.
- THE BRANCH, at watermark weight, in the corner of every price card. It is the one mark that is
unmistakably this shop, and it costs nothing: the mask was already there for the wordmark.
The lift is a lift now, two pixels, rather than the shadow just darkening.
TWO THINGS TAILWIND v4 MADE ME FIX ALONG THE WAY. `.mark`, `.mark-l`, `.mark-r` and `.no-scrollbar` were
plain classes in @layer components, and only a registered utility can be @applied by another rule — the
watermark builds on the branch mark, so they became @utility like everything else here. And rewriting that
file dropped the `summary { list-style: none }` reset, which would have put a disclosure triangle beside
the hamburger on every phone; caught it by grepping the compiled CSS rather than by luck.
53 tests green, every class on every page resolves, and the price cards are in the screenshot.
|
||
|
|
ec61b40b12 |
A design pass: the type ladder from the logo, and one name per thing
TYPOGRAPHY, taken from the lockup and walked down to something you can read a paragraph in. "The Vine" in LeJour Script over COFFEEHOUSE + BAKERY in AdBhashitha is the whole brand voice, so: the script stays in the wordmark and nowhere else (it is unreadable at length); AdBhashitha carries h1–h4 and the price, and keeps the wordmark's 0.01em letter-spacing so a heading sits on the same rhythm as the logo above it; `eyebrow` and `label` are the hinge — sans, but letterspaced like the tagline — and then plain Raleway. Sizes are declared once in type.css instead of being re-chosen per template, and none of the type classes set a colour, because the same heading appears in sage on cream and in cream on sage. BUTTONS AND SURFACES have names now. `panel` is the white box that was "bg-white rounded-3xl shadow-xs" written out in nine places (which is how two of them end up with different corners); `panel-lift` is one that reacts. `pill` is one shape in four colourways, which exist because this site puts buttons on cream AND on sage and they cannot be the same colour on both. `chip`/`chip-on` are the products filter. RHYTHM: `section` (py-16 md:py-24) and `band` (py-14 md:py-20, tighter because a coloured strip is already doing the separating) replace nine hand-picked paddings. FOCUS: one visible ring for everything reachable by keyboard, as an outline rather than a shadow so it works on cream, on white and on the sage bands, at zero specificity so a utility can still win. Z-INDEX: written down in base.css, all two of it — 40 the sticky header, 30 the mobile panel, which is a child of the header and so can only ever sit under the bar. Everything else stacks in document order. The point of the comment is that a third layer doesn't get invented at 9999. DROPDOWNS: the admin's category selects were drawing the platform's own arrow in the platform's grey; they get the site's chevron and room for it. 53 tests green. Every class used across all nine pages — five public, four admin — resolves in the compiled stylesheet, so nothing here is styled by a name that doesn't exist. |
||
|
|
5225312a86 |
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).
|
||
|
|
70af2922f5 |
The site renders itself: Thymeleaf pages, and a catering page among them
The public site was a React SPA. It is now server-rendered Thymeleaf, and the goodie box and catering tables added in the previous commit have a page of their own. The look is unchanged: the templates carry the same Tailwind classes the components did, and every one of the 241 classes the five pages use resolves in the compiled stylesheet. WHAT WENT AWAY. PageMetaController — 148 lines whose only job was to splice per-page <title> and OG tags into one shell with regular expressions, with a test that read the real index.html so that reformatting it failed the build instead of silently breaking the rewriting. A page rendered on the server writes its own head. Also react-router (no client-side routes left), motion, vite-plugin-svgr, and the SPA fallback (platform.web.spa.enabled=false): with the site server-rendered, forwarding a mistyped URL to /index.html would answer with a blank admin shell and a 200 instead of the site's own 404 page. WHAT GOT BETTER ON THE WAY, none of it visible. The category filter is a ?category= link, so every filtered view is a URL you can send someone and a crawler can reach all forty items instead of the twelve the default filter showed. The contact form is a form post: the enquiry is recorded before delivery is attempted, and a refused relay re-renders the page with what the visitor typed still in the boxes. The mobile menu is a <details> — the React version needed four effects to close on navigation, close on Escape, stop the page behind it scrolling, and unmount (a panel parked off-screen still extends the scrollable area, which is how you used to be able to scroll sideways and find the menu); a new document cannot inherit an open menu. THE PUBLIC SITE SHIPS 5 KB OF JAVASCRIPT, and works without it. The product cards are scroll-snap strips, so the photos swipe on a phone and scroll with a trackpad unaided; gallery.js adds the arrows and the dots, and creates them itself rather than having the template render controls that would sit there dead. Tailwind still needs its compiler, so npm remains a BUILD tool: the CLI compiles the templates into static/css/site.css at process-classes (so `spring-boot:run` gets it too), and frontend/ now builds only that stylesheet and the admin. The brand tokens are one file both stylesheets import — the alternative was the shop front and the screen that edits it drifting a shade apart. The stylesheet URL carries ?v=<sha>, because one hand-written CSS file has no content hash and a deploy has to be able to tell a browser that what it cached is stale. The admin is still React and is untouched, apart from losing the router it no longer needs. It is an editor, not content. PlatformContractTest stopped inheriting platform-starter-test's contract and restates it. The shared version asserts that an unknown path forwards to the SPA shell, which is no longer true here, and its test methods are package-private so it cannot be overridden. The platform should decide that assertion from platform.web.spa.enabled — noted in the file. 9 new tests (46 total): every page's real title and og:url, the catalogue and the catering tables in the HTML rather than fetched afterwards, server-side filtering, the 404, and that a crafted ?about= link cannot put words of its own choosing in front of a customer. |