Files
tanshu e64f65642f Add ESLint and Prettier
Prettier owns formatting: .prettierrc.json, format and format:check scripts, and
a one-off pass over the repo so the check is green from the start. Embedded
language formatting is off deliberately - the renderers build HTML in html
tagged templates and letting Prettier reflow those would change the markup
customers receive. docs/ is excluded because it is hand-wrapped prose, and shell
scripts have no Prettier parser.

ESLint covers the JavaScript: the two browser scripts, the build and check
scripts, and its own config. no-var is scoped out for the browser scripts on
purpose; they are plain ES5-shaped IIFEs and a hoisting difference would only
show up in a browser.

ESLint cannot lint the TypeScript here: typescript@7 is the native port with no
JavaScript compiler API, and typescript-eslint requires a 4.8-6.1 peer, so it
refuses to install and forcing it through would leave a parser that cannot read
the compiler. tsc --noEmit stays the TypeScript gate; README records the two
ways to change that.

lint and format:check now run in deploy.sh, and VS Code formats on save with
Prettier and surfaces ESLint findings.
2026-09-22 23:15:08 +05:30

568 lines
11 KiB
CSS

/* Hops n Grains menus, to the Atelier Dining & Hospitality system that came out
of Stitch: ivory linen and espresso for the guest's menu, burnished gold for
the one active thing on screen, Playfair Display carrying the restaurant's
voice and Plus Jakarta Sans carrying everything transactional.
See docs/design/stitch/atelier-dining-hospitality.md. */
@font-face {
font-family: 'Playfair Display';
font-style: normal;
font-weight: 400 700;
font-display: swap;
src: url('/assets/fonts/playfair-display-latin.woff2') format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 200 800;
font-display: swap;
src: url('/assets/fonts/plus-jakarta-sans-latin.woff2') format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
:root {
/* guest surfaces: warm ivory linen, never stark white */
--steel: #faf8f5;
--rail: #f4f1ea;
--paper: #ffffff;
--ink: #121110;
--ink-soft: #4b4640;
--ink-dim: #8c857b;
--ink-on-paper: #121110;
--accent: #c59b58;
--copper: #b08544;
--hairline: #e6e1d8;
--hairline-soft: #efebe4;
--sheet-shadow: rgba(18, 17, 16, 0.06);
--shadow-card: 0 4px 20px -2px rgba(18, 17, 16, 0.04), 0 2px 6px -1px rgba(18, 17, 16, 0.02);
--shadow-float: 0 12px 36px -4px rgba(18, 17, 16, 0.08), 0 4px 12px -2px rgba(18, 17, 16, 0.04);
--font-display: 'Playfair Display', Georgia, 'Times New Roman', serif;
--font-ui: 'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
--radius-card: 0.5rem;
--radius-pill: 9999px;
--step-0: 1rem;
--step-1: 1.25rem;
--step-2: 1.75rem;
--page-max: 780px;
--content-max: 46rem;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
background: var(--steel);
color: var(--ink);
font-family: var(--font-ui);
font-size: var(--step-0);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
a {
color: inherit;
text-decoration: none;
}
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
}
/* Every uppercase label in this system is tracked, never tight. */
.label {
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
}
/* ---------- Landing: the menu directory ---------- */
.landing {
max-width: var(--content-max);
margin: 0 auto;
padding: 0 1.25rem 3.5rem;
}
.masthead {
padding: clamp(2.25rem, 10vh, 4.5rem) 0 1.75rem;
border-bottom: 1px solid var(--hairline);
}
.wordmark {
margin: 0;
font-family: var(--font-display);
font-size: clamp(2rem, 8vw, 2.75rem);
font-weight: 400;
letter-spacing: -0.02em;
line-height: 1.05;
}
.masthead__tagline {
margin: 0.6rem 0 0;
color: var(--ink-soft);
font-size: 0.95rem;
}
.shelves {
display: block;
}
.shelf {
display: flex;
align-items: center;
gap: 0.9rem;
padding: 0.95rem 0;
border-bottom: 1px solid var(--hairline);
}
.shelf__peek {
flex: 0 0 auto;
width: 3.4rem;
height: 4.4rem;
overflow: hidden;
border: 1px solid var(--hairline);
border-radius: var(--radius-card);
background: var(--paper);
}
.shelf__peek img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: top center;
transition: transform 0.4s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.shelf__name {
font-family: var(--font-display);
font-size: 1.45rem;
font-weight: 400;
letter-spacing: -0.01em;
line-height: 1.15;
}
.shelf:hover .shelf__name,
.shelf:focus-visible .shelf__name {
color: var(--amber-deep, #b08544);
}
.shelf:hover .shelf__peek img,
.shelf:focus-visible .shelf__peek img {
transform: scale(1.05);
}
/* the dot leader that carries the eye from the name to the count */
.leader {
flex: 1 1 auto;
align-self: flex-end;
margin-bottom: 0.4rem;
border-bottom: 1px dotted #cdc5bd;
}
.shelf__pages {
flex: 0 0 auto;
color: var(--ink-soft);
font-size: 0.78rem;
font-weight: 600;
font-variant-numeric: tabular-nums;
letter-spacing: 0.02em;
}
.empty {
max-width: 34ch;
margin: 1.25rem 0 0;
color: var(--ink-soft);
font-size: 1.05rem;
}
/* ---------- Section page: the menu bar ---------- */
.bar {
position: sticky;
top: 0;
z-index: 20;
display: flex;
align-items: center;
gap: 1rem;
padding: 0.6rem 1.25rem;
background: color-mix(in srgb, var(--steel) 92%, transparent);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--hairline);
}
.bar__brand {
display: flex;
align-items: center;
gap: 0.55rem;
flex: 0 0 auto;
}
.bar__brand img {
display: block;
width: 26px;
height: 26px;
border-radius: var(--radius-pill);
}
.bar__name {
font-family: var(--font-display);
font-size: 1.05rem;
letter-spacing: -0.01em;
white-space: nowrap;
}
.bar__nav {
display: flex;
flex: 1 1 auto;
gap: 0.35rem;
overflow-x: auto;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
}
.bar__nav::-webkit-scrollbar {
display: none;
}
.bar__link {
padding: 0.42rem 0.9rem;
border: 1px solid transparent;
border-radius: var(--radius-pill);
color: var(--ink-soft);
font-size: 0.78rem;
font-weight: 600;
letter-spacing: 0.02em;
white-space: nowrap;
transition:
background 0.18s ease,
color 0.18s ease;
}
.bar__link:hover {
background: color-mix(in srgb, var(--accent) 16%, transparent);
color: var(--ink);
}
.bar__link.is-current {
background: var(--ink);
color: var(--steel);
}
.pour {
position: absolute;
right: 0;
bottom: -1px;
left: 0;
height: 2px;
background: transparent;
}
.pour__fill {
display: block;
width: 0;
height: 100%;
background: var(--accent);
}
/* ---------- The pages themselves ---------- */
.section-heading {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
max-width: var(--page-max);
margin: 1.75rem auto 1rem;
padding: 0 1.25rem;
}
.section-heading__label {
font-family: var(--font-display);
font-size: var(--step-2);
font-weight: 400;
letter-spacing: -0.015em;
}
.section-heading__date {
color: var(--ink-dim);
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
font-variant-numeric: tabular-nums;
}
.notice {
max-width: var(--page-max);
margin: 0 auto 1.25rem;
padding: 0.7rem 0.9rem;
border-left: 2px solid var(--accent);
background: var(--rail);
font-size: 0.9rem;
}
.notice a {
color: var(--copper);
text-decoration: underline;
}
.notice--admin {
max-width: none;
background: color-mix(in srgb, var(--accent) 12%, var(--rail));
}
.pages {
max-width: var(--page-max);
margin: 0 auto;
padding: 0 1.25rem 1.5rem;
}
.page {
margin: 0 0 0.85rem;
padding: 0.4rem;
background: var(--paper);
border: 1px solid var(--hairline);
border-radius: var(--radius-card);
box-shadow: var(--shadow-card);
}
.page__zoom {
display: block;
}
.page img {
display: block;
width: 100%;
height: auto;
border-radius: 0.25rem;
}
/* ---------- Footer: page index and older menus ---------- */
.foot {
max-width: var(--page-max);
margin: 0 auto 5rem;
padding: 0 1.25rem 1rem;
border-top: 1px solid var(--hairline);
}
.fold {
padding: 0.85rem 0;
border-bottom: 1px solid var(--hairline);
}
.fold summary {
color: var(--ink-soft);
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
cursor: pointer;
}
.fold[open] summary {
color: var(--ink);
}
.index {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
margin: 0;
padding: 1rem 0 0;
list-style: none;
}
.index a {
display: block;
min-width: 2.4rem;
padding: 0.35rem 0.5rem;
border: 1px solid var(--hairline);
border-radius: var(--radius-pill);
color: var(--ink-soft);
font-size: 0.78rem;
font-weight: 600;
font-variant-numeric: tabular-nums;
text-align: center;
}
.index a:hover,
.index a:focus-visible {
background: var(--ink);
color: var(--steel);
}
.older {
margin: 0.6rem 0 0;
padding: 0;
list-style: none;
}
.older a {
display: flex;
justify-content: space-between;
gap: 1rem;
padding: 0.55rem 0.15rem;
border-bottom: 1px solid var(--hairline-soft);
}
.older__label {
font-family: var(--font-display);
font-size: 1.05rem;
}
.older__date {
color: var(--ink-dim);
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
font-variant-numeric: tabular-nums;
}
.fold__back {
margin: 0.85rem 0 0;
}
.fold__back a {
color: var(--copper);
text-decoration: underline;
}
/* ---------- Counter and zoom ---------- */
.counter {
position: fixed;
right: 1.25rem;
bottom: calc(1.25rem + env(safe-area-inset-bottom));
z-index: 30;
display: inline-flex;
align-items: baseline;
gap: 0.35rem;
padding: 0.55rem 0.9rem;
border: 0;
border-radius: var(--radius-pill);
background: var(--ink);
color: var(--steel);
font: inherit;
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
font-variant-numeric: tabular-nums;
box-shadow: var(--shadow-float);
cursor: pointer;
}
.counter[hidden] {
display: none;
}
.counter__total {
color: color-mix(in srgb, var(--steel) 62%, transparent);
}
.zoom {
position: fixed;
inset: 0;
z-index: 50;
overflow: auto;
background: rgba(18, 17, 16, 0.45);
backdrop-filter: blur(8px);
-webkit-overflow-scrolling: touch;
padding: 1.25rem 0;
}
.zoom img {
display: block;
width: min(100%, 1600px);
height: auto;
margin: 0 auto;
border-radius: var(--radius-card);
box-shadow: var(--shadow-float);
}
.zoom__close {
position: fixed;
top: 0.75rem;
right: 0.75rem;
z-index: 1;
padding: 0.55rem 1rem;
border: 0;
border-radius: var(--radius-pill);
background: var(--ink);
color: var(--steel);
font: inherit;
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
cursor: pointer;
}
/* ---------- Wider screens ---------- */
@media (min-width: 62rem) {
.landing {
padding-bottom: 4.5rem;
}
.shelf {
padding: 1.15rem 0;
}
.shelf__name {
font-size: 1.7rem;
}
.pages {
max-width: 820px;
}
}
@media print {
.bar,
.counter,
.fold,
.notice {
display: none;
}
body {
background: #fff;
}
.page {
border: 0;
box-shadow: none;
break-inside: avoid;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation: none !important;
transition: none !important;
scroll-behavior: auto !important;
}
}