/* * 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); }