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>
This commit is contained in:
1 parent
fd928a24cf
commit
eab20899de
14 files changed
+1224
-441
No files matched your search
+335
-175
@@ -87,18 +87,6 @@
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
@@ -170,10 +158,6 @@
|
||||
background: linear-gradient(90deg, transparent, var(--gold-dim), transparent);
|
||||
}
|
||||
|
||||
.rule-row.left-align {
|
||||
margin-inline: 0;
|
||||
}
|
||||
|
||||
/* ---------- announcement banner ---------- */
|
||||
.announcement-banner {
|
||||
background: var(--ground-2);
|
||||
@@ -1123,15 +1107,6 @@
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: rgba(51, 80, 63, 0.25);
|
||||
border: 1px solid var(--green);
|
||||
color: #bbf7d0;
|
||||
padding: 0.9rem 1.25rem;
|
||||
border-radius: 0.125rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
@@ -1320,20 +1295,19 @@
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
/* The menu admin's status badge doubles as its own availability toggle
|
||||
(see .btn-toggle-item) — a plain <button> would otherwise pick up the
|
||||
/* 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 the read-only status badges elsewhere
|
||||
(e.g. the announcements table) aren't affected. */
|
||||
/* Fixed to fit its longest possible label ("● Available", ~6.7rem
|
||||
measured) so toggling doesn't change the badge's — and therefore the
|
||||
Status column's — width and shove every other column sideways. */
|
||||
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;
|
||||
width: 7rem;
|
||||
transition: opacity .15s ease;
|
||||
}
|
||||
|
||||
@@ -1346,40 +1320,22 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* admin table & list actions */
|
||||
.admin-table-wrap {
|
||||
overflow-x: auto;
|
||||
margin-top: 1rem;
|
||||
/* "● Available" is the longest menu item label, ~6.7rem measured. */
|
||||
.item-row button.status-badge {
|
||||
width: 7rem;
|
||||
}
|
||||
|
||||
.admin-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.92rem;
|
||||
/* "⏳ Scheduled" is the longest announcement label, ~7rem measured. */
|
||||
.ann-row button.status-badge {
|
||||
width: 7.25rem;
|
||||
}
|
||||
|
||||
.admin-table th,
|
||||
.admin-table td {
|
||||
padding: 0.85rem 0.75rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.admin-table th {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gold);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.admin-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.admin-table-actions {
|
||||
/* 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;
|
||||
@@ -1387,86 +1343,24 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.admin-table-actions .btn {
|
||||
.row-actions .btn {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Groups the item row's move (↑/↓) and edit/delete controls into two
|
||||
narrow vertical stacks instead of four buttons side by side — keeps
|
||||
the Actions column (and the table as a whole) from running wide. */
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* Below ~760px a plain <table> forces long text (item descriptions,
|
||||
headlines) into a column so narrow it wraps one word per line. Recast
|
||||
each row as a labeled card instead — the label comes from the td's
|
||||
data-label attribute, kept in sync with the <th> text in each admin
|
||||
page (and its matching *.js row template, where rows are rebuilt
|
||||
client-side). */
|
||||
@media (max-width: 47.5em) {
|
||||
.admin-table-wrap {
|
||||
overflow-x: visible;
|
||||
}
|
||||
|
||||
.admin-table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.admin-table,
|
||||
.admin-table tbody,
|
||||
.admin-table tr,
|
||||
.admin-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.admin-table tr {
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.9rem 1rem;
|
||||
margin-bottom: 0.85rem;
|
||||
}
|
||||
|
||||
.admin-table tr:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.admin-table td {
|
||||
padding: 0.4rem 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.admin-table td[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;
|
||||
}
|
||||
|
||||
.admin-table-actions {
|
||||
.row-actions {
|
||||
flex-wrap: wrap;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
/* Right-align the action buttons instead of leaving them stranded at
|
||||
the card's left edge — item rows (an inline-flex box in a
|
||||
full-width block <td>, so text-align positions it) and category
|
||||
headers (a flex item in .panel-head, so margin-left:auto pushes it
|
||||
to the end of its line) need different techniques to get there. */
|
||||
.admin-table td[data-label="Actions"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.panel-head .admin-table-actions {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
@@ -1623,15 +1517,12 @@
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* admin menu grid: category panel header + item row formatting.
|
||||
Shared between admin/menu.php's server render and admin/menu.js's
|
||||
client-side row/panel templates, so it lives here once instead of
|
||||
being duplicated as inline styles in both places.
|
||||
|
||||
This — not <table>/<tr>/<td> — is the item list's actual layout.
|
||||
Desktop and mobile each get their own grid-template-areas map (see the
|
||||
mobile override below), so the two can be redesigned independently
|
||||
instead of one being a display:block hack forced onto the other. */
|
||||
/* 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);
|
||||
}
|
||||
@@ -1664,27 +1555,56 @@
|
||||
column-gap: 0.75rem;
|
||||
}
|
||||
|
||||
.category-panel .panel-head .admin-table-actions {
|
||||
.category-panel .panel-head .row-actions {
|
||||
grid-area: actions;
|
||||
}
|
||||
|
||||
/* Each category's item list: a header row of column labels (desktop
|
||||
only — hidden on mobile below) plus one grid per item row, all
|
||||
sharing the same column template so they line up. */
|
||||
/* 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-grid-head,
|
||||
.item-row {
|
||||
.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-template-columns: auto minmax(0, 1fr) auto auto;
|
||||
grid-template-areas: "status item price actions";
|
||||
column-gap: 0.75rem;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item-grid-head {
|
||||
.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);
|
||||
@@ -1695,26 +1615,73 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.item-row {
|
||||
.item-row,
|
||||
.ann-row,
|
||||
.user-row {
|
||||
padding: 0.85rem 0.75rem;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.item-rows .item-row:last-child {
|
||||
.item-row:last-child,
|
||||
.ann-row:last-child,
|
||||
.user-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.col-status { grid-area: status; }
|
||||
/* 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; }
|
||||
.col-actions { grid-area: actions; }
|
||||
|
||||
.item-row [data-label="Status"] { grid-area: status; }
|
||||
.item-row [data-label="Item"] { grid-area: item; }
|
||||
.item-row [data-label="Price"] { grid-area: price; }
|
||||
.item-row [data-label="Actions"] { grid-area: actions; }
|
||||
|
||||
.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;
|
||||
}
|
||||
@@ -1739,29 +1706,41 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.item-sort-cell {
|
||||
.timestamp-cell {
|
||||
color: var(--ink-muted);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Menu admin grid: its own mobile arrangement, independent of the
|
||||
desktop one above — this is the whole point of grid-template-areas
|
||||
over the <table> hack. Same fields, different map. Placed after the
|
||||
desktop rules above (rather than in the earlier shared .admin-table
|
||||
mobile block) so it wins the cascade at equal specificity — a rule's
|
||||
source position still matters once a media query lets both compete. */
|
||||
/* 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) {
|
||||
.item-grid-head {
|
||||
/* 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 {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-areas:
|
||||
"status"
|
||||
"item"
|
||||
"price"
|
||||
"actions";
|
||||
.item-row,
|
||||
.ann-row,
|
||||
.user-row {
|
||||
row-gap: 0.4rem;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 0.25rem;
|
||||
@@ -1769,12 +1748,16 @@
|
||||
margin-bottom: 0.85rem;
|
||||
}
|
||||
|
||||
.item-rows .item-row:last-child {
|
||||
.item-row:last-child,
|
||||
.ann-row:last-child,
|
||||
.user-row:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.item-row [data-label]::before {
|
||||
.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);
|
||||
@@ -1785,8 +1768,30 @@
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.item-row [data-label="Actions"] {
|
||||
justify-self: end;
|
||||
.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 {
|
||||
@@ -1861,6 +1866,7 @@
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
line-height: 1;
|
||||
text-decoration: none;
|
||||
transition: color .15s ease;
|
||||
}
|
||||
|
||||
@@ -1921,4 +1927,158 @@
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user