diff --git a/src/scss/abstracts/_tokens.scss b/src/scss/abstracts/_tokens.scss
new file mode 100644
index 0000000..98de8de
--- /dev/null
+++ b/src/scss/abstracts/_tokens.scss
@@ -0,0 +1,21 @@
+:root {
+ --ground: #171310;
+ --ground-2: #0f0c0a;
+ --surface: #221c16;
+ --surface-2: #2b2319;
+ --gold: #c9a35c;
+ --gold-bright: #e6c789;
+ --gold-dim: #a68b59;
+ --green: #33503f;
+ --green-deep: #203026;
+ --ink: #f1e8d8;
+ --ink-muted: #a89a83;
+ --rule: #42362590;
+
+ --font-serif: 'Fraunces', Georgia, 'Times New Roman', serif;
+ --font-mono: 'IBM Plex Mono', ui-monospace, Menlo, monospace;
+ --font-sans: 'Karla', ui-sans-serif, system-ui, sans-serif;
+
+ --danger-bg: rgba(180, 50, 40, 0.15);
+ --danger-text: #ff9d9d;
+}
diff --git a/src/scss/base/_reset.scss b/src/scss/base/_reset.scss
new file mode 100644
index 0000000..b3eae6a
--- /dev/null
+++ b/src/scss/base/_reset.scss
@@ -0,0 +1,57 @@
+* {
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html {
+ scroll-behavior: auto;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ground);
+ color: var(--ink);
+ font-family: var(--font-sans);
+ font-size: 1rem;
+ line-height: 1.65;
+ -webkit-font-smoothing: antialiased;
+}
+
+a {
+ color: inherit;
+}
+
+img,
+svg {
+ display: block;
+ max-width: 100%;
+}
+
+h1,
+h2,
+h3 {
+ font-family: var(--font-serif);
+ font-weight: 600;
+ line-height: 1.08;
+ text-wrap: balance;
+ margin: 0;
+}
+
+p {
+ margin: 0;
+}
+
+:focus-visible {
+ outline: 2px solid var(--gold-bright);
+ outline-offset: 3px;
+}
+
+/* ---------- sections ---------- */
+section {
+ padding-block: 5.5rem;
+}
diff --git a/src/scss/base/_utilities.scss b/src/scss/base/_utilities.scss
new file mode 100644
index 0000000..d3440dd
--- /dev/null
+++ b/src/scss/base/_utilities.scss
@@ -0,0 +1,80 @@
+/* ---------- wrap-aware separators (JS-toggled, see main.js) ----------
+ Shared hook used by the announcement banner and hero tagline row: a
+ "·" between two pieces of text that can independently wrap onto
+ separate lines. No CSS selector exists for "am I on the same line as
+ my sibling," so main.js measures and toggles this class instead of a
+ fixed breakpoint. Visible by default so a no-JS visitor always sees
+ the dot (today's behavior), never hidden by default.
+
+ display, not visibility: a hidden separator must take no space, or
+ it still gets counted when justify-content: center balances its flex
+ line — a lone badge sharing its line with a space-reserving invisible
+ dot renders off-center. Safe from the "text merges together" bug that
+ originally motivated reserving space: both wrap-sep contexts space
+ their siblings with a real flex `gap` (see .announcement-inner,
+ .tagline-row) rather than the separator's own margin, so removing it
+ from flow still leaves normal spacing behind, never a collision. */
+.wrap-sep-hidden {
+ display: none;
+}
+
+.container {
+ width: 100%;
+ max-width: 70rem;
+ margin: 0 auto;
+ padding-inline: 1.5rem;
+}
+
+.eyebrow {
+ font-family: var(--font-mono);
+ font-size: 0.72rem;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ color: var(--gold);
+}
+
+.muted {
+ color: var(--ink-muted);
+}
+
+.skip-link {
+ position: absolute;
+ left: 1rem;
+ top: -3rem;
+ background: var(--gold);
+ color: var(--ground-2);
+ padding: 0.6rem 1rem;
+ border-radius: 0.125rem;
+ font-family: var(--font-mono);
+ font-size: 0.85rem;
+ transition: top .15s ease;
+ z-index: 100;
+}
+
+.skip-link:focus {
+ top: 1rem;
+}
+
+/* scroll reveal */
+.reveal {
+ opacity: 0;
+ transform: translateY(0.875rem);
+ transition: opacity .6s ease, transform .6s ease;
+}
+
+.reveal.is-visible {
+ opacity: 1;
+ transform: none;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .reveal {
+ opacity: 1;
+ transform: none;
+ transition: none;
+ }
+}
+
+.font-mono {
+ font-family: var(--font-mono);
+}
diff --git a/src/scss/components/_admin-actions.scss b/src/scss/components/_admin-actions.scss
new file mode 100644
index 0000000..196f362
--- /dev/null
+++ b/src/scss/components/_admin-actions.scss
@@ -0,0 +1,43 @@
+/* Admin row actions: the button-group styling shared by every admin
+ list (menu items/categories, announcements, users). The actual
+ row/column layout for each list is its own CSS Grid, defined where
+ that list's markup lives below — there is no
anywhere in
+ this project. */
+.row-actions {
+ display: inline-flex;
+ gap: 0.5rem;
+ align-items: center;
+ flex-wrap: nowrap;
+ white-space: nowrap;
+}
+
+.row-actions .btn {
+ white-space: nowrap;
+}
+
+/* Groups a row's move (↑/↓) and edit/delete controls into two narrow
+ vertical stacks instead of several buttons side by side — keeps the
+ Actions column (and the row as a whole) from running wide. */
+.action-stack {
+ display: inline-flex;
+ flex-direction: column;
+ gap: 0.35rem;
+}
+
+@media (max-width: 47.5em) {
+ .row-actions {
+ flex-wrap: wrap;
+ white-space: normal;
+ }
+}
+
+.empty-state {
+ text-align: center;
+ padding: 3rem 1.5rem;
+ color: var(--ink-muted);
+}
+
+.empty-state p {
+ font-size: 0.95rem;
+ margin-bottom: 1.25rem;
+}
diff --git a/src/scss/components/_admin-data-grid.scss b/src/scss/components/_admin-data-grid.scss
new file mode 100644
index 0000000..c16b53b
--- /dev/null
+++ b/src/scss/components/_admin-data-grid.scss
@@ -0,0 +1,288 @@
+/* Admin list grids: menu items/categories, announcements, and users all
+ share one CSS Grid pattern instead of a — see the git history
+ for why. Every list's header row and data rows are grouped under one
+ selector here for the properties they all share (box model,
+ typography, the mobile reflow); each list then only has to declare
+ its own grid-template-columns/areas, i.e. the shape unique to it. */
+
+/* Each list's real column tracks live on the -grid-wrap — the one
+ shared parent grid. The header row and every data row don't compute
+ their own auto columns (which is what let them drift out of
+ alignment: each was sizing "auto" only against its own content); they
+ subgrid instead, inheriting the exact same tracks from this parent.
+ .foo-rows is display:contents purely so .foo-row stays a direct grid
+ child of .foo-grid-wrap for that inheritance — it has no visual
+ footprint of its own. */
+.item-grid-wrap {
+ grid-template-columns: auto minmax(0, 1fr) auto auto;
+}
+
+.ann-grid-wrap {
+ grid-template-columns: auto minmax(0, 1fr) auto auto auto auto;
+}
+
+.user-grid-wrap {
+ grid-template-columns: auto minmax(0, 1fr) auto auto auto;
+}
+
+.item-grid-wrap,
+.ann-grid-wrap,
+.user-grid-wrap {
+ display: grid;
+ column-gap: 0.75rem;
+ margin-top: 1rem;
+}
+
+.item-rows,
+.ann-rows,
+.user-rows {
+ display: contents;
+}
+
+.item-grid-head, .item-row,
+.ann-grid-head, .ann-row,
+.user-grid-head, .user-row {
+ display: grid;
+ grid-column: 1 / -1;
+ grid-template-columns: subgrid;
+ align-items: center;
+}
+
+.item-grid-head,
+.ann-grid-head,
+.user-grid-head {
+ padding: 0 0.75rem 0.85rem;
+ border-bottom: 1px solid var(--rule);
+ font-family: var(--font-mono);
+ font-size: 0.72rem;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: var(--gold);
+ font-weight: 500;
+}
+
+.item-row,
+.ann-row,
+.user-row {
+ padding: 0.85rem 0.75rem;
+ border-bottom: 1px solid var(--rule);
+}
+
+.item-row:last-child,
+.ann-row:last-child,
+.user-row:last-child {
+ border-bottom: none;
+}
+
+/* Each list's own shape: which named area each field maps to. "Status"
+ → status and "Actions" → actions mean the same thing in all three, so
+ those two are declared once. */
+.item-grid-head, .item-row {
+ grid-template-areas: "status item price actions";
+}
+
+.ann-grid-head, .ann-row {
+ grid-template-areas: "status headline type schedule placements actions";
+}
+
+.user-grid-head, .user-row {
+ grid-template-areas: "status username role created actions";
+}
+
+.col-status,
+.item-row [data-label="Status"],
+.ann-row [data-label="Status"],
+.user-row [data-label="Status"] {
+ grid-area: status;
+}
+
+.col-actions,
+.item-row [data-label="Actions"],
+.ann-row [data-label="Actions"],
+.user-row [data-label="Actions"] {
+ grid-area: actions;
+}
+
+.col-item { grid-area: item; }
+
+.col-price { grid-area: price; }
+
+.item-row [data-label="Item"] { grid-area: item; }
+
+.item-row [data-label="Price"] { grid-area: price; }
+
+.col-headline { grid-area: headline; }
+
+.col-type { grid-area: type; }
+
+.col-schedule { grid-area: schedule; }
+
+.col-placements { grid-area: placements; }
+
+.ann-row [data-label="Headline"] { grid-area: headline; }
+
+.ann-row [data-label="Type"] { grid-area: type; }
+
+.ann-row [data-label="Schedule"] { grid-area: schedule; }
+
+.ann-row [data-label="Placements"] { grid-area: placements; }
+
+.col-username { grid-area: username; }
+
+.col-role { grid-area: role; }
+
+.col-created { grid-area: created; }
+
+.user-row [data-label="Username"] { grid-area: username; }
+
+.user-row [data-label="Role"] { grid-area: role; }
+
+.user-row [data-label="Created"] { grid-area: created; }
+
+/* .item-rows being display:contents makes this a direct child of the
+ .item-grid-wrap grid when a category is empty — span every column or
+ it lands squeezed into just the first (Status) track. */
+.item-grid-empty {
+ grid-column: 1 / -1;
+ text-align: center;
+ padding: 1.5rem 0;
+}
+
+.timestamp-cell {
+ color: var(--ink-muted);
+ font-size: 0.8rem;
+}
+
+/* Each grid's own mobile arrangement, independent of the desktop one
+ above — this is the whole point of grid-template-areas over a
+ . Same fields, different map, per list. Placed after the
+ desktop rules above so it wins the cascade at equal specificity — a
+ rule's source position still matters once a media query lets both
+ compete. The reflow mechanics (hide the header row, stack fields with
+ a printed label, pin Actions to the end) are identical across all
+ three lists and the category header, so those are shared too; only
+ each one's grid-template-areas differs. */
+@media (max-width: 47.5em) {
+ /* The wrap collapses to a single column, and every subgridded row
+ below inherits that automatically — they keep `grid-template-
+ columns: subgrid` from the desktop rule, so this one change is all
+ it takes to reflow the whole list (see grid-template-areas per row
+ further down for how each field then stacks within that column). */
+ /* Every list's cards get a second column on mobile so Actions can sit
+ in the top-right corner alongside that list's leading field, instead
+ of stacking at the very bottom of the card — overrides the shared
+ single-column rule that would otherwise apply here too.
+ minmax(0, auto), not plain auto, so this column can still shrink
+ below its content's natural width under pressure instead of forcing
+ the whole row to overflow the card. */
+ .item-grid-wrap,
+ .ann-grid-wrap,
+ .user-grid-wrap {
+ grid-template-columns: 1fr minmax(0, auto);
+ }
+
+ /* Users' actions column gets a fixed cap instead of auto: "Change
+ Password" is long enough that its max-content size (what auto would
+ size the column to) is wider than this corner has room for, which
+ pushed the button past the card edge. A firm cap plus the
+ wrap/stretch rules below keep it predictable instead. */
+ .user-grid-wrap {
+ grid-template-columns: minmax(0, 1fr) 6rem;
+ }
+
+ /* Every row's Actions cell holds one or two .action-stacks (move
+ up/down, edit/delete, etc. — each stacked vertically, see
+ .action-stack itself further up) side by side. Keeps those stacks
+ from wrapping onto their own lines in this narrow corner, which is
+ what the shared .row-actions wrap rule below would otherwise do. */
+ .item-row .row-actions,
+ .ann-row .row-actions,
+ .user-row .row-actions {
+ flex-wrap: nowrap;
+ }
+
+ .item-grid-head,
+ .ann-grid-head,
+ .user-grid-head {
+ display: none;
+ }
+
+ .item-row,
+ .ann-row,
+ .user-row {
+ row-gap: 0.4rem;
+ border: 1px solid var(--rule);
+ border-radius: 0.25rem;
+ padding: 0.9rem 1rem;
+ margin-bottom: 0.85rem;
+ }
+
+ .item-row:last-child,
+ .ann-row:last-child,
+ .user-row:last-child {
+ border-bottom: none;
+ margin-bottom: 0;
+ }
+
+ .item-row [data-label]::before,
+ .ann-row [data-label]::before,
+ .user-row [data-label]::before {
+ content: attr(data-label);
+ display: block;
+ font-family: var(--font-mono);
+ font-size: 0.68rem;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ color: var(--gold);
+ margin-bottom: 0.3rem;
+ }
+
+ .item-row {
+ grid-template-areas:
+ "status actions"
+ "item item"
+ "price price";
+ /* Overrides the shared align-items: center above — on desktop that
+ vertically centers Status against the taller Item cell in one wide
+ row, which is fine, but here it also centers Status against the
+ taller Actions block above, pushing its badge down instead of
+ keeping both flush with the card's top edge. */
+ align-items: start;
+ }
+
+ .ann-row {
+ grid-template-areas:
+ "status actions"
+ "headline headline"
+ "type type"
+ "schedule schedule"
+ "placements placements";
+ align-items: start;
+ }
+
+ .user-row {
+ grid-template-areas:
+ "status actions"
+ "username username"
+ "role role"
+ "created created";
+ align-items: start;
+ }
+
+ /* Stretches Actions' whole flex chain out to the fixed 6rem column
+ above (each is shrink-wrapped by default), then lets "Change
+ Password" — long enough on its own to blow out this corner next to a
+ username — wrap onto two lines within that width instead of forcing
+ the column wider. Every other action-stack button here (Edit,
+ Delete, ↑/↓) is short enough to stay on one line regardless. */
+ .user-row .row-actions,
+ .user-row .action-stack,
+ .user-row .row-actions .btn {
+ width: 100%;
+ }
+
+ .user-row .btn-change-password {
+ white-space: normal;
+ }
+
+}
diff --git a/src/scss/components/_admin-modal.scss b/src/scss/components/_admin-modal.scss
new file mode 100644
index 0000000..bf74dfd
--- /dev/null
+++ b/src/scss/components/_admin-modal.scss
@@ -0,0 +1,69 @@
+/* ---------- admin modal dialogs ---------- */
+.admin-modal-backdrop {
+ position: fixed;
+ inset: 0;
+ background: rgba(15, 12, 10, 0.8);
+ backdrop-filter: blur(0.25rem);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ padding: 1.5rem;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity .2s ease;
+}
+
+.admin-modal-backdrop.is-open {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.admin-modal {
+ background: var(--surface);
+ border: 1px solid var(--rule);
+ border-radius: 0.25rem;
+ width: 100%;
+ max-width: 36rem;
+ max-height: 90vh;
+ overflow-y: auto;
+ padding: 2rem;
+ box-shadow: 0 1.5rem 3rem rgba(0, 0, 0, 0.6);
+ transform: translateY(0.75rem);
+ transition: transform .2s ease;
+}
+
+.admin-modal-backdrop.is-open .admin-modal {
+ transform: translateY(0);
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1.5rem;
+ padding-bottom: 0.75rem;
+ border-bottom: 1px solid var(--rule);
+}
+
+.modal-header h2 {
+ font-size: 1.35rem;
+ color: var(--gold-bright);
+ margin: 0;
+}
+
+.modal-close {
+ background: transparent;
+ border: none;
+ color: var(--ink-muted);
+ font-size: 1.5rem;
+ cursor: pointer;
+ padding: 0.25rem;
+ line-height: 1;
+ text-decoration: none;
+ transition: color .15s ease;
+}
+
+.modal-close:hover {
+ color: var(--gold-bright);
+}
diff --git a/src/scss/components/_announcement-banner.scss b/src/scss/components/_announcement-banner.scss
new file mode 100644
index 0000000..cb8ea52
--- /dev/null
+++ b/src/scss/components/_announcement-banner.scss
@@ -0,0 +1,71 @@
+/* ---------- announcement banner ---------- */
+.announcement-banner {
+ background: var(--ground-2);
+ border-bottom: 1px solid var(--rule);
+ padding: 0.75rem 0;
+ font-size: 0.88rem;
+ position: relative;
+ z-index: 40;
+}
+
+.announcement-inner {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+ text-align: center;
+}
+
+.announcement-tag {
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ font-weight: 600;
+ padding: 0.2rem 0.55rem;
+ border-radius: 0.125rem;
+}
+
+.announcement-sep {
+ color: var(--gold-dim);
+ user-select: none;
+}
+
+.announcement-text {
+ color: var(--ink);
+ font-weight: 400;
+}
+
+/* banner theme styles */
+.banner-holiday {
+ background: linear-gradient(90deg, rgba(32, 48, 38, 0.9), rgba(15, 12, 10, 0.95), rgba(32, 48, 38, 0.9));
+ border-bottom-color: var(--rule);
+}
+
+.banner-holiday .announcement-tag {
+ background: rgba(201, 163, 92, 0.18);
+ border: 1px solid var(--gold-dim);
+ color: var(--gold-bright);
+}
+
+.banner-special {
+ background: linear-gradient(90deg, rgba(43, 35, 25, 0.9), rgba(15, 12, 10, 0.95), rgba(43, 35, 25, 0.9));
+ border-bottom-color: var(--gold-dim);
+}
+
+.banner-special .announcement-tag {
+ background: var(--gold);
+ color: var(--ground-2);
+}
+
+.banner-closure {
+ background: linear-gradient(90deg, rgba(80, 24, 20, 0.85), rgba(15, 12, 10, 0.95), rgba(80, 24, 20, 0.85));
+ border-bottom-color: rgba(200, 60, 50, 0.4);
+}
+
+.banner-closure .announcement-tag {
+ background: rgba(220, 60, 50, 0.25);
+ border: 1px solid rgba(220, 80, 70, 0.6);
+ color: #ffb4b4;
+}
diff --git a/src/scss/components/_buttons.scss b/src/scss/components/_buttons.scss
new file mode 100644
index 0000000..dd5a17d
--- /dev/null
+++ b/src/scss/components/_buttons.scss
@@ -0,0 +1,90 @@
+.hero-ctas {
+ margin-top: 2.5rem;
+ display: flex;
+ justify-content: center;
+ gap: 0.9rem;
+ flex-wrap: wrap;
+}
+
+.btn {
+ display: inline-flex;
+ align-items: center;
+ /* Centers label text horizontally too — matters once a button is
+ stretched wider than its content (e.g. action-stack children filling
+ a fixed-width column) or wraps to multiple lines; a no-op otherwise,
+ so every button gets this instead of one-off overrides per case. */
+ justify-content: center;
+ gap: 0.5rem;
+ padding: 0.8rem 1.5rem;
+ font-family: var(--font-mono);
+ font-size: 0.8rem;
+ letter-spacing: 0.05em;
+ text-decoration: none;
+ border-radius: 0.125rem;
+ transition: transform .15s ease, background .15s ease, border-color .15s ease;
+}
+
+.btn:hover {
+ transform: translateY(-1px);
+}
+
+.btn:disabled {
+ opacity: 0.35;
+ cursor: not-allowed;
+ transform: none;
+}
+
+/* :where() keeps this native-button reset at zero specificity, so
+ modifier classes like .btn-primary's background can actually win
+ instead of losing to button.btn's higher (0,1,1) specificity */
+:where(button.btn) {
+ cursor: pointer;
+ background: transparent;
+}
+
+.btn-primary {
+ background: linear-gradient(180deg, var(--gold-bright), var(--gold));
+ color: var(--ground-2);
+ font-weight: 500;
+ border: none;
+}
+
+.btn-primary:hover {
+ background: linear-gradient(180deg, var(--gold-bright), var(--gold-bright));
+}
+
+.btn-outline {
+ border: 1px solid var(--gold-dim);
+ color: var(--gold-bright);
+}
+
+/* .nav-cta mirrors this hover treatment */
+.btn-outline:hover,
+.nav-cta:hover {
+ border-color: var(--gold);
+ background: var(--surface);
+}
+
+.btn-sm {
+ padding: 0.4rem 0.8rem;
+ font-size: 0.75rem;
+ line-height: 1.2;
+}
+
+/* Single-glyph reorder buttons (↑ / ↓) — narrower than .btn-sm's text
+ padding since there's no label to give it room to breathe. */
+.btn-icon {
+ padding-inline: 0.55rem;
+}
+
+.btn-danger {
+ background: var(--danger-bg);
+ border: 1px solid rgba(220, 80, 60, 0.5);
+ color: var(--danger-text);
+}
+
+.btn-danger:hover {
+ background: rgba(220, 60, 50, 0.3);
+ border-color: #ff6e6e;
+ color: #fff;
+}
diff --git a/src/scss/components/_cards.scss b/src/scss/components/_cards.scss
new file mode 100644
index 0000000..b723337
--- /dev/null
+++ b/src/scss/components/_cards.scss
@@ -0,0 +1,88 @@
+/* shared surface-panel look: also used by .card, .login-card, .panel below */
+.stat-card,
+.card,
+.login-card,
+.panel,
+.menu-category {
+ background: var(--surface);
+ border: 1px solid var(--rule);
+}
+
+.stat-card {
+ padding: 2rem;
+ border-radius: 0.2rem;
+}
+
+.stat-card .eyebrow {
+ display: block;
+ margin-bottom: 1.1rem;
+}
+
+.stat-big {
+ font-family: var(--font-serif);
+ font-weight: 600;
+ font-style: italic;
+ font-size: 3rem;
+ color: var(--gold-bright);
+ line-height: 1;
+}
+
+.stat-card .stat-label {
+ margin-top: 0.4rem;
+ font-size: 0.92rem;
+ color: var(--ink-muted);
+}
+
+.stat-source {
+ margin-top: 1.5rem;
+ font-family: var(--font-mono);
+ font-size: 0.7rem;
+ color: var(--gold-dim);
+ letter-spacing: 0.05em;
+}
+
+.card {
+ border-radius: 0.2rem;
+ padding: 2.25rem;
+}
+
+.card h3 {
+ font-size: 1.3rem;
+ color: var(--gold-bright);
+ margin-bottom: 1.4rem;
+}
+
+.card .note {
+ margin-top: 1.25rem;
+ font-size: 0.88rem;
+ color: var(--ink-muted);
+}
+
+.card .note strong {
+ color: var(--ink);
+ font-weight: 600;
+}
+
+.panel {
+ border-radius: 0.25rem;
+ padding: 2rem;
+ margin-bottom: 2rem;
+}
+
+.panel h2 {
+ font-size: 1.35rem;
+ color: var(--gold-bright);
+ margin-bottom: 1.25rem;
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+}
+
+.panel-head {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
+ gap: 0.75rem;
+ margin-bottom: 1.5rem;
+}
diff --git a/src/scss/components/_dividers.scss b/src/scss/components/_dividers.scss
new file mode 100644
index 0000000..66b28b9
--- /dev/null
+++ b/src/scss/components/_dividers.scss
@@ -0,0 +1,34 @@
+/* ---------- knot mark ---------- */
+.knot {
+ display: block;
+ width: 1.75rem;
+ height: 1.75rem;
+ object-fit: contain;
+ flex-shrink: 0;
+}
+
+.rule-row .knot {
+ width: 1.375rem;
+ height: 1.375rem;
+ opacity: 0.9;
+}
+
+.footer-brand .knot {
+ width: 1.625rem;
+ height: 1.625rem;
+}
+
+/* ---------- brass rule divider ---------- */
+.rule-row {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ max-width: 13.75rem;
+ margin-inline: auto;
+}
+
+.rule-row .line {
+ height: 1px;
+ flex: 1;
+ background: linear-gradient(90deg, transparent, var(--gold-dim), transparent);
+}
diff --git a/src/scss/components/_forms.scss b/src/scss/components/_forms.scss
new file mode 100644
index 0000000..6917675
--- /dev/null
+++ b/src/scss/components/_forms.scss
@@ -0,0 +1,153 @@
+.form-group {
+ margin-bottom: 1.25rem;
+}
+
+.form-group.full-width {
+ grid-column: 1 / -1;
+}
+
+.form-group label {
+ display: block;
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--gold);
+ margin-bottom: 0.45rem;
+}
+
+.field-hint {
+ font-size: 0.78rem;
+}
+
+.form-control,
+.form-input {
+ width: 100%;
+ background: var(--ground-2);
+ border: 1px solid var(--rule);
+ color: var(--ink);
+ font-family: var(--font-sans);
+ font-size: 0.95rem;
+ padding: 0.7rem 0.85rem;
+ border-radius: 0.125rem;
+ box-sizing: border-box;
+ outline: none;
+ transition: border-color .15s ease;
+}
+
+.form-control:focus,
+.form-input:focus {
+ border-color: var(--gold-bright);
+}
+
+textarea.form-control {
+ resize: vertical;
+ min-height: 5.5rem;
+ line-height: 1.5;
+}
+
+.error-msg,
+.alert-error {
+ background: var(--danger-bg);
+ border: 1px solid rgba(220, 80, 60, 0.4);
+ color: var(--danger-text);
+ padding: 0.85rem 1.1rem;
+ font-size: 0.88rem;
+ border-radius: 0.125rem;
+ margin-bottom: 1.25rem;
+}
+
+.form-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1.5rem;
+}
+
+@media (max-width: 47.5em) {
+ .form-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+.form-actions {
+ display: flex;
+ gap: 0.75rem;
+ align-items: center;
+ margin-top: 1.75rem;
+}
+
+.checkbox-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ margin-top: 0.5rem;
+}
+
+.checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ font-size: 0.92rem;
+ color: var(--ink);
+ cursor: pointer;
+}
+
+.checkbox-label-lg {
+ font-size: 1.05rem;
+ font-weight: 500;
+}
+
+.checkbox-label input[type="checkbox"] {
+ width: 1.15rem;
+ height: 1.15rem;
+ accent-color: var(--gold);
+ cursor: pointer;
+}
+
+/* password toggle input wrapper */
+.password-input-wrap {
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.password-input-wrap .form-control,
+.password-input-wrap .form-input {
+ padding-right: 2.75rem;
+}
+
+.password-toggle-btn {
+ position: absolute;
+ right: 0.5rem;
+ top: 50%;
+ transform: translateY(-50%);
+ background: transparent;
+ border: none;
+ color: var(--ink-muted);
+ cursor: pointer;
+ padding: 0.35rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 0.125rem;
+ transition: color .15s ease;
+}
+
+.password-toggle-btn:hover {
+ color: var(--gold-bright);
+}
+
+.password-toggle-btn svg {
+ width: 1.15rem;
+ height: 1.15rem;
+}
+
+.alert-success {
+ background: rgba(51, 128, 90, 0.15);
+ border: 1px solid var(--green);
+ color: #9fe2bf;
+ padding: 0.85rem 1.1rem;
+ font-size: 0.88rem;
+ border-radius: 0.125rem;
+ margin-bottom: 1.25rem;
+}
diff --git a/src/scss/components/_social-row.scss b/src/scss/components/_social-row.scss
new file mode 100644
index 0000000..0248b2f
--- /dev/null
+++ b/src/scss/components/_social-row.scss
@@ -0,0 +1,32 @@
+.social-row {
+ margin-top: 1.5rem;
+ display: flex;
+ gap: 0.9rem;
+}
+
+.social-row a {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 2.375rem;
+ height: 2.375rem;
+ border: 1px solid var(--rule);
+ border-radius: 0.125rem;
+ color: var(--ink-muted);
+ text-decoration: none;
+ transition: border-color .15s ease, color .15s ease;
+}
+
+.social-row a:hover {
+ border-color: var(--gold);
+ color: var(--gold-bright);
+}
+
+.social-row svg {
+ width: 1.0625rem;
+ height: 1.0625rem;
+}
+
+footer .social-row {
+ margin-top: 1rem;
+}
diff --git a/src/scss/components/_status-badge.scss b/src/scss/components/_status-badge.scss
new file mode 100644
index 0000000..8070e55
--- /dev/null
+++ b/src/scss/components/_status-badge.scss
@@ -0,0 +1,69 @@
+.status-badge {
+ display: inline-flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 0.4rem;
+ font-family: var(--font-mono);
+ font-size: 0.72rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ padding: 0.25rem 0.6rem;
+ border-radius: 0.125rem;
+ white-space: nowrap;
+ line-height: 1.2;
+}
+
+.status-badge.active {
+ background: rgba(51, 80, 63, 0.4);
+ border: 1px solid var(--green);
+ color: #9fe2bf;
+}
+
+.status-badge.inactive {
+ background: rgba(138, 115, 74, 0.2);
+ border: 1px solid var(--rule);
+ color: var(--ink-muted);
+}
+
+/* Both the menu item and announcement status badges double as their own
+ toggle control — a plain would otherwise pick up the
+ browser's default chrome underneath the badge's own background/border.
+ Scoped to the element type so it's a no-op wherever a status-badge is
+ still a plain read-only . Width is fixed per context below
+ (each page's label set is a different length) so toggling never
+ changes the badge's — and therefore the Status column's — width and
+ shoves every other column sideways. */
+button.status-badge {
+ appearance: none;
+ -webkit-appearance: none;
+ cursor: pointer;
+ justify-content: center;
+ transition: opacity .15s ease;
+}
+
+button.status-badge:hover {
+ opacity: 0.8;
+}
+
+button.status-badge:disabled {
+ cursor: not-allowed;
+ opacity: 0.5;
+}
+
+/* "● Available" is the longest menu item label, ~6.7rem measured. */
+.item-row button.status-badge {
+ width: 7rem;
+}
+
+/* "⏳ Scheduled" is the longest announcement label, ~7rem measured. */
+.ann-row button.status-badge {
+ width: 7.25rem;
+}
+
+/* "○ Disabled" is the longer of the two user labels. Not scoped to
+ button.status-badge like the two above — your own row's badge is a
+ plain non-interactive span (see users.php), and it still needs to line
+ up in the same column. */
+.user-row .status-badge {
+ width: 7rem;
+}
diff --git a/src/scss/layout/_admin-shell.scss b/src/scss/layout/_admin-shell.scss
new file mode 100644
index 0000000..8e47f55
--- /dev/null
+++ b/src/scss/layout/_admin-shell.scss
@@ -0,0 +1,106 @@
+.admin-nav {
+ background: var(--ground-2);
+ border-bottom: 1px solid var(--rule);
+ padding: 0.85rem 0;
+}
+
+.admin-nav-inner {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.admin-brand {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ text-decoration: none;
+ font-family: var(--font-serif);
+ font-size: 1.15rem;
+ color: var(--ink);
+}
+
+.admin-user {
+ display: flex;
+ align-items: center;
+ gap: 1.25rem;
+ font-family: var(--font-mono);
+ font-size: 0.82rem;
+ color: var(--ink-muted);
+}
+
+.admin-user a {
+ color: var(--gold-bright);
+ text-decoration: none;
+}
+
+.admin-user a:hover {
+ text-decoration: underline;
+}
+
+@media (max-width: 47.5em) {
+ .admin-nav-inner {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.75rem;
+ }
+
+ /* two classes each, so these reliably outrank .admin-nav-links's and
+ .admin-user's own base-rule gap regardless of source order */
+ .admin-nav-inner .admin-nav-links,
+ .admin-nav-inner .admin-user {
+ flex-wrap: wrap;
+ gap: 0.5rem 1rem;
+ }
+}
+
+.dashboard-layout {
+ padding-block: 2.5rem 4rem;
+}
+
+.dash-header {
+ margin-bottom: 2rem;
+}
+
+.dash-header h1 {
+ margin-top: 0.4rem;
+ font-size: 2rem;
+ color: var(--ink);
+}
+
+/* admin nav links */
+.admin-nav-links {
+ display: flex;
+ gap: 1.25rem;
+ align-items: center;
+}
+
+.admin-nav-links a {
+ font-family: var(--font-mono);
+ font-size: 0.82rem;
+ letter-spacing: 0.05em;
+ text-decoration: none;
+ color: var(--ink-muted);
+ padding: 0.35rem 0.65rem;
+ border-radius: 0.125rem;
+ transition: color .15s ease, background .15s ease;
+}
+
+.admin-nav-links a:hover {
+ color: var(--gold-bright);
+ background: var(--surface);
+}
+
+.admin-nav-links a.active {
+ color: var(--gold-bright);
+ background: var(--surface);
+ border-bottom: 1px solid var(--gold);
+}
+
+.admin-toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ align-items: center;
+ margin-bottom: 2rem;
+}
diff --git a/src/scss/layout/_footer.scss b/src/scss/layout/_footer.scss
new file mode 100644
index 0000000..092a09b
--- /dev/null
+++ b/src/scss/layout/_footer.scss
@@ -0,0 +1,82 @@
+/* footer */
+footer {
+ background: var(--ground-2);
+ border-top: 1px solid var(--rule);
+ padding-block: 3.5rem 2.5rem;
+}
+
+.footer-grid {
+ display: grid;
+ grid-template-columns: 1.2fr 1fr 1fr;
+ gap: 2.5rem;
+}
+
+.footer-brand {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ text-decoration: none;
+}
+
+.footer-brand span {
+ font-family: var(--font-serif);
+ font-weight: 600;
+ color: var(--ink);
+ font-size: 1rem;
+}
+
+footer .col-title {
+ font-family: var(--font-mono);
+ font-size: 0.72rem;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--gold-dim);
+ margin-bottom: 0.9rem;
+}
+
+footer address {
+ margin-top: 1rem;
+ font-style: normal;
+ color: var(--ink-muted);
+ font-size: 0.92rem;
+ line-height: 1.7;
+}
+
+footer .hours-mini {
+ font-family: var(--font-mono);
+ font-size: 0.85rem;
+ color: var(--ink-muted);
+ font-variant-numeric: tabular-nums;
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+}
+
+footer a.email {
+ color: var(--gold-bright);
+ text-decoration: none;
+ font-size: 0.92rem;
+}
+
+footer a.email:hover {
+ text-decoration: underline;
+}
+
+.footer-bottom {
+ margin-top: 3rem;
+ padding-top: 1.75rem;
+ border-top: 1px solid var(--rule);
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ font-size: 0.78rem;
+ color: var(--ink-muted);
+}
+
+@media (max-width: 45em) {
+ .footer-grid {
+ grid-template-columns: 1fr;
+ gap: 2rem;
+ }
+}
diff --git a/src/scss/layout/_header.scss b/src/scss/layout/_header.scss
new file mode 100644
index 0000000..db0bd17
--- /dev/null
+++ b/src/scss/layout/_header.scss
@@ -0,0 +1,128 @@
+/* ---------- header / nav ---------- */
+header.site {
+ position: sticky;
+ top: 0;
+ z-index: 50;
+ background: rgba(15, 12, 10, 0.86);
+ backdrop-filter: saturate(140%) blur(0.5rem);
+ border-bottom: 1px solid var(--rule);
+}
+
+.nav-inner {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ padding-block: 0.75rem;
+}
+
+.brand {
+ display: flex;
+ align-items: center;
+ gap: 0.65rem;
+ text-decoration: none;
+ font-family: var(--font-serif);
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ font-size: 1.05rem;
+ color: var(--ink);
+}
+
+nav.links {
+ display: flex;
+ align-items: center;
+ gap: 1.75rem;
+}
+
+nav.links a {
+ text-decoration: none;
+ font-size: 0.92rem;
+ letter-spacing: 0.03em;
+ color: var(--ink-muted);
+ padding: 0.4rem 0.2rem;
+ transition: color .15s ease;
+}
+
+nav.links a:hover {
+ color: var(--gold-bright);
+}
+
+nav.links a.active {
+ color: var(--gold-bright);
+}
+
+.nav-cta {
+ display: inline-flex;
+ align-items: center;
+ border: 1px solid var(--gold-dim);
+ color: var(--gold-bright) !important;
+ padding: 0.45rem 0.9rem;
+ border-radius: 0.125rem;
+ font-family: var(--font-mono);
+ font-size: 0.75rem !important;
+ letter-spacing: 0.06em;
+}
+
+/* mobile nav toggle: hidden on desktop, shown only under the nav.links
+ breakpoint below. Icon swap is driven by the button's own
+ aria-expanded state rather than a second JS-toggled class. */
+.nav-toggle {
+ display: none;
+ background: transparent;
+ border: none;
+ padding: 0.5rem;
+ margin: -0.5rem;
+ color: var(--ink);
+ cursor: pointer;
+ align-items: center;
+ justify-content: center;
+}
+
+.nav-toggle svg {
+ width: 1.5rem;
+ height: 1.5rem;
+}
+
+.nav-toggle .icon-close {
+ display: none;
+}
+
+.nav-toggle[aria-expanded="true"] .icon-menu {
+ display: none;
+}
+
+.nav-toggle[aria-expanded="true"] .icon-close {
+ display: block;
+}
+
+@media (max-width: 47.5em) {
+ .nav-toggle {
+ display: inline-flex;
+ }
+
+ .nav-cta {
+ display: none;
+ }
+
+ nav.links {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 0;
+ background: var(--ground-2);
+ border-bottom: 1px solid var(--rule);
+ padding: 0.5rem 0;
+ }
+
+ nav.links.is-open {
+ display: flex;
+ }
+
+ nav.links a {
+ padding: 0.9rem 1.5rem;
+ }
+}
diff --git a/src/scss/pages/_admin-hours.scss b/src/scss/pages/_admin-hours.scss
new file mode 100644
index 0000000..06e30c2
--- /dev/null
+++ b/src/scss/pages/_admin-hours.scss
@@ -0,0 +1,143 @@
+/* ---------- admin: pub hours ---------- */
+/* Same subgrid technique as the item/ann/user lists above (see the
+ comment near .item-grid-wrap), but with no grid-template-areas: every
+ row's five fields sit in the same left-to-right order on both desktop
+ and mobile, so plain column order is enough — nothing needs to reflow. */
+.hours-grid-wrap {
+ display: grid;
+ grid-template-columns: auto auto 1fr 1fr 1fr;
+ column-gap: 0.75rem;
+ margin-top: 1rem;
+}
+
+.hours-day-rows {
+ display: contents;
+}
+
+.hours-grid-head,
+.hours-day-row {
+ display: grid;
+ grid-column: 1 / -1;
+ grid-template-columns: subgrid;
+ align-items: center;
+}
+
+.hours-grid-head {
+ padding: 0 0.75rem 0.85rem;
+ border-bottom: 1px solid var(--rule);
+ font-family: var(--font-mono);
+ font-size: 0.72rem;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: var(--gold);
+ font-weight: 500;
+}
+
+.hours-day-row {
+ padding: 0.85rem 0.75rem;
+ border-bottom: 1px solid var(--rule);
+}
+
+.hours-day-row:last-child {
+ border-bottom: none;
+}
+
+.hours-day-row .form-control {
+ min-width: 7rem;
+}
+
+.hours-day-row .day-time-field:disabled {
+ opacity: 0.4;
+}
+
+@media (max-width: 47.5em) {
+ .hours-grid-wrap {
+ grid-template-columns: 1fr;
+ }
+
+ .hours-grid-head {
+ display: none;
+ }
+
+ .hours-day-row {
+ grid-template-columns: 1fr;
+ row-gap: 0.5rem;
+ border: 1px solid var(--rule);
+ border-radius: 0.25rem;
+ padding: 0.9rem 1rem;
+ margin-bottom: 0.85rem;
+ }
+
+ .hours-day-row:last-child {
+ margin-bottom: 0;
+ }
+
+ .hours-day-row [data-label]::before {
+ content: attr(data-label);
+ display: block;
+ font-family: var(--font-mono);
+ font-size: 0.68rem;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ color: var(--gold);
+ margin-bottom: 0.3rem;
+ }
+
+ .hours-day-row .form-control {
+ width: 100%;
+ }
+}
+
+/* Live preview panel: mimics the public site's Visit card and footer
+ mini-summary without depending on their .card/footer ancestor scoping
+ (this lives inside an admin .panel, which already shares that same
+ surface look — nesting the real classes here would just blend in). */
+.hours-preview {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1.5rem;
+}
+
+.hours-preview-card,
+.hours-preview-footer {
+ background: var(--ground-2);
+ border: 1px solid var(--rule);
+ border-radius: 0.2rem;
+ padding: 1.5rem;
+}
+
+.hours-preview-card .note {
+ margin-top: 1.25rem;
+ font-size: 0.88rem;
+ color: var(--ink-muted);
+}
+
+.hours-preview-card .note strong {
+ color: var(--ink);
+ font-weight: 600;
+}
+
+.hours-preview-footer .col-title {
+ font-family: var(--font-mono);
+ font-size: 0.72rem;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--gold-dim);
+ margin-bottom: 0.9rem;
+}
+
+.hours-preview-footer .hours-mini {
+ font-family: var(--font-mono);
+ font-size: 0.85rem;
+ color: var(--ink-muted);
+ font-variant-numeric: tabular-nums;
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+}
+
+@media (max-width: 47.5em) {
+ .hours-preview {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/scss/pages/_admin-login.scss b/src/scss/pages/_admin-login.scss
new file mode 100644
index 0000000..c3fe623
--- /dev/null
+++ b/src/scss/pages/_admin-login.scss
@@ -0,0 +1,61 @@
+/* ---------- admin / management styles ---------- */
+.admin-login-body {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+}
+
+.login-card {
+ border-radius: 0.25rem;
+ padding: 2.5rem 2rem;
+ width: 100%;
+ max-width: 25rem;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.4);
+}
+
+.login-header {
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+.login-header img {
+ width: 2rem;
+ height: 2rem;
+ margin: 0 auto 0.75rem;
+}
+
+.login-header h1 {
+ font-size: 1.5rem;
+ color: var(--gold-bright);
+}
+
+.login-header p {
+ font-family: var(--font-mono);
+ font-size: 0.72rem;
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ color: var(--ink-muted);
+ margin-top: 0.35rem;
+}
+
+.login-btn {
+ width: 100%;
+ justify-content: center;
+ margin-top: 0.5rem;
+}
+
+.back-link {
+ display: block;
+ text-align: center;
+ margin-top: 1.5rem;
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ color: var(--ink-muted);
+ text-decoration: none;
+}
+
+.back-link:hover {
+ color: var(--gold-bright);
+}
diff --git a/src/scss/pages/_admin-menu.scss b/src/scss/pages/_admin-menu.scss
new file mode 100644
index 0000000..737b8c1
--- /dev/null
+++ b/src/scss/pages/_admin-menu.scss
@@ -0,0 +1,66 @@
+/* two classes to reliably outrank .panel h2's margin-bottom above */
+.category-panel .cat-name-heading {
+ margin-bottom: 0.25rem;
+ grid-area: name;
+}
+
+.cat-desc-text {
+ font-size: 0.88rem;
+}
+
+.category-panel .cat-desc-text {
+ grid-area: desc;
+}
+
+/* Category panel-head as a grid: title on its own top row, description
+ and the move/edit/delete buttons sharing the row below with the
+ buttons pinned to the far end (matches the desktop arrangement this
+ replaced — see the mobile override for how that changes below 760px). */
+.category-panel .panel-head {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ grid-template-areas:
+ "name actions"
+ "desc actions";
+ align-items: center;
+ column-gap: 0.75rem;
+}
+
+.category-panel .panel-head .row-actions {
+ grid-area: actions;
+}
+
+/* Wraps the item name and its optional badge (reusing the public menu's
+ .menu-item-badge pill) so they sit on one inline-flowing line instead
+ of giving the badge its own column. */
+.item-name-row {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
+.item-desc-cell {
+ font-size: 0.82rem;
+ margin-top: 0.2rem;
+}
+
+.item-price-cell {
+ color: var(--gold-bright);
+ font-weight: 500;
+}
+
+/* mobile reflow for the category header — shares the breakpoint and
+ reflow approach used by the item/announcement/user list grids in
+ admin-data-grid, but this one rule is specific to the menu editor's
+ category panel, so it lives here instead of there. */
+@media (max-width: 47.5em) {
+ .category-panel .panel-head {
+ grid-template-columns: 1fr;
+ grid-template-areas:
+ "name"
+ "desc"
+ "actions";
+ row-gap: 0.5rem;
+ }
+}
diff --git a/src/scss/pages/_home.scss b/src/scss/pages/_home.scss
new file mode 100644
index 0000000..24c3384
--- /dev/null
+++ b/src/scss/pages/_home.scss
@@ -0,0 +1,392 @@
+.hero-brand {
+ margin: 0;
+ line-height: 0;
+}
+
+.hero-logo {
+ display: block;
+ width: clamp(17.5rem, 28vw, 31rem);
+ max-width: 100%;
+ height: auto;
+ margin: 0 auto 1.75rem;
+}
+
+/* hours card alert callout */
+.hours-alert {
+ margin-bottom: 1.25rem;
+ padding: 0.85rem 1rem;
+ border-radius: 0.2rem;
+ font-size: 0.88rem;
+ line-height: 1.45;
+}
+
+.hours-alert strong {
+ display: block;
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ margin-bottom: 0.25rem;
+}
+
+.hours-alert.alert-holiday {
+ background: rgba(51, 80, 63, 0.25);
+ border: 1px solid rgba(201, 163, 92, 0.3);
+ color: var(--ink);
+}
+
+.hours-alert.alert-holiday strong {
+ color: var(--gold-bright);
+}
+
+.hours-alert.alert-special {
+ background: rgba(43, 35, 25, 0.6);
+ border: 1px solid var(--gold-dim);
+ color: var(--ink);
+}
+
+.hours-alert.alert-special strong {
+ color: var(--gold-bright);
+}
+
+.hours-alert.alert-closure {
+ background: rgba(80, 24, 20, 0.3);
+ border: 1px solid rgba(220, 80, 70, 0.5);
+ color: #ffdfdf;
+}
+
+.hours-alert.alert-closure strong {
+ color: #ffb4b4;
+}
+
+/* ---------- hero ---------- */
+.hero {
+ position: relative;
+ min-height: calc(100vh - 4.5rem);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding-block: clamp(4.5rem, 8vh, 7.5rem);
+ text-align: center;
+ overflow: hidden;
+ background: var(--ground);
+ /* fallback if brick-texture.png fails to load */
+}
+
+.hero::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ z-index: 1;
+ opacity: 0.6;
+ background:
+ linear-gradient(to bottom, rgba(0, 0, 0, 0.12), transparent 22%, transparent 78%, rgba(0, 0, 0, 0.18));
+ pointer-events: none;
+}
+
+.hero-media {
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+ overflow: hidden;
+ background-image: url('./assets/brick-texture.png');
+ background-repeat: repeat;
+}
+
+.hero-scrim {
+ position: absolute;
+ inset: 0;
+ background:
+ linear-gradient(180deg, rgba(15, 12, 10, 0.35) 0%, rgba(15, 12, 10, 0.2) 45%, rgba(15, 12, 10, 0.6) 100%),
+ radial-gradient(ellipse 60% 48% at 50% 18%, rgba(201, 163, 92, 0.16), transparent 58%);
+}
+
+.hero-inner {
+ position: relative;
+ z-index: 2;
+}
+
+.hero .eyebrow {
+ margin-bottom: 1rem;
+}
+
+.hero h1 {
+ font-size: clamp(3.1rem, 9vw, 6.2rem);
+ color: var(--gold-bright);
+ letter-spacing: -0.01em;
+}
+
+.hero .sub {
+ font-family: var(--font-serif);
+ font-style: italic;
+ font-weight: 400;
+ font-size: clamp(1.15rem, 2.6vw, 1.55rem);
+ color: var(--ink-muted);
+ margin-top: 0.35rem;
+}
+
+.tagline-row {
+ margin-top: 2rem;
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+ /* column-gap carries the spacing now (was 0 — the dot's own margin
+ used to be the only thing separating the phrases). Needs to be a
+ real flex gap, not a margin on the dot, so removing a hidden dot
+ from flow (see .wrap-sep-hidden) still leaves normal spacing
+ between the two phrases instead of running them together. */
+ gap: 0.6rem 0.7rem;
+ font-family: var(--font-mono);
+ font-size: 0.82rem;
+ letter-spacing: 0.04em;
+ color: var(--ink-muted);
+}
+
+/* Real element instead of a ::after pseudo-element — needs to be a
+ measurable, independently-toggleable DOM node for the wrap-aware
+ script in main.js to hide it when its neighbors wrap apart. No margin
+ of its own: the column-gap above already provides 0.7rem on each
+ side of it (matching the old margin-inline's total spacing exactly),
+ and still applies directly between the two phrases whenever the dot
+ itself is removed from flow. */
+.tag-sep {
+ color: var(--gold-dim);
+ user-select: none;
+}
+
+.section-head {
+ margin-bottom: 2.75rem;
+}
+
+.section-head h2 {
+ font-size: clamp(1.9rem, 3.6vw, 2.6rem);
+ color: var(--ink);
+ margin-top: 0.5rem;
+}
+
+/* about band */
+.about-grid {
+ display: grid;
+ grid-template-columns: 1.3fr 1fr;
+ gap: 3.5rem;
+ align-items: start;
+}
+
+.about-grid p+p {
+ margin-top: 1rem;
+}
+
+.about-grid h2 {
+ margin-top: 0.6rem;
+}
+
+.about-grid h2 + .lede {
+ margin-top: 1.5rem;
+}
+
+.about-grid .lede {
+ max-width: 56ch;
+ color: var(--ink);
+ font-size: 1.05rem;
+}
+
+@media (max-width: 51.25em) {
+ .about-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* hours / visit band */
+.visit {
+ background: var(--ground-2);
+}
+
+.visit-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1.75rem;
+}
+
+.hours-list {
+ margin: 0;
+ padding: 0;
+ font-family: var(--font-mono);
+ font-variant-numeric: tabular-nums;
+ font-size: 0.92rem;
+}
+
+.hours-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.55rem 0;
+ border-bottom: 1px solid var(--rule);
+}
+
+.hours-row:last-child {
+ border-bottom: none;
+}
+
+.hours-row dt {
+ margin: 0;
+ color: var(--ink-muted);
+ letter-spacing: 0.04em;
+ font-weight: normal;
+}
+
+.hours-row dd {
+ margin: 0;
+ text-align: right;
+ color: var(--ink);
+}
+
+.find-us .pin {
+ width: 2.125rem;
+ height: 2.125rem;
+ margin-bottom: 1rem;
+}
+
+.find-us address {
+ font-style: normal;
+ font-size: 1rem;
+ color: var(--ink);
+}
+
+.find-us .contact-list {
+ margin-top: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.6rem;
+}
+
+.find-us .contact-list a {
+ text-decoration: none;
+ color: var(--gold-bright);
+ font-size: 0.92rem;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ width: fit-content;
+ border-bottom: 1px solid transparent;
+}
+
+.find-us .contact-list a:hover {
+ border-bottom-color: var(--gold-dim);
+}
+
+@media (max-width: 45em) {
+ .visit-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* live music band */
+.music {
+ background: linear-gradient(180deg, var(--green-deep), var(--green));
+ color: var(--ink);
+ text-align: center;
+}
+
+.music-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 3.5rem;
+ align-items: center;
+ text-align: left;
+ margin-top: 1.5rem;
+}
+
+.music-content {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+.music-photo-wrap {
+ position: relative;
+ border-radius: 0.25rem;
+ overflow: hidden;
+ border: 1px solid rgba(201, 163, 92, 0.35);
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.45);
+}
+
+.music-photo {
+ width: 100%;
+ height: auto;
+ display: block;
+ object-fit: cover;
+ aspect-ratio: 4 / 3;
+ object-position: center top;
+}
+
+.music .section-head {
+ margin-bottom: 1.5rem;
+ text-align: left;
+}
+
+.music .section-head .eyebrow {
+ color: var(--gold-bright);
+}
+
+.music p.body {
+ max-width: 58ch;
+ margin: 0;
+ font-size: 1.02rem;
+ color: #e4ddc9;
+ line-height: 1.65;
+}
+
+.music .btn-outline {
+ border-color: rgba(230, 199, 137, 0.45);
+}
+
+.music .hero-ctas {
+ margin-top: 2rem;
+ justify-content: flex-start;
+}
+
+@media (max-width: 50em) {
+ .music-grid {
+ grid-template-columns: 1fr;
+ gap: 2.5rem;
+ text-align: center;
+ }
+
+ .music-content {
+ align-items: center;
+ }
+
+ .music .section-head {
+ text-align: center;
+ }
+
+ .music .hero-ctas {
+ justify-content: center;
+ }
+}
+
+/* menu band */
+.menu-band .container {
+ max-width: 60rem;
+ text-align: center;
+}
+
+.menu-band p.body {
+ font-size: 1.02rem;
+ color: var(--ink-muted);
+ max-width: 56ch;
+ margin: 0 auto;
+}
+
+.menu-band .hero-ctas {
+ margin-top: 2.25rem;
+ justify-content: center;
+}
+
+.menu-band .section-head {
+ text-align: center;
+}
+
+.menu-band .rule-row {
+ margin-bottom: 1.75rem;
+}
diff --git a/src/scss/pages/_menu.scss b/src/scss/pages/_menu.scss
new file mode 100644
index 0000000..cd55ff3
--- /dev/null
+++ b/src/scss/pages/_menu.scss
@@ -0,0 +1,98 @@
+/* public online menu section */
+.menu-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
+ gap: 2.5rem;
+ margin-top: 3rem;
+ text-align: left;
+}
+
+/* background/border come from the shared .panel rule above; no
+ margin-bottom here since these sit in a grid whose own gap spaces them */
+.menu-category {
+ border-radius: 0.25rem;
+ padding: 2rem;
+}
+
+.menu-category-header {
+ margin-bottom: 1.5rem;
+ padding-bottom: 0.75rem;
+ border-bottom: 1px solid var(--rule);
+}
+
+.menu-category-header h3 {
+ font-size: 1.45rem;
+ color: var(--gold-bright);
+}
+
+.menu-category-header p {
+ margin-top: 0.35rem;
+ font-size: 0.88rem;
+ color: var(--ink-muted);
+}
+
+.menu-items-list {
+ display: flex;
+ flex-direction: column;
+ gap: 1.4rem;
+}
+
+.menu-item-row {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.menu-item-top {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ gap: 0.75rem;
+}
+
+.menu-item-name {
+ font-family: var(--font-serif);
+ font-weight: 600;
+ font-size: 1.05rem;
+ color: var(--ink);
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
+.menu-item-price {
+ font-family: var(--font-mono);
+ font-weight: 500;
+ font-size: 0.95rem;
+ color: var(--gold-bright);
+ white-space: nowrap;
+}
+
+.menu-item-desc {
+ font-size: 0.88rem;
+ color: var(--ink-muted);
+ line-height: 1.45;
+}
+
+.menu-item-badge {
+ font-family: var(--font-mono);
+ font-size: 0.65rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ padding: 0.15rem 0.45rem;
+ border-radius: 0.125rem;
+ background: rgba(201, 163, 92, 0.15);
+ border: 1px solid var(--gold-dim);
+ color: var(--gold-bright);
+ font-weight: normal;
+}
+
+/* menu page specific headers & spacing */
+.menu-page-header {
+ padding-block: 4.5rem 2.5rem;
+}
+
+.menu-page-content {
+ padding-bottom: 5.5rem;
+}
diff --git a/src/scss/styles.scss b/src/scss/styles.scss
index dcdc102..4696306 100644
--- a/src/scss/styles.scss
+++ b/src/scss/styles.scss
@@ -1,2168 +1,33 @@
- :root {
- --ground: #171310;
- --ground-2: #0f0c0a;
- --surface: #221c16;
- --surface-2: #2b2319;
- --gold: #c9a35c;
- --gold-bright: #e6c789;
- --gold-dim: #a68b59;
- --green: #33503f;
- --green-deep: #203026;
- --ink: #f1e8d8;
- --ink-muted: #a89a83;
- --rule: #42362590;
-
- --font-serif: 'Fraunces', Georgia, 'Times New Roman', serif;
- --font-mono: 'IBM Plex Mono', ui-monospace, Menlo, monospace;
- --font-sans: 'Karla', ui-sans-serif, system-ui, sans-serif;
-
- --danger-bg: rgba(180, 50, 40, 0.15);
- --danger-text: #ff9d9d;
- }
-
- * {
- box-sizing: border-box;
- }
-
- html {
- scroll-behavior: smooth;
- }
-
- @media (prefers-reduced-motion: reduce) {
- html {
- scroll-behavior: auto;
- }
- }
-
- /* ---------- wrap-aware separators (JS-toggled, see main.js) ----------
- Shared hook used by the announcement banner and hero tagline row: a
- "·" between two pieces of text that can independently wrap onto
- separate lines. No CSS selector exists for "am I on the same line as
- my sibling," so main.js measures and toggles this class instead of a
- fixed breakpoint. Visible by default so a no-JS visitor always sees
- the dot (today's behavior), never hidden by default.
-
- display, not visibility: a hidden separator must take no space, or
- it still gets counted when justify-content: center balances its flex
- line — a lone badge sharing its line with a space-reserving invisible
- dot renders off-center. Safe from the "text merges together" bug that
- originally motivated reserving space: both wrap-sep contexts space
- their siblings with a real flex `gap` (see .announcement-inner,
- .tagline-row) rather than the separator's own margin, so removing it
- from flow still leaves normal spacing behind, never a collision. */
- .wrap-sep-hidden {
- display: none;
- }
-
- body {
- margin: 0;
- background: var(--ground);
- color: var(--ink);
- font-family: var(--font-sans);
- font-size: 1rem;
- line-height: 1.65;
- -webkit-font-smoothing: antialiased;
- }
-
- a {
- color: inherit;
- }
-
- img,
- svg {
- display: block;
- max-width: 100%;
- }
-
- .container {
- width: 100%;
- max-width: 70rem;
- margin: 0 auto;
- padding-inline: 1.5rem;
- }
-
- h1,
- h2,
- h3 {
- font-family: var(--font-serif);
- font-weight: 600;
- line-height: 1.08;
- text-wrap: balance;
- margin: 0;
- }
-
- .eyebrow {
- font-family: var(--font-mono);
- font-size: 0.72rem;
- letter-spacing: 0.22em;
- text-transform: uppercase;
- color: var(--gold);
- }
-
- p {
- margin: 0;
- }
-
- .muted {
- color: var(--ink-muted);
- }
-
- .skip-link {
- position: absolute;
- left: 1rem;
- top: -3rem;
- background: var(--gold);
- color: var(--ground-2);
- padding: 0.6rem 1rem;
- border-radius: 0.125rem;
- font-family: var(--font-mono);
- font-size: 0.85rem;
- transition: top .15s ease;
- z-index: 100;
- }
-
- .skip-link:focus {
- top: 1rem;
- }
-
- :focus-visible {
- outline: 2px solid var(--gold-bright);
- outline-offset: 3px;
- }
-
- /* ---------- knot mark ---------- */
- .knot {
- display: block;
- width: 1.75rem;
- height: 1.75rem;
- object-fit: contain;
- flex-shrink: 0;
- }
-
- .rule-row .knot {
- width: 1.375rem;
- height: 1.375rem;
- opacity: 0.9;
- }
-
- .footer-brand .knot {
- width: 1.625rem;
- height: 1.625rem;
- }
-
- .hero-brand {
- margin: 0;
- line-height: 0;
- }
-
- .hero-logo {
- display: block;
- width: clamp(17.5rem, 28vw, 31rem);
- max-width: 100%;
- height: auto;
- margin: 0 auto 1.75rem;
- }
-
- /* ---------- brass rule divider ---------- */
- .rule-row {
- display: flex;
- align-items: center;
- gap: 1rem;
- max-width: 13.75rem;
- margin-inline: auto;
- }
-
- .rule-row .line {
- height: 1px;
- flex: 1;
- background: linear-gradient(90deg, transparent, var(--gold-dim), transparent);
- }
-
- /* ---------- announcement banner ---------- */
- .announcement-banner {
- background: var(--ground-2);
- border-bottom: 1px solid var(--rule);
- padding: 0.75rem 0;
- font-size: 0.88rem;
- position: relative;
- z-index: 40;
- }
-
- .announcement-inner {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 0.75rem;
- flex-wrap: wrap;
- text-align: center;
- }
-
- .announcement-tag {
- font-family: var(--font-mono);
- font-size: 0.75rem;
- letter-spacing: 0.12em;
- text-transform: uppercase;
- font-weight: 600;
- padding: 0.2rem 0.55rem;
- border-radius: 0.125rem;
- }
-
- .announcement-sep {
- color: var(--gold-dim);
- user-select: none;
- }
-
- .announcement-text {
- color: var(--ink);
- font-weight: 400;
- }
-
- /* banner theme styles */
- .banner-holiday {
- background: linear-gradient(90deg, rgba(32, 48, 38, 0.9), rgba(15, 12, 10, 0.95), rgba(32, 48, 38, 0.9));
- border-bottom-color: var(--rule);
- }
-
- .banner-holiday .announcement-tag {
- background: rgba(201, 163, 92, 0.18);
- border: 1px solid var(--gold-dim);
- color: var(--gold-bright);
- }
-
- .banner-special {
- background: linear-gradient(90deg, rgba(43, 35, 25, 0.9), rgba(15, 12, 10, 0.95), rgba(43, 35, 25, 0.9));
- border-bottom-color: var(--gold-dim);
- }
-
- .banner-special .announcement-tag {
- background: var(--gold);
- color: var(--ground-2);
- }
-
- .banner-closure {
- background: linear-gradient(90deg, rgba(80, 24, 20, 0.85), rgba(15, 12, 10, 0.95), rgba(80, 24, 20, 0.85));
- border-bottom-color: rgba(200, 60, 50, 0.4);
- }
-
- .banner-closure .announcement-tag {
- background: rgba(220, 60, 50, 0.25);
- border: 1px solid rgba(220, 80, 70, 0.6);
- color: #ffb4b4;
- }
-
- /* hours card alert callout */
- .hours-alert {
- margin-bottom: 1.25rem;
- padding: 0.85rem 1rem;
- border-radius: 0.2rem;
- font-size: 0.88rem;
- line-height: 1.45;
- }
-
- .hours-alert strong {
- display: block;
- font-family: var(--font-mono);
- font-size: 0.75rem;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- margin-bottom: 0.25rem;
- }
-
- .hours-alert.alert-holiday {
- background: rgba(51, 80, 63, 0.25);
- border: 1px solid rgba(201, 163, 92, 0.3);
- color: var(--ink);
- }
-
- .hours-alert.alert-holiday strong {
- color: var(--gold-bright);
- }
-
- .hours-alert.alert-special {
- background: rgba(43, 35, 25, 0.6);
- border: 1px solid var(--gold-dim);
- color: var(--ink);
- }
-
- .hours-alert.alert-special strong {
- color: var(--gold-bright);
- }
-
- .hours-alert.alert-closure {
- background: rgba(80, 24, 20, 0.3);
- border: 1px solid rgba(220, 80, 70, 0.5);
- color: #ffdfdf;
- }
-
- .hours-alert.alert-closure strong {
- color: #ffb4b4;
- }
-
- /* ---------- header / nav ---------- */
- header.site {
- position: sticky;
- top: 0;
- z-index: 50;
- background: rgba(15, 12, 10, 0.86);
- backdrop-filter: saturate(140%) blur(0.5rem);
- border-bottom: 1px solid var(--rule);
- }
-
- .nav-inner {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 1rem;
- padding-block: 0.75rem;
- }
-
- .brand {
- display: flex;
- align-items: center;
- gap: 0.65rem;
- text-decoration: none;
- font-family: var(--font-serif);
- font-weight: 600;
- letter-spacing: 0.02em;
- font-size: 1.05rem;
- color: var(--ink);
- }
-
- nav.links {
- display: flex;
- align-items: center;
- gap: 1.75rem;
- }
-
- nav.links a {
- text-decoration: none;
- font-size: 0.92rem;
- letter-spacing: 0.03em;
- color: var(--ink-muted);
- padding: 0.4rem 0.2rem;
- transition: color .15s ease;
- }
-
- nav.links a:hover {
- color: var(--gold-bright);
- }
-
- nav.links a.active {
- color: var(--gold-bright);
- }
-
- .nav-cta {
- display: inline-flex;
- align-items: center;
- border: 1px solid var(--gold-dim);
- color: var(--gold-bright) !important;
- padding: 0.45rem 0.9rem;
- border-radius: 0.125rem;
- font-family: var(--font-mono);
- font-size: 0.75rem !important;
- letter-spacing: 0.06em;
- }
-
- /* mobile nav toggle: hidden on desktop, shown only under the nav.links
- breakpoint below. Icon swap is driven by the button's own
- aria-expanded state rather than a second JS-toggled class. */
- .nav-toggle {
- display: none;
- background: transparent;
- border: none;
- padding: 0.5rem;
- margin: -0.5rem;
- color: var(--ink);
- cursor: pointer;
- align-items: center;
- justify-content: center;
- }
-
- .nav-toggle svg {
- width: 1.5rem;
- height: 1.5rem;
- }
-
- .nav-toggle .icon-close {
- display: none;
- }
-
- .nav-toggle[aria-expanded="true"] .icon-menu {
- display: none;
- }
-
- .nav-toggle[aria-expanded="true"] .icon-close {
- display: block;
- }
-
- @media (max-width: 47.5em) {
- .nav-toggle {
- display: inline-flex;
- }
-
- .nav-cta {
- display: none;
- }
-
- nav.links {
- display: none;
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- flex-direction: column;
- align-items: stretch;
- gap: 0;
- background: var(--ground-2);
- border-bottom: 1px solid var(--rule);
- padding: 0.5rem 0;
- }
-
- nav.links.is-open {
- display: flex;
- }
-
- nav.links a {
- padding: 0.9rem 1.5rem;
- }
- }
-
- /* ---------- hero ---------- */
- .hero {
- position: relative;
- min-height: calc(100vh - 4.5rem);
- display: flex;
- align-items: center;
- justify-content: center;
- padding-block: clamp(4.5rem, 8vh, 7.5rem);
- text-align: center;
- overflow: hidden;
- background: var(--ground);
- /* fallback if brick-texture.png fails to load */
- }
-
- .hero::before {
- content: "";
- position: absolute;
- inset: 0;
- z-index: 1;
- opacity: 0.6;
- background:
- linear-gradient(to bottom, rgba(0, 0, 0, 0.12), transparent 22%, transparent 78%, rgba(0, 0, 0, 0.18));
- pointer-events: none;
- }
-
- .hero-media {
- position: absolute;
- inset: 0;
- z-index: 0;
- overflow: hidden;
- background-image: url('./assets/brick-texture.png');
- background-repeat: repeat;
- }
-
- .hero-scrim {
- position: absolute;
- inset: 0;
- background:
- linear-gradient(180deg, rgba(15, 12, 10, 0.35) 0%, rgba(15, 12, 10, 0.2) 45%, rgba(15, 12, 10, 0.6) 100%),
- radial-gradient(ellipse 60% 48% at 50% 18%, rgba(201, 163, 92, 0.16), transparent 58%);
- }
-
- .hero-inner {
- position: relative;
- z-index: 2;
- }
-
- .hero .eyebrow {
- margin-bottom: 1rem;
- }
-
- .hero h1 {
- font-size: clamp(3.1rem, 9vw, 6.2rem);
- color: var(--gold-bright);
- letter-spacing: -0.01em;
- }
-
- .hero .sub {
- font-family: var(--font-serif);
- font-style: italic;
- font-weight: 400;
- font-size: clamp(1.15rem, 2.6vw, 1.55rem);
- color: var(--ink-muted);
- margin-top: 0.35rem;
- }
-
- .tagline-row {
- margin-top: 2rem;
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
- /* column-gap carries the spacing now (was 0 — the dot's own margin
- used to be the only thing separating the phrases). Needs to be a
- real flex gap, not a margin on the dot, so removing a hidden dot
- from flow (see .wrap-sep-hidden) still leaves normal spacing
- between the two phrases instead of running them together. */
- gap: 0.6rem 0.7rem;
- font-family: var(--font-mono);
- font-size: 0.82rem;
- letter-spacing: 0.04em;
- color: var(--ink-muted);
- }
-
- /* Real element instead of a ::after pseudo-element — needs to be a
- measurable, independently-toggleable DOM node for the wrap-aware
- script in main.js to hide it when its neighbors wrap apart. No margin
- of its own: the column-gap above already provides 0.7rem on each
- side of it (matching the old margin-inline's total spacing exactly),
- and still applies directly between the two phrases whenever the dot
- itself is removed from flow. */
- .tag-sep {
- color: var(--gold-dim);
- user-select: none;
- }
-
- .hero-ctas {
- margin-top: 2.5rem;
- display: flex;
- justify-content: center;
- gap: 0.9rem;
- flex-wrap: wrap;
- }
-
- .btn {
- display: inline-flex;
- align-items: center;
- /* Centers label text horizontally too — matters once a button is
- stretched wider than its content (e.g. action-stack children filling
- a fixed-width column) or wraps to multiple lines; a no-op otherwise,
- so every button gets this instead of one-off overrides per case. */
- justify-content: center;
- gap: 0.5rem;
- padding: 0.8rem 1.5rem;
- font-family: var(--font-mono);
- font-size: 0.8rem;
- letter-spacing: 0.05em;
- text-decoration: none;
- border-radius: 0.125rem;
- transition: transform .15s ease, background .15s ease, border-color .15s ease;
- }
-
- .btn:hover {
- transform: translateY(-1px);
- }
-
- .btn:disabled {
- opacity: 0.35;
- cursor: not-allowed;
- transform: none;
- }
-
- /* :where() keeps this native-button reset at zero specificity, so
- modifier classes like .btn-primary's background can actually win
- instead of losing to button.btn's higher (0,1,1) specificity */
- :where(button.btn) {
- cursor: pointer;
- background: transparent;
- }
-
- .btn-primary {
- background: linear-gradient(180deg, var(--gold-bright), var(--gold));
- color: var(--ground-2);
- font-weight: 500;
- border: none;
- }
-
- .btn-primary:hover {
- background: linear-gradient(180deg, var(--gold-bright), var(--gold-bright));
- }
-
- .btn-outline {
- border: 1px solid var(--gold-dim);
- color: var(--gold-bright);
- }
-
- /* .nav-cta mirrors this hover treatment */
- .btn-outline:hover,
- .nav-cta:hover {
- border-color: var(--gold);
- background: var(--surface);
- }
-
- /* ---------- sections ---------- */
- section {
- padding-block: 5.5rem;
- }
-
- .section-head {
- margin-bottom: 2.75rem;
- }
-
- .section-head h2 {
- font-size: clamp(1.9rem, 3.6vw, 2.6rem);
- color: var(--ink);
- margin-top: 0.5rem;
- }
-
- /* about band */
- .about-grid {
- display: grid;
- grid-template-columns: 1.3fr 1fr;
- gap: 3.5rem;
- align-items: start;
- }
-
- .about-grid p+p {
- margin-top: 1rem;
- }
-
- .about-grid h2 {
- margin-top: 0.6rem;
- }
-
- .about-grid h2 + .lede {
- margin-top: 1.5rem;
- }
-
- .about-grid .lede {
- max-width: 56ch;
- color: var(--ink);
- font-size: 1.05rem;
- }
-
- /* shared surface-panel look: also used by .card, .login-card, .panel below */
- .stat-card,
- .card,
- .login-card,
- .panel,
- .menu-category {
- background: var(--surface);
- border: 1px solid var(--rule);
- }
-
- .stat-card {
- padding: 2rem;
- border-radius: 0.2rem;
- }
-
- .stat-card .eyebrow {
- display: block;
- margin-bottom: 1.1rem;
- }
-
- .stat-big {
- font-family: var(--font-serif);
- font-weight: 600;
- font-style: italic;
- font-size: 3rem;
- color: var(--gold-bright);
- line-height: 1;
- }
-
- .stat-card .stat-label {
- margin-top: 0.4rem;
- font-size: 0.92rem;
- color: var(--ink-muted);
- }
-
- .stat-source {
- margin-top: 1.5rem;
- font-family: var(--font-mono);
- font-size: 0.7rem;
- color: var(--gold-dim);
- letter-spacing: 0.05em;
- }
-
- @media (max-width: 51.25em) {
- .about-grid {
- grid-template-columns: 1fr;
- }
- }
-
- /* hours / visit band */
- .visit {
- background: var(--ground-2);
- }
-
- .visit-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 1.75rem;
- }
-
- .card {
- border-radius: 0.2rem;
- padding: 2.25rem;
- }
-
- .card h3 {
- font-size: 1.3rem;
- color: var(--gold-bright);
- margin-bottom: 1.4rem;
- }
-
- .hours-list {
- margin: 0;
- padding: 0;
- font-family: var(--font-mono);
- font-variant-numeric: tabular-nums;
- font-size: 0.92rem;
- }
-
- .hours-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0.55rem 0;
- border-bottom: 1px solid var(--rule);
- }
-
- .hours-row:last-child {
- border-bottom: none;
- }
-
- .hours-row dt {
- margin: 0;
- color: var(--ink-muted);
- letter-spacing: 0.04em;
- font-weight: normal;
- }
-
- .hours-row dd {
- margin: 0;
- text-align: right;
- color: var(--ink);
- }
-
- .card .note {
- margin-top: 1.25rem;
- font-size: 0.88rem;
- color: var(--ink-muted);
- }
-
- .card .note strong {
- color: var(--ink);
- font-weight: 600;
- }
-
- .find-us .pin {
- width: 2.125rem;
- height: 2.125rem;
- margin-bottom: 1rem;
- }
-
- .find-us address {
- font-style: normal;
- font-size: 1rem;
- color: var(--ink);
- }
-
- .find-us .contact-list {
- margin-top: 1.5rem;
- display: flex;
- flex-direction: column;
- gap: 0.6rem;
- }
-
- .find-us .contact-list a {
- text-decoration: none;
- color: var(--gold-bright);
- font-size: 0.92rem;
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- width: fit-content;
- border-bottom: 1px solid transparent;
- }
-
- .find-us .contact-list a:hover {
- border-bottom-color: var(--gold-dim);
- }
-
- .social-row {
- margin-top: 1.5rem;
- display: flex;
- gap: 0.9rem;
- }
-
- .social-row a {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 2.375rem;
- height: 2.375rem;
- border: 1px solid var(--rule);
- border-radius: 0.125rem;
- color: var(--ink-muted);
- text-decoration: none;
- transition: border-color .15s ease, color .15s ease;
- }
-
- .social-row a:hover {
- border-color: var(--gold);
- color: var(--gold-bright);
- }
-
- .social-row svg {
- width: 1.0625rem;
- height: 1.0625rem;
- }
-
- @media (max-width: 45em) {
- .visit-grid {
- grid-template-columns: 1fr;
- }
- }
-
- /* live music band */
- .music {
- background: linear-gradient(180deg, var(--green-deep), var(--green));
- color: var(--ink);
- text-align: center;
- }
-
- .music-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 3.5rem;
- align-items: center;
- text-align: left;
- margin-top: 1.5rem;
- }
-
- .music-content {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- }
-
- .music-photo-wrap {
- position: relative;
- border-radius: 0.25rem;
- overflow: hidden;
- border: 1px solid rgba(201, 163, 92, 0.35);
- box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.45);
- }
-
- .music-photo {
- width: 100%;
- height: auto;
- display: block;
- object-fit: cover;
- aspect-ratio: 4 / 3;
- object-position: center top;
- }
-
- .music .section-head {
- margin-bottom: 1.5rem;
- text-align: left;
- }
-
- .music .section-head .eyebrow {
- color: var(--gold-bright);
- }
-
- .music p.body {
- max-width: 58ch;
- margin: 0;
- font-size: 1.02rem;
- color: #e4ddc9;
- line-height: 1.65;
- }
-
- .music .btn-outline {
- border-color: rgba(230, 199, 137, 0.45);
- }
-
- .music .hero-ctas {
- margin-top: 2rem;
- justify-content: flex-start;
- }
-
- @media (max-width: 50em) {
- .music-grid {
- grid-template-columns: 1fr;
- gap: 2.5rem;
- text-align: center;
- }
-
- .music-content {
- align-items: center;
- }
-
- .music .section-head {
- text-align: center;
- }
-
- .music .hero-ctas {
- justify-content: center;
- }
- }
-
- /* menu band */
- .menu-band .container {
- max-width: 60rem;
- text-align: center;
- }
-
- .menu-band p.body {
- font-size: 1.02rem;
- color: var(--ink-muted);
- max-width: 56ch;
- margin: 0 auto;
- }
-
- .menu-band .hero-ctas {
- margin-top: 2.25rem;
- justify-content: center;
- }
-
- .menu-band .section-head {
- text-align: center;
- }
-
- .menu-band .rule-row {
- margin-bottom: 1.75rem;
- }
-
- /* footer */
- footer {
- background: var(--ground-2);
- border-top: 1px solid var(--rule);
- padding-block: 3.5rem 2.5rem;
- }
-
- .footer-grid {
- display: grid;
- grid-template-columns: 1.2fr 1fr 1fr;
- gap: 2.5rem;
- }
-
- .footer-brand {
- display: flex;
- align-items: center;
- gap: 0.6rem;
- text-decoration: none;
- }
-
- .footer-brand span {
- font-family: var(--font-serif);
- font-weight: 600;
- color: var(--ink);
- font-size: 1rem;
- }
-
- footer .col-title {
- font-family: var(--font-mono);
- font-size: 0.72rem;
- letter-spacing: 0.14em;
- text-transform: uppercase;
- color: var(--gold-dim);
- margin-bottom: 0.9rem;
- }
-
- footer address {
- margin-top: 1rem;
- font-style: normal;
- color: var(--ink-muted);
- font-size: 0.92rem;
- line-height: 1.7;
- }
-
- footer .social-row {
- margin-top: 1rem;
- }
-
- footer .hours-mini {
- font-family: var(--font-mono);
- font-size: 0.85rem;
- color: var(--ink-muted);
- font-variant-numeric: tabular-nums;
- display: flex;
- flex-direction: column;
- gap: 0.35rem;
- }
-
- footer a.email {
- color: var(--gold-bright);
- text-decoration: none;
- font-size: 0.92rem;
- }
-
- footer a.email:hover {
- text-decoration: underline;
- }
-
- .footer-bottom {
- margin-top: 3rem;
- padding-top: 1.75rem;
- border-top: 1px solid var(--rule);
- display: flex;
- justify-content: space-between;
- flex-wrap: wrap;
- gap: 0.75rem;
- font-size: 0.78rem;
- color: var(--ink-muted);
- }
-
- @media (max-width: 45em) {
- .footer-grid {
- grid-template-columns: 1fr;
- gap: 2rem;
- }
- }
-
- /* scroll reveal */
- .reveal {
- opacity: 0;
- transform: translateY(0.875rem);
- transition: opacity .6s ease, transform .6s ease;
- }
-
- .reveal.is-visible {
- opacity: 1;
- transform: none;
- }
-
- @media (prefers-reduced-motion: reduce) {
- .reveal {
- opacity: 1;
- transform: none;
- transition: none;
- }
- }
-
- /* ---------- admin / management styles ---------- */
- .admin-login-body {
- min-height: 100vh;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 1.5rem;
- }
-
- .login-card {
- border-radius: 0.25rem;
- padding: 2.5rem 2rem;
- width: 100%;
- max-width: 25rem;
- box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.4);
- }
-
- .login-header {
- text-align: center;
- margin-bottom: 2rem;
- }
-
- .login-header img {
- width: 2rem;
- height: 2rem;
- margin: 0 auto 0.75rem;
- }
-
- .login-header h1 {
- font-size: 1.5rem;
- color: var(--gold-bright);
- }
-
- .login-header p {
- font-family: var(--font-mono);
- font-size: 0.72rem;
- letter-spacing: 0.15em;
- text-transform: uppercase;
- color: var(--ink-muted);
- margin-top: 0.35rem;
- }
-
- .form-group {
- margin-bottom: 1.25rem;
- }
-
- .form-group.full-width {
- grid-column: 1 / -1;
- }
-
- .form-group label {
- display: block;
- font-family: var(--font-mono);
- font-size: 0.75rem;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- color: var(--gold);
- margin-bottom: 0.45rem;
- }
-
- .field-hint {
- font-size: 0.78rem;
- }
-
- .form-control,
- .form-input {
- width: 100%;
- background: var(--ground-2);
- border: 1px solid var(--rule);
- color: var(--ink);
- font-family: var(--font-sans);
- font-size: 0.95rem;
- padding: 0.7rem 0.85rem;
- border-radius: 0.125rem;
- box-sizing: border-box;
- outline: none;
- transition: border-color .15s ease;
- }
-
- .form-control:focus,
- .form-input:focus {
- border-color: var(--gold-bright);
- }
-
- textarea.form-control {
- resize: vertical;
- min-height: 5.5rem;
- line-height: 1.5;
- }
-
- .error-msg,
- .alert-error {
- background: var(--danger-bg);
- border: 1px solid rgba(220, 80, 60, 0.4);
- color: var(--danger-text);
- padding: 0.85rem 1.1rem;
- font-size: 0.88rem;
- border-radius: 0.125rem;
- margin-bottom: 1.25rem;
- }
-
- .login-btn {
- width: 100%;
- justify-content: center;
- margin-top: 0.5rem;
- }
-
- .back-link {
- display: block;
- text-align: center;
- margin-top: 1.5rem;
- font-family: var(--font-mono);
- font-size: 0.75rem;
- color: var(--ink-muted);
- text-decoration: none;
- }
-
- .back-link:hover {
- color: var(--gold-bright);
- }
-
- .admin-nav {
- background: var(--ground-2);
- border-bottom: 1px solid var(--rule);
- padding: 0.85rem 0;
- }
-
- .admin-nav-inner {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
-
- .admin-brand {
- display: flex;
- align-items: center;
- gap: 0.75rem;
- text-decoration: none;
- font-family: var(--font-serif);
- font-size: 1.15rem;
- color: var(--ink);
- }
-
- .admin-user {
- display: flex;
- align-items: center;
- gap: 1.25rem;
- font-family: var(--font-mono);
- font-size: 0.82rem;
- color: var(--ink-muted);
- }
-
- .admin-user a {
- color: var(--gold-bright);
- text-decoration: none;
- }
-
- .admin-user a:hover {
- text-decoration: underline;
- }
-
- @media (max-width: 47.5em) {
- .admin-nav-inner {
- flex-direction: column;
- align-items: flex-start;
- gap: 0.75rem;
- }
-
- /* two classes each, so these reliably outrank .admin-nav-links's and
- .admin-user's own base-rule gap regardless of source order */
- .admin-nav-inner .admin-nav-links,
- .admin-nav-inner .admin-user {
- flex-wrap: wrap;
- gap: 0.5rem 1rem;
- }
- }
-
- .dashboard-layout {
- padding-block: 2.5rem 4rem;
- }
-
- .dash-header {
- margin-bottom: 2rem;
- }
-
- .dash-header h1 {
- margin-top: 0.4rem;
- font-size: 2rem;
- color: var(--ink);
- }
-
- .panel {
- border-radius: 0.25rem;
- padding: 2rem;
- margin-bottom: 2rem;
- }
-
- .panel h2 {
- font-size: 1.35rem;
- color: var(--gold-bright);
- margin-bottom: 1.25rem;
- display: flex;
- align-items: center;
- gap: 0.6rem;
- }
-
- .panel-head {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between;
- align-items: center;
- gap: 0.75rem;
- margin-bottom: 1.5rem;
- }
-
- .form-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 1.5rem;
- }
-
- @media (max-width: 47.5em) {
- .form-grid {
- grid-template-columns: 1fr;
- }
- }
-
- .form-actions {
- display: flex;
- gap: 0.75rem;
- align-items: center;
- margin-top: 1.75rem;
- }
-
- .checkbox-group {
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
- margin-top: 0.5rem;
- }
-
- .checkbox-label {
- display: flex;
- align-items: center;
- gap: 0.6rem;
- font-size: 0.92rem;
- color: var(--ink);
- cursor: pointer;
- }
-
- .checkbox-label-lg {
- font-size: 1.05rem;
- font-weight: 500;
- }
-
- .checkbox-label input[type="checkbox"] {
- width: 1.15rem;
- height: 1.15rem;
- accent-color: var(--gold);
- cursor: pointer;
- }
-
- .status-badge {
- display: inline-flex;
- flex-direction: row;
- align-items: center;
- gap: 0.4rem;
- font-family: var(--font-mono);
- font-size: 0.72rem;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- padding: 0.25rem 0.6rem;
- border-radius: 0.125rem;
- white-space: nowrap;
- line-height: 1.2;
- }
-
- .status-badge.active {
- background: rgba(51, 80, 63, 0.4);
- border: 1px solid var(--green);
- color: #9fe2bf;
- }
-
- .status-badge.inactive {
- background: rgba(138, 115, 74, 0.2);
- border: 1px solid var(--rule);
- color: var(--ink-muted);
- }
-
- /* Both the menu item and announcement status badges double as their own
- toggle control — a plain would otherwise pick up the
- browser's default chrome underneath the badge's own background/border.
- Scoped to the element type so it's a no-op wherever a status-badge is
- still a plain read-only . Width is fixed per context below
- (each page's label set is a different length) so toggling never
- changes the badge's — and therefore the Status column's — width and
- shoves every other column sideways. */
- button.status-badge {
- appearance: none;
- -webkit-appearance: none;
- cursor: pointer;
- justify-content: center;
- transition: opacity .15s ease;
- }
-
- button.status-badge:hover {
- opacity: 0.8;
- }
-
- button.status-badge:disabled {
- cursor: not-allowed;
- opacity: 0.5;
- }
-
- /* "● Available" is the longest menu item label, ~6.7rem measured. */
- .item-row button.status-badge {
- width: 7rem;
- }
-
- /* "⏳ Scheduled" is the longest announcement label, ~7rem measured. */
- .ann-row button.status-badge {
- width: 7.25rem;
- }
-
- /* "○ Disabled" is the longer of the two user labels. Not scoped to
- button.status-badge like the two above — your own row's badge is a
- plain non-interactive span (see users.php), and it still needs to line
- up in the same column. */
- .user-row .status-badge {
- width: 7rem;
- }
-
- /* Admin row actions: the button-group styling shared by every admin
- list (menu items/categories, announcements, users). The actual
- row/column layout for each list is its own CSS Grid, defined where
- that list's markup lives below — there is no anywhere in
- this project. */
- .row-actions {
- display: inline-flex;
- gap: 0.5rem;
- align-items: center;
- flex-wrap: nowrap;
- white-space: nowrap;
- }
-
- .row-actions .btn {
- white-space: nowrap;
- }
-
- /* Groups a row's move (↑/↓) and edit/delete controls into two narrow
- vertical stacks instead of several buttons side by side — keeps the
- Actions column (and the row as a whole) from running wide. */
- .action-stack {
- display: inline-flex;
- flex-direction: column;
- gap: 0.35rem;
- }
-
- @media (max-width: 47.5em) {
- .row-actions {
- flex-wrap: wrap;
- white-space: normal;
- }
- }
-
- .btn-sm {
- padding: 0.4rem 0.8rem;
- font-size: 0.75rem;
- line-height: 1.2;
- }
-
- /* Single-glyph reorder buttons (↑ / ↓) — narrower than .btn-sm's text
- padding since there's no label to give it room to breathe. */
- .btn-icon {
- padding-inline: 0.55rem;
- }
-
- .btn-danger {
- background: var(--danger-bg);
- border: 1px solid rgba(220, 80, 60, 0.5);
- color: var(--danger-text);
- }
-
- .btn-danger:hover {
- background: rgba(220, 60, 50, 0.3);
- border-color: #ff6e6e;
- color: #fff;
- }
-
- .empty-state {
- text-align: center;
- padding: 3rem 1.5rem;
- color: var(--ink-muted);
- }
-
- .empty-state p {
- font-size: 0.95rem;
- margin-bottom: 1.25rem;
- }
-
- /* admin nav links */
- .admin-nav-links {
- display: flex;
- gap: 1.25rem;
- align-items: center;
- }
-
- .admin-nav-links a {
- font-family: var(--font-mono);
- font-size: 0.82rem;
- letter-spacing: 0.05em;
- text-decoration: none;
- color: var(--ink-muted);
- padding: 0.35rem 0.65rem;
- border-radius: 0.125rem;
- transition: color .15s ease, background .15s ease;
- }
-
- .admin-nav-links a:hover {
- color: var(--gold-bright);
- background: var(--surface);
- }
-
- .admin-nav-links a.active {
- color: var(--gold-bright);
- background: var(--surface);
- border-bottom: 1px solid var(--gold);
- }
-
- /* public online menu section */
- .menu-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
- gap: 2.5rem;
- margin-top: 3rem;
- text-align: left;
- }
-
- /* background/border come from the shared .panel rule above; no
- margin-bottom here since these sit in a grid whose own gap spaces them */
- .menu-category {
- border-radius: 0.25rem;
- padding: 2rem;
- }
-
- .menu-category-header {
- margin-bottom: 1.5rem;
- padding-bottom: 0.75rem;
- border-bottom: 1px solid var(--rule);
- }
-
- .menu-category-header h3 {
- font-size: 1.45rem;
- color: var(--gold-bright);
- }
-
- .menu-category-header p {
- margin-top: 0.35rem;
- font-size: 0.88rem;
- color: var(--ink-muted);
- }
-
- .menu-items-list {
- display: flex;
- flex-direction: column;
- gap: 1.4rem;
- }
-
- .menu-item-row {
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
- }
-
- .menu-item-top {
- display: flex;
- justify-content: space-between;
- align-items: baseline;
- gap: 0.75rem;
- }
-
- .menu-item-name {
- font-family: var(--font-serif);
- font-weight: 600;
- font-size: 1.05rem;
- color: var(--ink);
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- flex-wrap: wrap;
- }
-
- .menu-item-price {
- font-family: var(--font-mono);
- font-weight: 500;
- font-size: 0.95rem;
- color: var(--gold-bright);
- white-space: nowrap;
- }
-
- .menu-item-desc {
- font-size: 0.88rem;
- color: var(--ink-muted);
- line-height: 1.45;
- }
-
- .menu-item-badge {
- font-family: var(--font-mono);
- font-size: 0.65rem;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- padding: 0.15rem 0.45rem;
- border-radius: 0.125rem;
- background: rgba(201, 163, 92, 0.15);
- border: 1px solid var(--gold-dim);
- color: var(--gold-bright);
- font-weight: normal;
- }
-
- /* Admin list grids: menu items/categories, announcements, and users all
- share one CSS Grid pattern instead of a — see the git history
- for why. Every list's header row and data rows are grouped under one
- selector here for the properties they all share (box model,
- typography, the mobile reflow); each list then only has to declare
- its own grid-template-columns/areas, i.e. the shape unique to it. */
- .font-mono {
- font-family: var(--font-mono);
- }
-
- /* two classes to reliably outrank .panel h2's margin-bottom above */
- .category-panel .cat-name-heading {
- margin-bottom: 0.25rem;
- grid-area: name;
- }
-
- .cat-desc-text {
- font-size: 0.88rem;
- }
-
- .category-panel .cat-desc-text {
- grid-area: desc;
- }
-
- /* Category panel-head as a grid: title on its own top row, description
- and the move/edit/delete buttons sharing the row below with the
- buttons pinned to the far end (matches the desktop arrangement this
- replaced — see the mobile override for how that changes below 760px). */
- .category-panel .panel-head {
- display: grid;
- grid-template-columns: 1fr auto;
- grid-template-areas:
- "name actions"
- "desc actions";
- align-items: center;
- column-gap: 0.75rem;
- }
-
- .category-panel .panel-head .row-actions {
- grid-area: actions;
- }
-
- /* Each list's real column tracks live on the -grid-wrap — the one
- shared parent grid. The header row and every data row don't compute
- their own auto columns (which is what let them drift out of
- alignment: each was sizing "auto" only against its own content); they
- subgrid instead, inheriting the exact same tracks from this parent.
- .foo-rows is display:contents purely so .foo-row stays a direct grid
- child of .foo-grid-wrap for that inheritance — it has no visual
- footprint of its own. */
- .item-grid-wrap {
- grid-template-columns: auto minmax(0, 1fr) auto auto;
- }
-
- .ann-grid-wrap {
- grid-template-columns: auto minmax(0, 1fr) auto auto auto auto;
- }
-
- .user-grid-wrap {
- grid-template-columns: auto minmax(0, 1fr) auto auto auto;
- }
-
- .item-grid-wrap,
- .ann-grid-wrap,
- .user-grid-wrap {
- display: grid;
- column-gap: 0.75rem;
- margin-top: 1rem;
- }
-
- .item-rows,
- .ann-rows,
- .user-rows {
- display: contents;
- }
-
- .item-grid-head, .item-row,
- .ann-grid-head, .ann-row,
- .user-grid-head, .user-row {
- display: grid;
- grid-column: 1 / -1;
- grid-template-columns: subgrid;
- align-items: center;
- }
-
- .item-grid-head,
- .ann-grid-head,
- .user-grid-head {
- padding: 0 0.75rem 0.85rem;
- border-bottom: 1px solid var(--rule);
- font-family: var(--font-mono);
- font-size: 0.72rem;
- letter-spacing: 0.12em;
- text-transform: uppercase;
- color: var(--gold);
- font-weight: 500;
- }
-
- .item-row,
- .ann-row,
- .user-row {
- padding: 0.85rem 0.75rem;
- border-bottom: 1px solid var(--rule);
- }
-
- .item-row:last-child,
- .ann-row:last-child,
- .user-row:last-child {
- border-bottom: none;
- }
-
- /* Each list's own shape: which named area each field maps to. "Status"
- → status and "Actions" → actions mean the same thing in all three, so
- those two are declared once. */
- .item-grid-head, .item-row {
- grid-template-areas: "status item price actions";
- }
-
- .ann-grid-head, .ann-row {
- grid-template-areas: "status headline type schedule placements actions";
- }
-
- .user-grid-head, .user-row {
- grid-template-areas: "status username role created actions";
- }
-
- .col-status,
- .item-row [data-label="Status"],
- .ann-row [data-label="Status"],
- .user-row [data-label="Status"] {
- grid-area: status;
- }
-
- .col-actions,
- .item-row [data-label="Actions"],
- .ann-row [data-label="Actions"],
- .user-row [data-label="Actions"] {
- grid-area: actions;
- }
-
- .col-item { grid-area: item; }
- .col-price { grid-area: price; }
- .item-row [data-label="Item"] { grid-area: item; }
- .item-row [data-label="Price"] { grid-area: price; }
-
- .col-headline { grid-area: headline; }
- .col-type { grid-area: type; }
- .col-schedule { grid-area: schedule; }
- .col-placements { grid-area: placements; }
- .ann-row [data-label="Headline"] { grid-area: headline; }
- .ann-row [data-label="Type"] { grid-area: type; }
- .ann-row [data-label="Schedule"] { grid-area: schedule; }
- .ann-row [data-label="Placements"] { grid-area: placements; }
-
- .col-username { grid-area: username; }
- .col-role { grid-area: role; }
- .col-created { grid-area: created; }
- .user-row [data-label="Username"] { grid-area: username; }
- .user-row [data-label="Role"] { grid-area: role; }
- .user-row [data-label="Created"] { grid-area: created; }
-
- /* .item-rows being display:contents makes this a direct child of the
- .item-grid-wrap grid when a category is empty — span every column or
- it lands squeezed into just the first (Status) track. */
- .item-grid-empty {
- grid-column: 1 / -1;
- text-align: center;
- padding: 1.5rem 0;
- }
-
- /* Wraps the item name and its optional badge (reusing the public menu's
- .menu-item-badge pill) so they sit on one inline-flowing line instead
- of giving the badge its own column. */
- .item-name-row {
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- flex-wrap: wrap;
- }
-
- .item-desc-cell {
- font-size: 0.82rem;
- margin-top: 0.2rem;
- }
-
- .item-price-cell {
- color: var(--gold-bright);
- font-weight: 500;
- }
-
- .timestamp-cell {
- color: var(--ink-muted);
- font-size: 0.8rem;
- }
-
- /* Each grid's own mobile arrangement, independent of the desktop one
- above — this is the whole point of grid-template-areas over a
- . Same fields, different map, per list. Placed after the
- desktop rules above so it wins the cascade at equal specificity — a
- rule's source position still matters once a media query lets both
- compete. The reflow mechanics (hide the header row, stack fields with
- a printed label, pin Actions to the end) are identical across all
- three lists and the category header, so those are shared too; only
- each one's grid-template-areas differs. */
- @media (max-width: 47.5em) {
- /* The wrap collapses to a single column, and every subgridded row
- below inherits that automatically — they keep `grid-template-
- columns: subgrid` from the desktop rule, so this one change is all
- it takes to reflow the whole list (see grid-template-areas per row
- further down for how each field then stacks within that column). */
- /* Every list's cards get a second column on mobile so Actions can sit
- in the top-right corner alongside that list's leading field, instead
- of stacking at the very bottom of the card — overrides the shared
- single-column rule that would otherwise apply here too.
- minmax(0, auto), not plain auto, so this column can still shrink
- below its content's natural width under pressure instead of forcing
- the whole row to overflow the card. */
- .item-grid-wrap,
- .ann-grid-wrap,
- .user-grid-wrap {
- grid-template-columns: 1fr minmax(0, auto);
- }
-
- /* Users' actions column gets a fixed cap instead of auto: "Change
- Password" is long enough that its max-content size (what auto would
- size the column to) is wider than this corner has room for, which
- pushed the button past the card edge. A firm cap plus the
- wrap/stretch rules below keep it predictable instead. */
- .user-grid-wrap {
- grid-template-columns: minmax(0, 1fr) 6rem;
- }
-
- /* Every row's Actions cell holds one or two .action-stacks (move
- up/down, edit/delete, etc. — each stacked vertically, see
- .action-stack itself further up) side by side. Keeps those stacks
- from wrapping onto their own lines in this narrow corner, which is
- what the shared .row-actions wrap rule below would otherwise do. */
- .item-row .row-actions,
- .ann-row .row-actions,
- .user-row .row-actions {
- flex-wrap: nowrap;
- }
-
- .item-grid-head,
- .ann-grid-head,
- .user-grid-head {
- display: none;
- }
-
- .item-row,
- .ann-row,
- .user-row {
- row-gap: 0.4rem;
- border: 1px solid var(--rule);
- border-radius: 0.25rem;
- padding: 0.9rem 1rem;
- margin-bottom: 0.85rem;
- }
-
- .item-row:last-child,
- .ann-row:last-child,
- .user-row:last-child {
- border-bottom: none;
- margin-bottom: 0;
- }
-
- .item-row [data-label]::before,
- .ann-row [data-label]::before,
- .user-row [data-label]::before {
- content: attr(data-label);
- display: block;
- font-family: var(--font-mono);
- font-size: 0.68rem;
- letter-spacing: 0.1em;
- text-transform: uppercase;
- color: var(--gold);
- margin-bottom: 0.3rem;
- }
-
- .item-row {
- grid-template-areas:
- "status actions"
- "item item"
- "price price";
- /* Overrides the shared align-items: center above — on desktop that
- vertically centers Status against the taller Item cell in one wide
- row, which is fine, but here it also centers Status against the
- taller Actions block above, pushing its badge down instead of
- keeping both flush with the card's top edge. */
- align-items: start;
- }
-
- .ann-row {
- grid-template-areas:
- "status actions"
- "headline headline"
- "type type"
- "schedule schedule"
- "placements placements";
- align-items: start;
- }
-
- .user-row {
- grid-template-areas:
- "status actions"
- "username username"
- "role role"
- "created created";
- align-items: start;
- }
-
- /* Stretches Actions' whole flex chain out to the fixed 6rem column
- above (each is shrink-wrapped by default), then lets "Change
- Password" — long enough on its own to blow out this corner next to a
- username — wrap onto two lines within that width instead of forcing
- the column wider. Every other action-stack button here (Edit,
- Delete, ↑/↓) is short enough to stay on one line regardless. */
- .user-row .row-actions,
- .user-row .action-stack,
- .user-row .row-actions .btn {
- width: 100%;
- }
-
- .user-row .btn-change-password {
- white-space: normal;
- }
-
- .category-panel .panel-head {
- grid-template-columns: 1fr;
- grid-template-areas:
- "name"
- "desc"
- "actions";
- row-gap: 0.5rem;
- }
- }
-
- /* ---------- admin modal dialogs ---------- */
- .admin-modal-backdrop {
- position: fixed;
- inset: 0;
- background: rgba(15, 12, 10, 0.8);
- backdrop-filter: blur(0.25rem);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
- padding: 1.5rem;
- opacity: 0;
- pointer-events: none;
- transition: opacity .2s ease;
- }
-
- .admin-modal-backdrop.is-open {
- opacity: 1;
- pointer-events: auto;
- }
-
- .admin-modal {
- background: var(--surface);
- border: 1px solid var(--rule);
- border-radius: 0.25rem;
- width: 100%;
- max-width: 36rem;
- max-height: 90vh;
- overflow-y: auto;
- padding: 2rem;
- box-shadow: 0 1.5rem 3rem rgba(0, 0, 0, 0.6);
- transform: translateY(0.75rem);
- transition: transform .2s ease;
- }
-
- .admin-modal-backdrop.is-open .admin-modal {
- transform: translateY(0);
- }
-
- .modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 1.5rem;
- padding-bottom: 0.75rem;
- border-bottom: 1px solid var(--rule);
- }
-
- .modal-header h2 {
- font-size: 1.35rem;
- color: var(--gold-bright);
- margin: 0;
- }
-
- .modal-close {
- background: transparent;
- border: none;
- color: var(--ink-muted);
- font-size: 1.5rem;
- cursor: pointer;
- padding: 0.25rem;
- line-height: 1;
- text-decoration: none;
- transition: color .15s ease;
- }
-
- .modal-close:hover {
- color: var(--gold-bright);
- }
-
- .admin-toolbar {
- display: flex;
- flex-wrap: wrap;
- gap: 0.75rem;
- align-items: center;
- margin-bottom: 2rem;
- }
-
- /* password toggle input wrapper */
- .password-input-wrap {
- position: relative;
- display: flex;
- align-items: center;
- }
-
- .password-input-wrap .form-control,
- .password-input-wrap .form-input {
- padding-right: 2.75rem;
- }
-
- .password-toggle-btn {
- position: absolute;
- right: 0.5rem;
- top: 50%;
- transform: translateY(-50%);
- background: transparent;
- border: none;
- color: var(--ink-muted);
- cursor: pointer;
- padding: 0.35rem;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 0.125rem;
- transition: color .15s ease;
- }
-
- .password-toggle-btn:hover {
- color: var(--gold-bright);
- }
-
- .password-toggle-btn svg {
- width: 1.15rem;
- height: 1.15rem;
- }
-
- /* menu page specific headers & spacing */
- .menu-page-header {
- padding-block: 4.5rem 2.5rem;
- }
-
- .menu-page-content {
- padding-bottom: 5.5rem;
- }
-
- .alert-success {
- background: rgba(51, 128, 90, 0.15);
- border: 1px solid var(--green);
- color: #9fe2bf;
- padding: 0.85rem 1.1rem;
- font-size: 0.88rem;
- border-radius: 0.125rem;
- margin-bottom: 1.25rem;
- }
-
- /* ---------- admin: pub hours ---------- */
- /* Same subgrid technique as the item/ann/user lists above (see the
- comment near .item-grid-wrap), but with no grid-template-areas: every
- row's five fields sit in the same left-to-right order on both desktop
- and mobile, so plain column order is enough — nothing needs to reflow. */
- .hours-grid-wrap {
- display: grid;
- grid-template-columns: auto auto 1fr 1fr 1fr;
- column-gap: 0.75rem;
- margin-top: 1rem;
- }
-
- .hours-day-rows {
- display: contents;
- }
-
- .hours-grid-head,
- .hours-day-row {
- display: grid;
- grid-column: 1 / -1;
- grid-template-columns: subgrid;
- align-items: center;
- }
-
- .hours-grid-head {
- padding: 0 0.75rem 0.85rem;
- border-bottom: 1px solid var(--rule);
- font-family: var(--font-mono);
- font-size: 0.72rem;
- letter-spacing: 0.12em;
- text-transform: uppercase;
- color: var(--gold);
- font-weight: 500;
- }
-
- .hours-day-row {
- padding: 0.85rem 0.75rem;
- border-bottom: 1px solid var(--rule);
- }
-
- .hours-day-row:last-child {
- border-bottom: none;
- }
-
- .hours-day-row .form-control {
- min-width: 7rem;
- }
-
- .hours-day-row .day-time-field:disabled {
- opacity: 0.4;
- }
-
- @media (max-width: 47.5em) {
- .hours-grid-wrap {
- grid-template-columns: 1fr;
- }
-
- .hours-grid-head {
- display: none;
- }
-
- .hours-day-row {
- grid-template-columns: 1fr;
- row-gap: 0.5rem;
- border: 1px solid var(--rule);
- border-radius: 0.25rem;
- padding: 0.9rem 1rem;
- margin-bottom: 0.85rem;
- }
-
- .hours-day-row:last-child {
- margin-bottom: 0;
- }
-
- .hours-day-row [data-label]::before {
- content: attr(data-label);
- display: block;
- font-family: var(--font-mono);
- font-size: 0.68rem;
- letter-spacing: 0.1em;
- text-transform: uppercase;
- color: var(--gold);
- margin-bottom: 0.3rem;
- }
-
- .hours-day-row .form-control {
- width: 100%;
- }
- }
-
- /* Live preview panel: mimics the public site's Visit card and footer
- mini-summary without depending on their .card/footer ancestor scoping
- (this lives inside an admin .panel, which already shares that same
- surface look — nesting the real classes here would just blend in). */
- .hours-preview {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 1.5rem;
- }
-
- .hours-preview-card,
- .hours-preview-footer {
- background: var(--ground-2);
- border: 1px solid var(--rule);
- border-radius: 0.2rem;
- padding: 1.5rem;
- }
-
- .hours-preview-card .note {
- margin-top: 1.25rem;
- font-size: 0.88rem;
- color: var(--ink-muted);
- }
-
- .hours-preview-card .note strong {
- color: var(--ink);
- font-weight: 600;
- }
-
- .hours-preview-footer .col-title {
- font-family: var(--font-mono);
- font-size: 0.72rem;
- letter-spacing: 0.14em;
- text-transform: uppercase;
- color: var(--gold-dim);
- margin-bottom: 0.9rem;
- }
-
- .hours-preview-footer .hours-mini {
- font-family: var(--font-mono);
- font-size: 0.85rem;
- color: var(--ink-muted);
- font-variant-numeric: tabular-nums;
- display: flex;
- flex-direction: column;
- gap: 0.35rem;
- }
-
- @media (max-width: 47.5em) {
- .hours-preview {
- grid-template-columns: 1fr;
- }
- }
\ No newline at end of file
+// The Old Fitz — stylesheet manifest. Forwards every partial below in
+// cascade order; no rules are defined directly in this file.
+
+// Abstracts
+@use "abstracts/tokens";
+
+// Base
+@use "base/reset";
+@use "base/utilities";
+
+// Components
+@use "components/dividers";
+@use "components/buttons";
+@use "components/cards";
+@use "components/status-badge";
+@use "components/admin-actions";
+@use "components/admin-data-grid";
+@use "components/admin-modal";
+@use "components/forms";
+@use "components/announcement-banner";
+@use "components/social-row";
+
+// Layout
+@use "layout/header";
+@use "layout/footer";
+@use "layout/admin-shell";
+
+// Pages
+@use "pages/home";
+@use "pages/menu";
+@use "pages/admin-login";
+@use "pages/admin-menu";
+@use "pages/admin-hours";
diff --git a/styles.css b/styles.css
index cc4a6b1..8fa2ea5 100644
--- a/styles.css
+++ b/styles.css
@@ -32,26 +32,6 @@ html {
scroll-behavior: auto;
}
}
-/* ---------- wrap-aware separators (JS-toggled, see main.js) ----------
- Shared hook used by the announcement banner and hero tagline row: a
- "·" between two pieces of text that can independently wrap onto
- separate lines. No CSS selector exists for "am I on the same line as
- my sibling," so main.js measures and toggles this class instead of a
- fixed breakpoint. Visible by default so a no-JS visitor always sees
- the dot (today's behavior), never hidden by default.
-
- display, not visibility: a hidden separator must take no space, or
- it still gets counted when justify-content: center balances its flex
- line — a lone badge sharing its line with a space-reserving invisible
- dot renders off-center. Safe from the "text merges together" bug that
- originally motivated reserving space: both wrap-sep contexts space
- their siblings with a real flex `gap` (see .announcement-inner,
- .tagline-row) rather than the separator's own margin, so removing it
- from flow still leaves normal spacing behind, never a collision. */
-.wrap-sep-hidden {
- display: none;
-}
-
body {
margin: 0;
background: var(--ground);
@@ -72,13 +52,6 @@ svg {
max-width: 100%;
}
-.container {
- width: 100%;
- max-width: 70rem;
- margin: 0 auto;
- padding-inline: 1.5rem;
-}
-
h1,
h2,
h3 {
@@ -89,6 +62,47 @@ h3 {
margin: 0;
}
+p {
+ margin: 0;
+}
+
+:focus-visible {
+ outline: 2px solid var(--gold-bright);
+ outline-offset: 3px;
+}
+
+/* ---------- sections ---------- */
+section {
+ padding-block: 5.5rem;
+}
+
+/* ---------- wrap-aware separators (JS-toggled, see main.js) ----------
+ Shared hook used by the announcement banner and hero tagline row: a
+ "·" between two pieces of text that can independently wrap onto
+ separate lines. No CSS selector exists for "am I on the same line as
+ my sibling," so main.js measures and toggles this class instead of a
+ fixed breakpoint. Visible by default so a no-JS visitor always sees
+ the dot (today's behavior), never hidden by default.
+
+ display, not visibility: a hidden separator must take no space, or
+ it still gets counted when justify-content: center balances its flex
+ line — a lone badge sharing its line with a space-reserving invisible
+ dot renders off-center. Safe from the "text merges together" bug that
+ originally motivated reserving space: both wrap-sep contexts space
+ their siblings with a real flex `gap` (see .announcement-inner,
+ .tagline-row) rather than the separator's own margin, so removing it
+ from flow still leaves normal spacing behind, never a collision. */
+.wrap-sep-hidden {
+ display: none;
+}
+
+.container {
+ width: 100%;
+ max-width: 70rem;
+ margin: 0 auto;
+ padding-inline: 1.5rem;
+}
+
.eyebrow {
font-family: var(--font-mono);
font-size: 0.72rem;
@@ -97,10 +111,6 @@ h3 {
color: var(--gold);
}
-p {
- margin: 0;
-}
-
.muted {
color: var(--ink-muted);
}
@@ -123,9 +133,27 @@ p {
top: 1rem;
}
-:focus-visible {
- outline: 2px solid var(--gold-bright);
- outline-offset: 3px;
+/* scroll reveal */
+.reveal {
+ opacity: 0;
+ transform: translateY(0.875rem);
+ transition: opacity 0.6s ease, transform 0.6s ease;
+}
+
+.reveal.is-visible {
+ opacity: 1;
+ transform: none;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .reveal {
+ opacity: 1;
+ transform: none;
+ transition: none;
+ }
+}
+.font-mono {
+ font-family: var(--font-mono);
}
/* ---------- knot mark ---------- */
@@ -148,19 +176,6 @@ p {
height: 1.625rem;
}
-.hero-brand {
- margin: 0;
- line-height: 0;
-}
-
-.hero-logo {
- display: block;
- width: clamp(17.5rem, 28vw, 31rem);
- max-width: 100%;
- height: auto;
- margin: 0 auto 1.75rem;
-}
-
/* ---------- brass rule divider ---------- */
.rule-row {
display: flex;
@@ -176,342 +191,6 @@ p {
background: linear-gradient(90deg, transparent, var(--gold-dim), transparent);
}
-/* ---------- announcement banner ---------- */
-.announcement-banner {
- background: var(--ground-2);
- border-bottom: 1px solid var(--rule);
- padding: 0.75rem 0;
- font-size: 0.88rem;
- position: relative;
- z-index: 40;
-}
-
-.announcement-inner {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 0.75rem;
- flex-wrap: wrap;
- text-align: center;
-}
-
-.announcement-tag {
- font-family: var(--font-mono);
- font-size: 0.75rem;
- letter-spacing: 0.12em;
- text-transform: uppercase;
- font-weight: 600;
- padding: 0.2rem 0.55rem;
- border-radius: 0.125rem;
-}
-
-.announcement-sep {
- color: var(--gold-dim);
- user-select: none;
-}
-
-.announcement-text {
- color: var(--ink);
- font-weight: 400;
-}
-
-/* banner theme styles */
-.banner-holiday {
- background: linear-gradient(90deg, rgba(32, 48, 38, 0.9), rgba(15, 12, 10, 0.95), rgba(32, 48, 38, 0.9));
- border-bottom-color: var(--rule);
-}
-
-.banner-holiday .announcement-tag {
- background: rgba(201, 163, 92, 0.18);
- border: 1px solid var(--gold-dim);
- color: var(--gold-bright);
-}
-
-.banner-special {
- background: linear-gradient(90deg, rgba(43, 35, 25, 0.9), rgba(15, 12, 10, 0.95), rgba(43, 35, 25, 0.9));
- border-bottom-color: var(--gold-dim);
-}
-
-.banner-special .announcement-tag {
- background: var(--gold);
- color: var(--ground-2);
-}
-
-.banner-closure {
- background: linear-gradient(90deg, rgba(80, 24, 20, 0.85), rgba(15, 12, 10, 0.95), rgba(80, 24, 20, 0.85));
- border-bottom-color: rgba(200, 60, 50, 0.4);
-}
-
-.banner-closure .announcement-tag {
- background: rgba(220, 60, 50, 0.25);
- border: 1px solid rgba(220, 80, 70, 0.6);
- color: #ffb4b4;
-}
-
-/* hours card alert callout */
-.hours-alert {
- margin-bottom: 1.25rem;
- padding: 0.85rem 1rem;
- border-radius: 0.2rem;
- font-size: 0.88rem;
- line-height: 1.45;
-}
-
-.hours-alert strong {
- display: block;
- font-family: var(--font-mono);
- font-size: 0.75rem;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- margin-bottom: 0.25rem;
-}
-
-.hours-alert.alert-holiday {
- background: rgba(51, 80, 63, 0.25);
- border: 1px solid rgba(201, 163, 92, 0.3);
- color: var(--ink);
-}
-
-.hours-alert.alert-holiday strong {
- color: var(--gold-bright);
-}
-
-.hours-alert.alert-special {
- background: rgba(43, 35, 25, 0.6);
- border: 1px solid var(--gold-dim);
- color: var(--ink);
-}
-
-.hours-alert.alert-special strong {
- color: var(--gold-bright);
-}
-
-.hours-alert.alert-closure {
- background: rgba(80, 24, 20, 0.3);
- border: 1px solid rgba(220, 80, 70, 0.5);
- color: #ffdfdf;
-}
-
-.hours-alert.alert-closure strong {
- color: #ffb4b4;
-}
-
-/* ---------- header / nav ---------- */
-header.site {
- position: sticky;
- top: 0;
- z-index: 50;
- background: rgba(15, 12, 10, 0.86);
- backdrop-filter: saturate(140%) blur(0.5rem);
- border-bottom: 1px solid var(--rule);
-}
-
-.nav-inner {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 1rem;
- padding-block: 0.75rem;
-}
-
-.brand {
- display: flex;
- align-items: center;
- gap: 0.65rem;
- text-decoration: none;
- font-family: var(--font-serif);
- font-weight: 600;
- letter-spacing: 0.02em;
- font-size: 1.05rem;
- color: var(--ink);
-}
-
-nav.links {
- display: flex;
- align-items: center;
- gap: 1.75rem;
-}
-
-nav.links a {
- text-decoration: none;
- font-size: 0.92rem;
- letter-spacing: 0.03em;
- color: var(--ink-muted);
- padding: 0.4rem 0.2rem;
- transition: color 0.15s ease;
-}
-
-nav.links a:hover {
- color: var(--gold-bright);
-}
-
-nav.links a.active {
- color: var(--gold-bright);
-}
-
-.nav-cta {
- display: inline-flex;
- align-items: center;
- border: 1px solid var(--gold-dim);
- color: var(--gold-bright) !important;
- padding: 0.45rem 0.9rem;
- border-radius: 0.125rem;
- font-family: var(--font-mono);
- font-size: 0.75rem !important;
- letter-spacing: 0.06em;
-}
-
-/* mobile nav toggle: hidden on desktop, shown only under the nav.links
- breakpoint below. Icon swap is driven by the button's own
- aria-expanded state rather than a second JS-toggled class. */
-.nav-toggle {
- display: none;
- background: transparent;
- border: none;
- padding: 0.5rem;
- margin: -0.5rem;
- color: var(--ink);
- cursor: pointer;
- align-items: center;
- justify-content: center;
-}
-
-.nav-toggle svg {
- width: 1.5rem;
- height: 1.5rem;
-}
-
-.nav-toggle .icon-close {
- display: none;
-}
-
-.nav-toggle[aria-expanded=true] .icon-menu {
- display: none;
-}
-
-.nav-toggle[aria-expanded=true] .icon-close {
- display: block;
-}
-
-@media (max-width: 47.5em) {
- .nav-toggle {
- display: inline-flex;
- }
- .nav-cta {
- display: none;
- }
- nav.links {
- display: none;
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- flex-direction: column;
- align-items: stretch;
- gap: 0;
- background: var(--ground-2);
- border-bottom: 1px solid var(--rule);
- padding: 0.5rem 0;
- }
- nav.links.is-open {
- display: flex;
- }
- nav.links a {
- padding: 0.9rem 1.5rem;
- }
-}
-/* ---------- hero ---------- */
-.hero {
- position: relative;
- min-height: calc(100vh - 4.5rem);
- display: flex;
- align-items: center;
- justify-content: center;
- padding-block: clamp(4.5rem, 8vh, 7.5rem);
- text-align: center;
- overflow: hidden;
- background: var(--ground);
- /* fallback if brick-texture.png fails to load */
-}
-
-.hero::before {
- content: "";
- position: absolute;
- inset: 0;
- z-index: 1;
- opacity: 0.6;
- background: linear-gradient(to bottom, rgba(0, 0, 0, 0.12), transparent 22%, transparent 78%, rgba(0, 0, 0, 0.18));
- pointer-events: none;
-}
-
-.hero-media {
- position: absolute;
- inset: 0;
- z-index: 0;
- overflow: hidden;
- background-image: url("./assets/brick-texture.png");
- background-repeat: repeat;
-}
-
-.hero-scrim {
- position: absolute;
- inset: 0;
- background: linear-gradient(180deg, rgba(15, 12, 10, 0.35) 0%, rgba(15, 12, 10, 0.2) 45%, rgba(15, 12, 10, 0.6) 100%), radial-gradient(ellipse 60% 48% at 50% 18%, rgba(201, 163, 92, 0.16), transparent 58%);
-}
-
-.hero-inner {
- position: relative;
- z-index: 2;
-}
-
-.hero .eyebrow {
- margin-bottom: 1rem;
-}
-
-.hero h1 {
- font-size: clamp(3.1rem, 9vw, 6.2rem);
- color: var(--gold-bright);
- letter-spacing: -0.01em;
-}
-
-.hero .sub {
- font-family: var(--font-serif);
- font-style: italic;
- font-weight: 400;
- font-size: clamp(1.15rem, 2.6vw, 1.55rem);
- color: var(--ink-muted);
- margin-top: 0.35rem;
-}
-
-.tagline-row {
- margin-top: 2rem;
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
- /* column-gap carries the spacing now (was 0 — the dot's own margin
- used to be the only thing separating the phrases). Needs to be a
- real flex gap, not a margin on the dot, so removing a hidden dot
- from flow (see .wrap-sep-hidden) still leaves normal spacing
- between the two phrases instead of running them together. */
- gap: 0.6rem 0.7rem;
- font-family: var(--font-mono);
- font-size: 0.82rem;
- letter-spacing: 0.04em;
- color: var(--ink-muted);
-}
-
-/* Real element instead of a ::after pseudo-element — needs to be a
- measurable, independently-toggleable DOM node for the wrap-aware
- script in main.js to hide it when its neighbors wrap apart. No margin
- of its own: the column-gap above already provides 0.7rem on each
- side of it (matching the old margin-inline's total spacing exactly),
- and still applies directly between the two phrases whenever the dot
- itself is removed from flow. */
-.tag-sep {
- color: var(--gold-dim);
- user-select: none;
-}
-
.hero-ctas {
margin-top: 2.5rem;
display: flex;
@@ -579,45 +258,28 @@ nav.links a.active {
background: var(--surface);
}
-/* ---------- sections ---------- */
-section {
- padding-block: 5.5rem;
+.btn-sm {
+ padding: 0.4rem 0.8rem;
+ font-size: 0.75rem;
+ line-height: 1.2;
}
-.section-head {
- margin-bottom: 2.75rem;
+/* Single-glyph reorder buttons (↑ / ↓) — narrower than .btn-sm's text
+ padding since there's no label to give it room to breathe. */
+.btn-icon {
+ padding-inline: 0.55rem;
}
-.section-head h2 {
- font-size: clamp(1.9rem, 3.6vw, 2.6rem);
- color: var(--ink);
- margin-top: 0.5rem;
+.btn-danger {
+ background: var(--danger-bg);
+ border: 1px solid rgba(220, 80, 60, 0.5);
+ color: var(--danger-text);
}
-/* about band */
-.about-grid {
- display: grid;
- grid-template-columns: 1.3fr 1fr;
- gap: 3.5rem;
- align-items: start;
-}
-
-.about-grid p + p {
- margin-top: 1rem;
-}
-
-.about-grid h2 {
- margin-top: 0.6rem;
-}
-
-.about-grid h2 + .lede {
- margin-top: 1.5rem;
-}
-
-.about-grid .lede {
- max-width: 56ch;
- color: var(--ink);
- font-size: 1.05rem;
+.btn-danger:hover {
+ background: rgba(220, 60, 50, 0.3);
+ border-color: #ff6e6e;
+ color: #fff;
}
/* shared surface-panel look: also used by .card, .login-card, .panel below */
@@ -663,22 +325,6 @@ section {
letter-spacing: 0.05em;
}
-@media (max-width: 51.25em) {
- .about-grid {
- grid-template-columns: 1fr;
- }
-}
-/* hours / visit band */
-.visit {
- background: var(--ground-2);
-}
-
-.visit-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 1.75rem;
-}
-
.card {
border-radius: 0.2rem;
padding: 2.25rem;
@@ -690,39 +336,6 @@ section {
margin-bottom: 1.4rem;
}
-.hours-list {
- margin: 0;
- padding: 0;
- font-family: var(--font-mono);
- font-variant-numeric: tabular-nums;
- font-size: 0.92rem;
-}
-
-.hours-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0.55rem 0;
- border-bottom: 1px solid var(--rule);
-}
-
-.hours-row:last-child {
- border-bottom: none;
-}
-
-.hours-row dt {
- margin: 0;
- color: var(--ink-muted);
- letter-spacing: 0.04em;
- font-weight: normal;
-}
-
-.hours-row dd {
- margin: 0;
- text-align: right;
- color: var(--ink);
-}
-
.card .note {
margin-top: 1.25rem;
font-size: 0.88rem;
@@ -734,475 +347,6 @@ section {
font-weight: 600;
}
-.find-us .pin {
- width: 2.125rem;
- height: 2.125rem;
- margin-bottom: 1rem;
-}
-
-.find-us address {
- font-style: normal;
- font-size: 1rem;
- color: var(--ink);
-}
-
-.find-us .contact-list {
- margin-top: 1.5rem;
- display: flex;
- flex-direction: column;
- gap: 0.6rem;
-}
-
-.find-us .contact-list a {
- text-decoration: none;
- color: var(--gold-bright);
- font-size: 0.92rem;
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- width: fit-content;
- border-bottom: 1px solid transparent;
-}
-
-.find-us .contact-list a:hover {
- border-bottom-color: var(--gold-dim);
-}
-
-.social-row {
- margin-top: 1.5rem;
- display: flex;
- gap: 0.9rem;
-}
-
-.social-row a {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 2.375rem;
- height: 2.375rem;
- border: 1px solid var(--rule);
- border-radius: 0.125rem;
- color: var(--ink-muted);
- text-decoration: none;
- transition: border-color 0.15s ease, color 0.15s ease;
-}
-
-.social-row a:hover {
- border-color: var(--gold);
- color: var(--gold-bright);
-}
-
-.social-row svg {
- width: 1.0625rem;
- height: 1.0625rem;
-}
-
-@media (max-width: 45em) {
- .visit-grid {
- grid-template-columns: 1fr;
- }
-}
-/* live music band */
-.music {
- background: linear-gradient(180deg, var(--green-deep), var(--green));
- color: var(--ink);
- text-align: center;
-}
-
-.music-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 3.5rem;
- align-items: center;
- text-align: left;
- margin-top: 1.5rem;
-}
-
-.music-content {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
-}
-
-.music-photo-wrap {
- position: relative;
- border-radius: 0.25rem;
- overflow: hidden;
- border: 1px solid rgba(201, 163, 92, 0.35);
- box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.45);
-}
-
-.music-photo {
- width: 100%;
- height: auto;
- display: block;
- object-fit: cover;
- aspect-ratio: 4/3;
- object-position: center top;
-}
-
-.music .section-head {
- margin-bottom: 1.5rem;
- text-align: left;
-}
-
-.music .section-head .eyebrow {
- color: var(--gold-bright);
-}
-
-.music p.body {
- max-width: 58ch;
- margin: 0;
- font-size: 1.02rem;
- color: #e4ddc9;
- line-height: 1.65;
-}
-
-.music .btn-outline {
- border-color: rgba(230, 199, 137, 0.45);
-}
-
-.music .hero-ctas {
- margin-top: 2rem;
- justify-content: flex-start;
-}
-
-@media (max-width: 50em) {
- .music-grid {
- grid-template-columns: 1fr;
- gap: 2.5rem;
- text-align: center;
- }
- .music-content {
- align-items: center;
- }
- .music .section-head {
- text-align: center;
- }
- .music .hero-ctas {
- justify-content: center;
- }
-}
-/* menu band */
-.menu-band .container {
- max-width: 60rem;
- text-align: center;
-}
-
-.menu-band p.body {
- font-size: 1.02rem;
- color: var(--ink-muted);
- max-width: 56ch;
- margin: 0 auto;
-}
-
-.menu-band .hero-ctas {
- margin-top: 2.25rem;
- justify-content: center;
-}
-
-.menu-band .section-head {
- text-align: center;
-}
-
-.menu-band .rule-row {
- margin-bottom: 1.75rem;
-}
-
-/* footer */
-footer {
- background: var(--ground-2);
- border-top: 1px solid var(--rule);
- padding-block: 3.5rem 2.5rem;
-}
-
-.footer-grid {
- display: grid;
- grid-template-columns: 1.2fr 1fr 1fr;
- gap: 2.5rem;
-}
-
-.footer-brand {
- display: flex;
- align-items: center;
- gap: 0.6rem;
- text-decoration: none;
-}
-
-.footer-brand span {
- font-family: var(--font-serif);
- font-weight: 600;
- color: var(--ink);
- font-size: 1rem;
-}
-
-footer .col-title {
- font-family: var(--font-mono);
- font-size: 0.72rem;
- letter-spacing: 0.14em;
- text-transform: uppercase;
- color: var(--gold-dim);
- margin-bottom: 0.9rem;
-}
-
-footer address {
- margin-top: 1rem;
- font-style: normal;
- color: var(--ink-muted);
- font-size: 0.92rem;
- line-height: 1.7;
-}
-
-footer .social-row {
- margin-top: 1rem;
-}
-
-footer .hours-mini {
- font-family: var(--font-mono);
- font-size: 0.85rem;
- color: var(--ink-muted);
- font-variant-numeric: tabular-nums;
- display: flex;
- flex-direction: column;
- gap: 0.35rem;
-}
-
-footer a.email {
- color: var(--gold-bright);
- text-decoration: none;
- font-size: 0.92rem;
-}
-
-footer a.email:hover {
- text-decoration: underline;
-}
-
-.footer-bottom {
- margin-top: 3rem;
- padding-top: 1.75rem;
- border-top: 1px solid var(--rule);
- display: flex;
- justify-content: space-between;
- flex-wrap: wrap;
- gap: 0.75rem;
- font-size: 0.78rem;
- color: var(--ink-muted);
-}
-
-@media (max-width: 45em) {
- .footer-grid {
- grid-template-columns: 1fr;
- gap: 2rem;
- }
-}
-/* scroll reveal */
-.reveal {
- opacity: 0;
- transform: translateY(0.875rem);
- transition: opacity 0.6s ease, transform 0.6s ease;
-}
-
-.reveal.is-visible {
- opacity: 1;
- transform: none;
-}
-
-@media (prefers-reduced-motion: reduce) {
- .reveal {
- opacity: 1;
- transform: none;
- transition: none;
- }
-}
-/* ---------- admin / management styles ---------- */
-.admin-login-body {
- min-height: 100vh;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 1.5rem;
-}
-
-.login-card {
- border-radius: 0.25rem;
- padding: 2.5rem 2rem;
- width: 100%;
- max-width: 25rem;
- box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.4);
-}
-
-.login-header {
- text-align: center;
- margin-bottom: 2rem;
-}
-
-.login-header img {
- width: 2rem;
- height: 2rem;
- margin: 0 auto 0.75rem;
-}
-
-.login-header h1 {
- font-size: 1.5rem;
- color: var(--gold-bright);
-}
-
-.login-header p {
- font-family: var(--font-mono);
- font-size: 0.72rem;
- letter-spacing: 0.15em;
- text-transform: uppercase;
- color: var(--ink-muted);
- margin-top: 0.35rem;
-}
-
-.form-group {
- margin-bottom: 1.25rem;
-}
-
-.form-group.full-width {
- grid-column: 1/-1;
-}
-
-.form-group label {
- display: block;
- font-family: var(--font-mono);
- font-size: 0.75rem;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- color: var(--gold);
- margin-bottom: 0.45rem;
-}
-
-.field-hint {
- font-size: 0.78rem;
-}
-
-.form-control,
-.form-input {
- width: 100%;
- background: var(--ground-2);
- border: 1px solid var(--rule);
- color: var(--ink);
- font-family: var(--font-sans);
- font-size: 0.95rem;
- padding: 0.7rem 0.85rem;
- border-radius: 0.125rem;
- box-sizing: border-box;
- outline: none;
- transition: border-color 0.15s ease;
-}
-
-.form-control:focus,
-.form-input:focus {
- border-color: var(--gold-bright);
-}
-
-textarea.form-control {
- resize: vertical;
- min-height: 5.5rem;
- line-height: 1.5;
-}
-
-.error-msg,
-.alert-error {
- background: var(--danger-bg);
- border: 1px solid rgba(220, 80, 60, 0.4);
- color: var(--danger-text);
- padding: 0.85rem 1.1rem;
- font-size: 0.88rem;
- border-radius: 0.125rem;
- margin-bottom: 1.25rem;
-}
-
-.login-btn {
- width: 100%;
- justify-content: center;
- margin-top: 0.5rem;
-}
-
-.back-link {
- display: block;
- text-align: center;
- margin-top: 1.5rem;
- font-family: var(--font-mono);
- font-size: 0.75rem;
- color: var(--ink-muted);
- text-decoration: none;
-}
-
-.back-link:hover {
- color: var(--gold-bright);
-}
-
-.admin-nav {
- background: var(--ground-2);
- border-bottom: 1px solid var(--rule);
- padding: 0.85rem 0;
-}
-
-.admin-nav-inner {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.admin-brand {
- display: flex;
- align-items: center;
- gap: 0.75rem;
- text-decoration: none;
- font-family: var(--font-serif);
- font-size: 1.15rem;
- color: var(--ink);
-}
-
-.admin-user {
- display: flex;
- align-items: center;
- gap: 1.25rem;
- font-family: var(--font-mono);
- font-size: 0.82rem;
- color: var(--ink-muted);
-}
-
-.admin-user a {
- color: var(--gold-bright);
- text-decoration: none;
-}
-
-.admin-user a:hover {
- text-decoration: underline;
-}
-
-@media (max-width: 47.5em) {
- .admin-nav-inner {
- flex-direction: column;
- align-items: flex-start;
- gap: 0.75rem;
- }
- /* two classes each, so these reliably outrank .admin-nav-links's and
- .admin-user's own base-rule gap regardless of source order */
- .admin-nav-inner .admin-nav-links,
- .admin-nav-inner .admin-user {
- flex-wrap: wrap;
- gap: 0.5rem 1rem;
- }
-}
-.dashboard-layout {
- padding-block: 2.5rem 4rem;
-}
-
-.dash-header {
- margin-bottom: 2rem;
-}
-
-.dash-header h1 {
- margin-top: 0.4rem;
- font-size: 2rem;
- color: var(--ink);
-}
-
.panel {
border-radius: 0.25rem;
padding: 2rem;
@@ -1227,52 +371,6 @@ textarea.form-control {
margin-bottom: 1.5rem;
}
-.form-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 1.5rem;
-}
-
-@media (max-width: 47.5em) {
- .form-grid {
- grid-template-columns: 1fr;
- }
-}
-.form-actions {
- display: flex;
- gap: 0.75rem;
- align-items: center;
- margin-top: 1.75rem;
-}
-
-.checkbox-group {
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
- margin-top: 0.5rem;
-}
-
-.checkbox-label {
- display: flex;
- align-items: center;
- gap: 0.6rem;
- font-size: 0.92rem;
- color: var(--ink);
- cursor: pointer;
-}
-
-.checkbox-label-lg {
- font-size: 1.05rem;
- font-weight: 500;
-}
-
-.checkbox-label input[type=checkbox] {
- width: 1.15rem;
- height: 1.15rem;
- accent-color: var(--gold);
- cursor: pointer;
-}
-
.status-badge {
display: inline-flex;
flex-direction: row;
@@ -1375,30 +473,6 @@ button.status-badge:disabled {
white-space: normal;
}
}
-.btn-sm {
- padding: 0.4rem 0.8rem;
- font-size: 0.75rem;
- line-height: 1.2;
-}
-
-/* Single-glyph reorder buttons (↑ / ↓) — narrower than .btn-sm's text
- padding since there's no label to give it room to breathe. */
-.btn-icon {
- padding-inline: 0.55rem;
-}
-
-.btn-danger {
- background: var(--danger-bg);
- border: 1px solid rgba(220, 80, 60, 0.5);
- color: var(--danger-text);
-}
-
-.btn-danger:hover {
- background: rgba(220, 60, 50, 0.3);
- border-color: #ff6e6e;
- color: #fff;
-}
-
.empty-state {
text-align: center;
padding: 3rem 1.5rem;
@@ -1410,165 +484,12 @@ button.status-badge:disabled {
margin-bottom: 1.25rem;
}
-/* admin nav links */
-.admin-nav-links {
- display: flex;
- gap: 1.25rem;
- align-items: center;
-}
-
-.admin-nav-links a {
- font-family: var(--font-mono);
- font-size: 0.82rem;
- letter-spacing: 0.05em;
- text-decoration: none;
- color: var(--ink-muted);
- padding: 0.35rem 0.65rem;
- border-radius: 0.125rem;
- transition: color 0.15s ease, background 0.15s ease;
-}
-
-.admin-nav-links a:hover {
- color: var(--gold-bright);
- background: var(--surface);
-}
-
-.admin-nav-links a.active {
- color: var(--gold-bright);
- background: var(--surface);
- border-bottom: 1px solid var(--gold);
-}
-
-/* public online menu section */
-.menu-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
- gap: 2.5rem;
- margin-top: 3rem;
- text-align: left;
-}
-
-/* background/border come from the shared .panel rule above; no
- margin-bottom here since these sit in a grid whose own gap spaces them */
-.menu-category {
- border-radius: 0.25rem;
- padding: 2rem;
-}
-
-.menu-category-header {
- margin-bottom: 1.5rem;
- padding-bottom: 0.75rem;
- border-bottom: 1px solid var(--rule);
-}
-
-.menu-category-header h3 {
- font-size: 1.45rem;
- color: var(--gold-bright);
-}
-
-.menu-category-header p {
- margin-top: 0.35rem;
- font-size: 0.88rem;
- color: var(--ink-muted);
-}
-
-.menu-items-list {
- display: flex;
- flex-direction: column;
- gap: 1.4rem;
-}
-
-.menu-item-row {
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
-}
-
-.menu-item-top {
- display: flex;
- justify-content: space-between;
- align-items: baseline;
- gap: 0.75rem;
-}
-
-.menu-item-name {
- font-family: var(--font-serif);
- font-weight: 600;
- font-size: 1.05rem;
- color: var(--ink);
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- flex-wrap: wrap;
-}
-
-.menu-item-price {
- font-family: var(--font-mono);
- font-weight: 500;
- font-size: 0.95rem;
- color: var(--gold-bright);
- white-space: nowrap;
-}
-
-.menu-item-desc {
- font-size: 0.88rem;
- color: var(--ink-muted);
- line-height: 1.45;
-}
-
-.menu-item-badge {
- font-family: var(--font-mono);
- font-size: 0.65rem;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- padding: 0.15rem 0.45rem;
- border-radius: 0.125rem;
- background: rgba(201, 163, 92, 0.15);
- border: 1px solid var(--gold-dim);
- color: var(--gold-bright);
- font-weight: normal;
-}
-
/* Admin list grids: menu items/categories, announcements, and users all
share one CSS Grid pattern instead of a — see the git history
for why. Every list's header row and data rows are grouped under one
selector here for the properties they all share (box model,
typography, the mobile reflow); each list then only has to declare
its own grid-template-columns/areas, i.e. the shape unique to it. */
-.font-mono {
- font-family: var(--font-mono);
-}
-
-/* two classes to reliably outrank .panel h2's margin-bottom above */
-.category-panel .cat-name-heading {
- margin-bottom: 0.25rem;
- grid-area: name;
-}
-
-.cat-desc-text {
- font-size: 0.88rem;
-}
-
-.category-panel .cat-desc-text {
- grid-area: desc;
-}
-
-/* Category panel-head as a grid: title on its own top row, description
- and the move/edit/delete buttons sharing the row below with the
- buttons pinned to the far end (matches the desktop arrangement this
- replaced — see the mobile override for how that changes below 760px). */
-.category-panel .panel-head {
- display: grid;
- grid-template-columns: 1fr auto;
- grid-template-areas: "name actions" "desc actions";
- align-items: center;
- column-gap: 0.75rem;
-}
-
-.category-panel .panel-head .row-actions {
- grid-area: actions;
-}
-
/* Each list's real column tracks live on the -grid-wrap — the one
shared parent grid. The header row and every data row don't compute
their own auto columns (which is what let them drift out of
@@ -1748,26 +669,6 @@ button.status-badge:disabled {
padding: 1.5rem 0;
}
-/* Wraps the item name and its optional badge (reusing the public menu's
- .menu-item-badge pill) so they sit on one inline-flowing line instead
- of giving the badge its own column. */
-.item-name-row {
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- flex-wrap: wrap;
-}
-
-.item-desc-cell {
- font-size: 0.82rem;
- margin-top: 0.2rem;
-}
-
-.item-price-cell {
- color: var(--gold-bright);
- font-weight: 500;
-}
-
.timestamp-cell {
color: var(--ink-muted);
font-size: 0.8rem;
@@ -1881,11 +782,6 @@ button.status-badge:disabled {
.user-row .btn-change-password {
white-space: normal;
}
- .category-panel .panel-head {
- grid-template-columns: 1fr;
- grid-template-areas: "name" "desc" "actions";
- row-gap: 0.5rem;
- }
}
/* ---------- admin modal dialogs ---------- */
.admin-modal-backdrop {
@@ -1957,12 +853,109 @@ button.status-badge:disabled {
color: var(--gold-bright);
}
-.admin-toolbar {
+.form-group {
+ margin-bottom: 1.25rem;
+}
+
+.form-group.full-width {
+ grid-column: 1/-1;
+}
+
+.form-group label {
+ display: block;
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--gold);
+ margin-bottom: 0.45rem;
+}
+
+.field-hint {
+ font-size: 0.78rem;
+}
+
+.form-control,
+.form-input {
+ width: 100%;
+ background: var(--ground-2);
+ border: 1px solid var(--rule);
+ color: var(--ink);
+ font-family: var(--font-sans);
+ font-size: 0.95rem;
+ padding: 0.7rem 0.85rem;
+ border-radius: 0.125rem;
+ box-sizing: border-box;
+ outline: none;
+ transition: border-color 0.15s ease;
+}
+
+.form-control:focus,
+.form-input:focus {
+ border-color: var(--gold-bright);
+}
+
+textarea.form-control {
+ resize: vertical;
+ min-height: 5.5rem;
+ line-height: 1.5;
+}
+
+.error-msg,
+.alert-error {
+ background: var(--danger-bg);
+ border: 1px solid rgba(220, 80, 60, 0.4);
+ color: var(--danger-text);
+ padding: 0.85rem 1.1rem;
+ font-size: 0.88rem;
+ border-radius: 0.125rem;
+ margin-bottom: 1.25rem;
+}
+
+.form-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1.5rem;
+}
+
+@media (max-width: 47.5em) {
+ .form-grid {
+ grid-template-columns: 1fr;
+ }
+}
+.form-actions {
display: flex;
- flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
- margin-bottom: 2rem;
+ margin-top: 1.75rem;
+}
+
+.checkbox-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ margin-top: 0.5rem;
+}
+
+.checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ font-size: 0.92rem;
+ color: var(--ink);
+ cursor: pointer;
+}
+
+.checkbox-label-lg {
+ font-size: 1.05rem;
+ font-weight: 500;
+}
+
+.checkbox-label input[type=checkbox] {
+ width: 1.15rem;
+ height: 1.15rem;
+ accent-color: var(--gold);
+ cursor: pointer;
}
/* password toggle input wrapper */
@@ -2003,15 +996,6 @@ button.status-badge:disabled {
height: 1.15rem;
}
-/* menu page specific headers & spacing */
-.menu-page-header {
- padding-block: 4.5rem 2.5rem;
-}
-
-.menu-page-content {
- padding-bottom: 5.5rem;
-}
-
.alert-success {
background: rgba(51, 128, 90, 0.15);
border: 1px solid var(--green);
@@ -2022,6 +1006,1028 @@ button.status-badge:disabled {
margin-bottom: 1.25rem;
}
+/* ---------- announcement banner ---------- */
+.announcement-banner {
+ background: var(--ground-2);
+ border-bottom: 1px solid var(--rule);
+ padding: 0.75rem 0;
+ font-size: 0.88rem;
+ position: relative;
+ z-index: 40;
+}
+
+.announcement-inner {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+ text-align: center;
+}
+
+.announcement-tag {
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ font-weight: 600;
+ padding: 0.2rem 0.55rem;
+ border-radius: 0.125rem;
+}
+
+.announcement-sep {
+ color: var(--gold-dim);
+ user-select: none;
+}
+
+.announcement-text {
+ color: var(--ink);
+ font-weight: 400;
+}
+
+/* banner theme styles */
+.banner-holiday {
+ background: linear-gradient(90deg, rgba(32, 48, 38, 0.9), rgba(15, 12, 10, 0.95), rgba(32, 48, 38, 0.9));
+ border-bottom-color: var(--rule);
+}
+
+.banner-holiday .announcement-tag {
+ background: rgba(201, 163, 92, 0.18);
+ border: 1px solid var(--gold-dim);
+ color: var(--gold-bright);
+}
+
+.banner-special {
+ background: linear-gradient(90deg, rgba(43, 35, 25, 0.9), rgba(15, 12, 10, 0.95), rgba(43, 35, 25, 0.9));
+ border-bottom-color: var(--gold-dim);
+}
+
+.banner-special .announcement-tag {
+ background: var(--gold);
+ color: var(--ground-2);
+}
+
+.banner-closure {
+ background: linear-gradient(90deg, rgba(80, 24, 20, 0.85), rgba(15, 12, 10, 0.95), rgba(80, 24, 20, 0.85));
+ border-bottom-color: rgba(200, 60, 50, 0.4);
+}
+
+.banner-closure .announcement-tag {
+ background: rgba(220, 60, 50, 0.25);
+ border: 1px solid rgba(220, 80, 70, 0.6);
+ color: #ffb4b4;
+}
+
+.social-row {
+ margin-top: 1.5rem;
+ display: flex;
+ gap: 0.9rem;
+}
+
+.social-row a {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 2.375rem;
+ height: 2.375rem;
+ border: 1px solid var(--rule);
+ border-radius: 0.125rem;
+ color: var(--ink-muted);
+ text-decoration: none;
+ transition: border-color 0.15s ease, color 0.15s ease;
+}
+
+.social-row a:hover {
+ border-color: var(--gold);
+ color: var(--gold-bright);
+}
+
+.social-row svg {
+ width: 1.0625rem;
+ height: 1.0625rem;
+}
+
+footer .social-row {
+ margin-top: 1rem;
+}
+
+/* ---------- header / nav ---------- */
+header.site {
+ position: sticky;
+ top: 0;
+ z-index: 50;
+ background: rgba(15, 12, 10, 0.86);
+ backdrop-filter: saturate(140%) blur(0.5rem);
+ border-bottom: 1px solid var(--rule);
+}
+
+.nav-inner {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ padding-block: 0.75rem;
+}
+
+.brand {
+ display: flex;
+ align-items: center;
+ gap: 0.65rem;
+ text-decoration: none;
+ font-family: var(--font-serif);
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ font-size: 1.05rem;
+ color: var(--ink);
+}
+
+nav.links {
+ display: flex;
+ align-items: center;
+ gap: 1.75rem;
+}
+
+nav.links a {
+ text-decoration: none;
+ font-size: 0.92rem;
+ letter-spacing: 0.03em;
+ color: var(--ink-muted);
+ padding: 0.4rem 0.2rem;
+ transition: color 0.15s ease;
+}
+
+nav.links a:hover {
+ color: var(--gold-bright);
+}
+
+nav.links a.active {
+ color: var(--gold-bright);
+}
+
+.nav-cta {
+ display: inline-flex;
+ align-items: center;
+ border: 1px solid var(--gold-dim);
+ color: var(--gold-bright) !important;
+ padding: 0.45rem 0.9rem;
+ border-radius: 0.125rem;
+ font-family: var(--font-mono);
+ font-size: 0.75rem !important;
+ letter-spacing: 0.06em;
+}
+
+/* mobile nav toggle: hidden on desktop, shown only under the nav.links
+ breakpoint below. Icon swap is driven by the button's own
+ aria-expanded state rather than a second JS-toggled class. */
+.nav-toggle {
+ display: none;
+ background: transparent;
+ border: none;
+ padding: 0.5rem;
+ margin: -0.5rem;
+ color: var(--ink);
+ cursor: pointer;
+ align-items: center;
+ justify-content: center;
+}
+
+.nav-toggle svg {
+ width: 1.5rem;
+ height: 1.5rem;
+}
+
+.nav-toggle .icon-close {
+ display: none;
+}
+
+.nav-toggle[aria-expanded=true] .icon-menu {
+ display: none;
+}
+
+.nav-toggle[aria-expanded=true] .icon-close {
+ display: block;
+}
+
+@media (max-width: 47.5em) {
+ .nav-toggle {
+ display: inline-flex;
+ }
+ .nav-cta {
+ display: none;
+ }
+ nav.links {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 0;
+ background: var(--ground-2);
+ border-bottom: 1px solid var(--rule);
+ padding: 0.5rem 0;
+ }
+ nav.links.is-open {
+ display: flex;
+ }
+ nav.links a {
+ padding: 0.9rem 1.5rem;
+ }
+}
+/* footer */
+footer {
+ background: var(--ground-2);
+ border-top: 1px solid var(--rule);
+ padding-block: 3.5rem 2.5rem;
+}
+
+.footer-grid {
+ display: grid;
+ grid-template-columns: 1.2fr 1fr 1fr;
+ gap: 2.5rem;
+}
+
+.footer-brand {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ text-decoration: none;
+}
+
+.footer-brand span {
+ font-family: var(--font-serif);
+ font-weight: 600;
+ color: var(--ink);
+ font-size: 1rem;
+}
+
+footer .col-title {
+ font-family: var(--font-mono);
+ font-size: 0.72rem;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--gold-dim);
+ margin-bottom: 0.9rem;
+}
+
+footer address {
+ margin-top: 1rem;
+ font-style: normal;
+ color: var(--ink-muted);
+ font-size: 0.92rem;
+ line-height: 1.7;
+}
+
+footer .hours-mini {
+ font-family: var(--font-mono);
+ font-size: 0.85rem;
+ color: var(--ink-muted);
+ font-variant-numeric: tabular-nums;
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+}
+
+footer a.email {
+ color: var(--gold-bright);
+ text-decoration: none;
+ font-size: 0.92rem;
+}
+
+footer a.email:hover {
+ text-decoration: underline;
+}
+
+.footer-bottom {
+ margin-top: 3rem;
+ padding-top: 1.75rem;
+ border-top: 1px solid var(--rule);
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ font-size: 0.78rem;
+ color: var(--ink-muted);
+}
+
+@media (max-width: 45em) {
+ .footer-grid {
+ grid-template-columns: 1fr;
+ gap: 2rem;
+ }
+}
+.admin-nav {
+ background: var(--ground-2);
+ border-bottom: 1px solid var(--rule);
+ padding: 0.85rem 0;
+}
+
+.admin-nav-inner {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.admin-brand {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ text-decoration: none;
+ font-family: var(--font-serif);
+ font-size: 1.15rem;
+ color: var(--ink);
+}
+
+.admin-user {
+ display: flex;
+ align-items: center;
+ gap: 1.25rem;
+ font-family: var(--font-mono);
+ font-size: 0.82rem;
+ color: var(--ink-muted);
+}
+
+.admin-user a {
+ color: var(--gold-bright);
+ text-decoration: none;
+}
+
+.admin-user a:hover {
+ text-decoration: underline;
+}
+
+@media (max-width: 47.5em) {
+ .admin-nav-inner {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.75rem;
+ }
+ /* two classes each, so these reliably outrank .admin-nav-links's and
+ .admin-user's own base-rule gap regardless of source order */
+ .admin-nav-inner .admin-nav-links,
+ .admin-nav-inner .admin-user {
+ flex-wrap: wrap;
+ gap: 0.5rem 1rem;
+ }
+}
+.dashboard-layout {
+ padding-block: 2.5rem 4rem;
+}
+
+.dash-header {
+ margin-bottom: 2rem;
+}
+
+.dash-header h1 {
+ margin-top: 0.4rem;
+ font-size: 2rem;
+ color: var(--ink);
+}
+
+/* admin nav links */
+.admin-nav-links {
+ display: flex;
+ gap: 1.25rem;
+ align-items: center;
+}
+
+.admin-nav-links a {
+ font-family: var(--font-mono);
+ font-size: 0.82rem;
+ letter-spacing: 0.05em;
+ text-decoration: none;
+ color: var(--ink-muted);
+ padding: 0.35rem 0.65rem;
+ border-radius: 0.125rem;
+ transition: color 0.15s ease, background 0.15s ease;
+}
+
+.admin-nav-links a:hover {
+ color: var(--gold-bright);
+ background: var(--surface);
+}
+
+.admin-nav-links a.active {
+ color: var(--gold-bright);
+ background: var(--surface);
+ border-bottom: 1px solid var(--gold);
+}
+
+.admin-toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ align-items: center;
+ margin-bottom: 2rem;
+}
+
+.hero-brand {
+ margin: 0;
+ line-height: 0;
+}
+
+.hero-logo {
+ display: block;
+ width: clamp(17.5rem, 28vw, 31rem);
+ max-width: 100%;
+ height: auto;
+ margin: 0 auto 1.75rem;
+}
+
+/* hours card alert callout */
+.hours-alert {
+ margin-bottom: 1.25rem;
+ padding: 0.85rem 1rem;
+ border-radius: 0.2rem;
+ font-size: 0.88rem;
+ line-height: 1.45;
+}
+
+.hours-alert strong {
+ display: block;
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ margin-bottom: 0.25rem;
+}
+
+.hours-alert.alert-holiday {
+ background: rgba(51, 80, 63, 0.25);
+ border: 1px solid rgba(201, 163, 92, 0.3);
+ color: var(--ink);
+}
+
+.hours-alert.alert-holiday strong {
+ color: var(--gold-bright);
+}
+
+.hours-alert.alert-special {
+ background: rgba(43, 35, 25, 0.6);
+ border: 1px solid var(--gold-dim);
+ color: var(--ink);
+}
+
+.hours-alert.alert-special strong {
+ color: var(--gold-bright);
+}
+
+.hours-alert.alert-closure {
+ background: rgba(80, 24, 20, 0.3);
+ border: 1px solid rgba(220, 80, 70, 0.5);
+ color: #ffdfdf;
+}
+
+.hours-alert.alert-closure strong {
+ color: #ffb4b4;
+}
+
+/* ---------- hero ---------- */
+.hero {
+ position: relative;
+ min-height: calc(100vh - 4.5rem);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding-block: clamp(4.5rem, 8vh, 7.5rem);
+ text-align: center;
+ overflow: hidden;
+ background: var(--ground);
+ /* fallback if brick-texture.png fails to load */
+}
+
+.hero::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ z-index: 1;
+ opacity: 0.6;
+ background: linear-gradient(to bottom, rgba(0, 0, 0, 0.12), transparent 22%, transparent 78%, rgba(0, 0, 0, 0.18));
+ pointer-events: none;
+}
+
+.hero-media {
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+ overflow: hidden;
+ background-image: url("./assets/brick-texture.png");
+ background-repeat: repeat;
+}
+
+.hero-scrim {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(180deg, rgba(15, 12, 10, 0.35) 0%, rgba(15, 12, 10, 0.2) 45%, rgba(15, 12, 10, 0.6) 100%), radial-gradient(ellipse 60% 48% at 50% 18%, rgba(201, 163, 92, 0.16), transparent 58%);
+}
+
+.hero-inner {
+ position: relative;
+ z-index: 2;
+}
+
+.hero .eyebrow {
+ margin-bottom: 1rem;
+}
+
+.hero h1 {
+ font-size: clamp(3.1rem, 9vw, 6.2rem);
+ color: var(--gold-bright);
+ letter-spacing: -0.01em;
+}
+
+.hero .sub {
+ font-family: var(--font-serif);
+ font-style: italic;
+ font-weight: 400;
+ font-size: clamp(1.15rem, 2.6vw, 1.55rem);
+ color: var(--ink-muted);
+ margin-top: 0.35rem;
+}
+
+.tagline-row {
+ margin-top: 2rem;
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+ /* column-gap carries the spacing now (was 0 — the dot's own margin
+ used to be the only thing separating the phrases). Needs to be a
+ real flex gap, not a margin on the dot, so removing a hidden dot
+ from flow (see .wrap-sep-hidden) still leaves normal spacing
+ between the two phrases instead of running them together. */
+ gap: 0.6rem 0.7rem;
+ font-family: var(--font-mono);
+ font-size: 0.82rem;
+ letter-spacing: 0.04em;
+ color: var(--ink-muted);
+}
+
+/* Real element instead of a ::after pseudo-element — needs to be a
+ measurable, independently-toggleable DOM node for the wrap-aware
+ script in main.js to hide it when its neighbors wrap apart. No margin
+ of its own: the column-gap above already provides 0.7rem on each
+ side of it (matching the old margin-inline's total spacing exactly),
+ and still applies directly between the two phrases whenever the dot
+ itself is removed from flow. */
+.tag-sep {
+ color: var(--gold-dim);
+ user-select: none;
+}
+
+.section-head {
+ margin-bottom: 2.75rem;
+}
+
+.section-head h2 {
+ font-size: clamp(1.9rem, 3.6vw, 2.6rem);
+ color: var(--ink);
+ margin-top: 0.5rem;
+}
+
+/* about band */
+.about-grid {
+ display: grid;
+ grid-template-columns: 1.3fr 1fr;
+ gap: 3.5rem;
+ align-items: start;
+}
+
+.about-grid p + p {
+ margin-top: 1rem;
+}
+
+.about-grid h2 {
+ margin-top: 0.6rem;
+}
+
+.about-grid h2 + .lede {
+ margin-top: 1.5rem;
+}
+
+.about-grid .lede {
+ max-width: 56ch;
+ color: var(--ink);
+ font-size: 1.05rem;
+}
+
+@media (max-width: 51.25em) {
+ .about-grid {
+ grid-template-columns: 1fr;
+ }
+}
+/* hours / visit band */
+.visit {
+ background: var(--ground-2);
+}
+
+.visit-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1.75rem;
+}
+
+.hours-list {
+ margin: 0;
+ padding: 0;
+ font-family: var(--font-mono);
+ font-variant-numeric: tabular-nums;
+ font-size: 0.92rem;
+}
+
+.hours-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.55rem 0;
+ border-bottom: 1px solid var(--rule);
+}
+
+.hours-row:last-child {
+ border-bottom: none;
+}
+
+.hours-row dt {
+ margin: 0;
+ color: var(--ink-muted);
+ letter-spacing: 0.04em;
+ font-weight: normal;
+}
+
+.hours-row dd {
+ margin: 0;
+ text-align: right;
+ color: var(--ink);
+}
+
+.find-us .pin {
+ width: 2.125rem;
+ height: 2.125rem;
+ margin-bottom: 1rem;
+}
+
+.find-us address {
+ font-style: normal;
+ font-size: 1rem;
+ color: var(--ink);
+}
+
+.find-us .contact-list {
+ margin-top: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.6rem;
+}
+
+.find-us .contact-list a {
+ text-decoration: none;
+ color: var(--gold-bright);
+ font-size: 0.92rem;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ width: fit-content;
+ border-bottom: 1px solid transparent;
+}
+
+.find-us .contact-list a:hover {
+ border-bottom-color: var(--gold-dim);
+}
+
+@media (max-width: 45em) {
+ .visit-grid {
+ grid-template-columns: 1fr;
+ }
+}
+/* live music band */
+.music {
+ background: linear-gradient(180deg, var(--green-deep), var(--green));
+ color: var(--ink);
+ text-align: center;
+}
+
+.music-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 3.5rem;
+ align-items: center;
+ text-align: left;
+ margin-top: 1.5rem;
+}
+
+.music-content {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+.music-photo-wrap {
+ position: relative;
+ border-radius: 0.25rem;
+ overflow: hidden;
+ border: 1px solid rgba(201, 163, 92, 0.35);
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.45);
+}
+
+.music-photo {
+ width: 100%;
+ height: auto;
+ display: block;
+ object-fit: cover;
+ aspect-ratio: 4/3;
+ object-position: center top;
+}
+
+.music .section-head {
+ margin-bottom: 1.5rem;
+ text-align: left;
+}
+
+.music .section-head .eyebrow {
+ color: var(--gold-bright);
+}
+
+.music p.body {
+ max-width: 58ch;
+ margin: 0;
+ font-size: 1.02rem;
+ color: #e4ddc9;
+ line-height: 1.65;
+}
+
+.music .btn-outline {
+ border-color: rgba(230, 199, 137, 0.45);
+}
+
+.music .hero-ctas {
+ margin-top: 2rem;
+ justify-content: flex-start;
+}
+
+@media (max-width: 50em) {
+ .music-grid {
+ grid-template-columns: 1fr;
+ gap: 2.5rem;
+ text-align: center;
+ }
+ .music-content {
+ align-items: center;
+ }
+ .music .section-head {
+ text-align: center;
+ }
+ .music .hero-ctas {
+ justify-content: center;
+ }
+}
+/* menu band */
+.menu-band .container {
+ max-width: 60rem;
+ text-align: center;
+}
+
+.menu-band p.body {
+ font-size: 1.02rem;
+ color: var(--ink-muted);
+ max-width: 56ch;
+ margin: 0 auto;
+}
+
+.menu-band .hero-ctas {
+ margin-top: 2.25rem;
+ justify-content: center;
+}
+
+.menu-band .section-head {
+ text-align: center;
+}
+
+.menu-band .rule-row {
+ margin-bottom: 1.75rem;
+}
+
+/* public online menu section */
+.menu-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
+ gap: 2.5rem;
+ margin-top: 3rem;
+ text-align: left;
+}
+
+/* background/border come from the shared .panel rule above; no
+ margin-bottom here since these sit in a grid whose own gap spaces them */
+.menu-category {
+ border-radius: 0.25rem;
+ padding: 2rem;
+}
+
+.menu-category-header {
+ margin-bottom: 1.5rem;
+ padding-bottom: 0.75rem;
+ border-bottom: 1px solid var(--rule);
+}
+
+.menu-category-header h3 {
+ font-size: 1.45rem;
+ color: var(--gold-bright);
+}
+
+.menu-category-header p {
+ margin-top: 0.35rem;
+ font-size: 0.88rem;
+ color: var(--ink-muted);
+}
+
+.menu-items-list {
+ display: flex;
+ flex-direction: column;
+ gap: 1.4rem;
+}
+
+.menu-item-row {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.menu-item-top {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ gap: 0.75rem;
+}
+
+.menu-item-name {
+ font-family: var(--font-serif);
+ font-weight: 600;
+ font-size: 1.05rem;
+ color: var(--ink);
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
+.menu-item-price {
+ font-family: var(--font-mono);
+ font-weight: 500;
+ font-size: 0.95rem;
+ color: var(--gold-bright);
+ white-space: nowrap;
+}
+
+.menu-item-desc {
+ font-size: 0.88rem;
+ color: var(--ink-muted);
+ line-height: 1.45;
+}
+
+.menu-item-badge {
+ font-family: var(--font-mono);
+ font-size: 0.65rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ padding: 0.15rem 0.45rem;
+ border-radius: 0.125rem;
+ background: rgba(201, 163, 92, 0.15);
+ border: 1px solid var(--gold-dim);
+ color: var(--gold-bright);
+ font-weight: normal;
+}
+
+/* menu page specific headers & spacing */
+.menu-page-header {
+ padding-block: 4.5rem 2.5rem;
+}
+
+.menu-page-content {
+ padding-bottom: 5.5rem;
+}
+
+/* ---------- admin / management styles ---------- */
+.admin-login-body {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+}
+
+.login-card {
+ border-radius: 0.25rem;
+ padding: 2.5rem 2rem;
+ width: 100%;
+ max-width: 25rem;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.4);
+}
+
+.login-header {
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+.login-header img {
+ width: 2rem;
+ height: 2rem;
+ margin: 0 auto 0.75rem;
+}
+
+.login-header h1 {
+ font-size: 1.5rem;
+ color: var(--gold-bright);
+}
+
+.login-header p {
+ font-family: var(--font-mono);
+ font-size: 0.72rem;
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ color: var(--ink-muted);
+ margin-top: 0.35rem;
+}
+
+.login-btn {
+ width: 100%;
+ justify-content: center;
+ margin-top: 0.5rem;
+}
+
+.back-link {
+ display: block;
+ text-align: center;
+ margin-top: 1.5rem;
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ color: var(--ink-muted);
+ text-decoration: none;
+}
+
+.back-link:hover {
+ color: var(--gold-bright);
+}
+
+/* two classes to reliably outrank .panel h2's margin-bottom above */
+.category-panel .cat-name-heading {
+ margin-bottom: 0.25rem;
+ grid-area: name;
+}
+
+.cat-desc-text {
+ font-size: 0.88rem;
+}
+
+.category-panel .cat-desc-text {
+ grid-area: desc;
+}
+
+/* Category panel-head as a grid: title on its own top row, description
+ and the move/edit/delete buttons sharing the row below with the
+ buttons pinned to the far end (matches the desktop arrangement this
+ replaced — see the mobile override for how that changes below 760px). */
+.category-panel .panel-head {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ grid-template-areas: "name actions" "desc actions";
+ align-items: center;
+ column-gap: 0.75rem;
+}
+
+.category-panel .panel-head .row-actions {
+ grid-area: actions;
+}
+
+/* Wraps the item name and its optional badge (reusing the public menu's
+ .menu-item-badge pill) so they sit on one inline-flowing line instead
+ of giving the badge its own column. */
+.item-name-row {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
+.item-desc-cell {
+ font-size: 0.82rem;
+ margin-top: 0.2rem;
+}
+
+.item-price-cell {
+ color: var(--gold-bright);
+ font-weight: 500;
+}
+
+/* mobile reflow for the category header — shares the breakpoint and
+ reflow approach used by the item/announcement/user list grids in
+ admin-data-grid, but this one rule is specific to the menu editor's
+ category panel, so it lives here instead of there. */
+@media (max-width: 47.5em) {
+ .category-panel .panel-head {
+ grid-template-columns: 1fr;
+ grid-template-areas: "name" "desc" "actions";
+ row-gap: 0.5rem;
+ }
+}
/* ---------- admin: pub hours ---------- */
/* Same subgrid technique as the item/ann/user lists above (see the
comment near .item-grid-wrap), but with no grid-template-areas: every