/*
* The brand: the sage-and-cream palette, the three fonts, and the base element rules.
*
* Shared deliberately. Two stylesheets are compiled from this — the server-rendered site
* (src/main/tailwind/site.css, scanning the Thymeleaf templates) and the admin SPA
* (frontend/src/index.css, scanning the React source) — and if the tokens were copied into both, the
* two halves of the same site would drift a shade apart the first time one of them was edited.
*
* Fonts are served by Spring from /fonts, not bundled by Vite, so both stylesheets can name the same
* URL and the browser caches one copy.
*/
@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;
}
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);
}
}
/* 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
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 ; its default disclosure triangle would sit next to the hamburger. */
summary {
list-style: none;
}
summary::-webkit-details-marker {
display: none;
}
::selection {
background-color: var(--color-bakery-300);
color: var(--color-bakery-900);
}