Files
theoldfitz/styles.css
T
David DyerandClaude Sonnet 5 eab20899de Add pub hours admin panel, rebuild announcements/users lists on CSS Grid
- Add a Hours admin panel (admin/hours.php, admin/hours.js) for editing
  the weekly open/close schedule and an optional per-day kitchen closing
  time, with a live preview of how it'll read on the public site
- Store hours in a new pub_hours table (inc/db.php), seeded with the
  site's existing Thu-Sat/9pm-kitchen schedule; inc/hours.php formats
  and groups it for display (day ranges, kitchen-close note, footer
  summary)
- Wire the public Visit card and both site footers (index.php, menu.php)
  to read hours from that data instead of hardcoded copy
- Rebuild the Announcements and Users admin tables onto the same CSS
  Grid list pattern already used for the Menu (admin-table-actions ->
  row-actions), and make the Announcements status toggle an in-place
  AJAX action like the menu item's (admin/index.js)
- Add the Hours tab to admin nav across all four admin pages

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 11:18:23 -04:00

2084 lines
43 KiB
CSS

: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;
}
}
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;
gap: 0.6rem 0;
font-family: var(--font-mono);
font-size: 0.82rem;
letter-spacing: 0.04em;
color: var(--ink-muted);
}
.tagline-row span:not(:last-child)::after {
content: "·";
color: var(--gold-dim);
margin-inline: 0.7rem;
}
.hero-ctas {
margin-top: 2.5rem;
display: flex;
justify-content: center;
gap: 0.9rem;
flex-wrap: wrap;
}
.btn {
display: inline-flex;
align-items: 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-card .stat-row {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--rule);
}
.stat-card .stat-row .stat-big {
font-size: 1.9rem;
}
.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 <button> 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 <span>. 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;
}
/* 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 <table> 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 <table> — 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: 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: "username role created actions";
}
.col-status,
.item-row [data-label="Status"],
.ann-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
<table>. 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). */
.item-grid-wrap,
.ann-grid-wrap,
.user-grid-wrap {
grid-template-columns: 1fr;
}
.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"
"item"
"price"
"actions";
}
.ann-row {
grid-template-areas:
"status"
"headline"
"type"
"schedule"
"placements"
"actions";
}
.user-row {
grid-template-areas:
"username"
"role"
"created"
"actions";
}
.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;
}
}