Skip to content

Commit 61c2f98

Browse files
committed
The panel fits the screen, and the screen stops scrolling
The overview ran to 1,436px against about 674px of usable height on the owner's monitor -- twice the screen -- and the window scrolled, which took the sidebar and the search box with it. Three changes, measured rather than guessed. The window no longer scrolls on a desktop. The shell is the height of the screen and the page area inside it does the scrolling, so the sidebar, the top bar and whatever is at the head of a panel stay put. A phone keeps ordinary page scrolling: a fixed-height shell there fights the address bar hiding and showing, and loses. The overview is two columns. Counters on the left, because they are a fixed block you scan; what needs doing on the right, because that is the part that grows. Each column scrolls on its own, so the overview is one screen whether there are two alerts or twenty. It was ten sections stacked down a 1,180px strip with a third of the monitor empty beside it; the width cap goes to 1,520px for the same reason the lists already had it -- counters and lists are not a paragraph. A density pass over the whole panel: padding, gaps, card and heading sizes. Nothing changed colour or disappeared. The lists fill the screen and scroll inside themselves. A fixed 72vh left dead space under the table on a tall monitor and cut it off on a short one; the panel, the view and the wrapper each hand their remaining height down, so the table ends where the screen does and the filters and pager stay on screen. Rows went from 63px to 42px. Measured at 1536x730 and 1920x950 by rendering the real markup against the real stylesheet: overview 1,436px -> 674px, fits exactly, window does not scroll bookings 4 rows and 60 rows: window still, pager on screen, table scrolls inside, header sticky over the rows a tall tab bottom still reachable at 1536x730, 1280x620, 390x780 Two of those checks failed first on the harness rather than the CSS -- forty cards in a grid fit in five rows and prove nothing about scrolling -- and were rewritten before they passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0128YzbhrfGdegUSc9RrARRf
1 parent 0514765 commit 61c2f98

3 files changed

Lines changed: 179 additions & 56 deletions

File tree

‎admin/admin.css‎

Lines changed: 147 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,24 @@ button { font-family: inherit; }
121121

122122
.ns { display: grid; grid-template-columns: var(--side, 244px) minmax(0, 1fr); min-height: 100vh; }
123123

124+
/* ---- the window itself does not scroll ----
125+
On a desktop the shell is the height of the screen and the page area inside
126+
it does the scrolling. The sidebar and the top bar then stay where they
127+
are, instead of the whole document sliding and taking the navigation with
128+
it -- which is what made a long tab feel like a web page rather than a
129+
panel. A phone keeps ordinary page scrolling: a fixed-height shell there
130+
fights the address bar hiding and showing, and loses. */
131+
@media (min-width: 1024px) {
132+
html, body { height: 100%; overflow: hidden; }
133+
.ns { height: 100dvh; min-height: 0; }
134+
.ns-main { min-height: 0; overflow: hidden; }
135+
.ns-content {
136+
min-height: 0;
137+
overflow-y: auto;
138+
overscroll-behavior: contain;
139+
}
140+
}
141+
124142
/* ---- the column ---- */
125143
.ns-side {
126144
grid-column: 1; background: var(--primary); color: #fff;
@@ -315,7 +333,7 @@ button { font-family: inherit; }
315333
outline: 2px solid var(--primary); outline-offset: 1px; opacity: 1;
316334
}
317335

318-
.ns-content { flex: 1; width: 100%; max-width: 1180px; padding: 24px; }
336+
.ns-content { flex: 1; width: 100%; max-width: 1180px; padding: 16px 20px; }
319337

320338
/* Except on a list. 1180px is a reading measure -- the width at which a
321339
paragraph or a form stays comfortable -- and a records table is neither. At
@@ -325,6 +343,11 @@ button { font-family: inherit; }
325343
and on any screen too narrow to use it, nothing changes. */
326344
.ns-content:has(.admin-panel:not([hidden]) .rec-tablewrap:not([hidden] *)) { max-width: 1520px; }
327345

346+
/* The overview too, and for the same reason: it is counters and lists, not a
347+
paragraph. Held to a reading measure it stacked ten sections down a narrow
348+
strip with a third of the monitor empty beside it. */
349+
.ns-content:has(#panel-dashboard:not([hidden])) { max-width: 1520px; }
350+
328351
/* The scrim only exists while the drawer is open, and only on a phone. */
329352
.ns-scrim { display: none; }
330353

@@ -357,17 +380,17 @@ button { font-family: inherit; }
357380
}
358381

359382
.panel-header {
360-
display: flex; justify-content: space-between; align-items: flex-end; flex-wrap: wrap; gap: 12px;
361-
margin-bottom: 14px;
383+
display: flex; justify-content: space-between; align-items: flex-end; flex-wrap: wrap; gap: 10px;
384+
margin-bottom: 9px;
362385
}
363386
/* Heading and description on one line. They were stacked, which cost a whole
364387
line of height at the top of every panel for a sentence that is context
365388
rather than content. */
366389
.panel-header > div {
367390
display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; min-width: 0;
368391
}
369-
.panel-header h2 { font-size: 1.15rem; margin-bottom: 0; }
370-
.panel-header p { color: var(--ink-dim); font-size: 0.84rem; }
392+
.panel-header h2 { font-size: 1.05rem; margin-bottom: 0; }
393+
.panel-header p { color: var(--ink-dim); font-size: 0.78rem; }
371394

372395
/* Shown only when a migration could not be applied, which is rare and worth
373396
interrupting for: the panel works, but something in it will not. */
@@ -605,22 +628,23 @@ button { font-family: inherit; }
605628

606629
/* ---------- Dashboard stats ---------- */
607630
.stat-grid {
608-
display: grid; grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
609-
gap: 10px; margin-bottom: 18px;
631+
display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
632+
gap: 7px; margin-bottom: 12px;
610633
}
611634
.stat-grid-3 { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); margin-bottom: 16px; }
612635
.stat-card {
613-
background: #fff; border: 1px solid var(--border); border-radius: var(--radius);
614-
padding: 11px 13px; display: flex; flex-direction: column; gap: 3px;
636+
background: #fff; border: 1px solid var(--border); border-radius: 10px;
637+
padding: 7px 10px; display: flex; flex-direction: column; gap: 1px;
638+
line-height: 1.3;
615639
}
616640
/* The plate on a counter card: top right, out of the way of the number, which
617641
is what anybody is here to read. Only the five cards that carry one -- the
618642
rest of the grid keeps its own rhythm without a hole where an icon is not. */
619643
.stat-card .ns-ico3d {
620-
position: absolute; top: 9px; right: 10px;
644+
position: absolute; top: 6px; right: 8px;
621645
transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
622646
}
623-
.stat-card:has(.ns-ico3d) { position: relative; padding-right: 52px; }
647+
.stat-card:has(.ns-ico3d) { position: relative; padding-right: 44px; }
624648
.stat-card:hover .ns-ico3d { transform: translateY(-2px) scale(1.05); }
625649

626650
/* The sidebar's plates sit a hair lower than a hairline did, and brighten
@@ -635,21 +659,62 @@ button { font-family: inherit; }
635659
small, and it was already being used at 0.78rem. Shrinking it further would
636660
have made a failing contrast worse, so the label moves to --ink-dim at
637661
7.54:1. */
638-
.stat-label { font-size: 0.72rem; color: var(--ink-dim); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; }
662+
.stat-label { font-size: 0.66rem; color: var(--ink-dim); font-weight: 600; text-transform: uppercase; letter-spacing: 0.02em; }
639663
/* margin-top:auto: grid stretches every card in a row to the tallest one, so a
640664
label that wraps to two lines ("Security deposit refunded") left its
641665
neighbours' numbers floating mid-card. Pinning the value to the bottom lines
642666
the numbers up across the row again. */
643667
.stat-value {
644-
font-size: 1.15rem; font-weight: 800; color: var(--ink);
668+
font-size: 1rem; font-weight: 800; color: var(--ink);
645669
font-variant-numeric: tabular-nums; margin-top: auto;
646670
}
647671
.stat-card-income .stat-value { color: #2F6B4F; }
648672
.stat-card-expense .stat-value { color: var(--danger); }
649673
.stat-card-balance .stat-value { color: var(--primary); }
650674

651-
.dashboard-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
652-
.dashboard-col h3 { font-size: 0.95rem; margin-bottom: 8px; }
675+
.dashboard-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
676+
677+
/* ---------- The overview, as two columns that fill the screen ----------
678+
The panel is a column: a fixed head (title, search, the six buttons) and a
679+
body that takes whatever height is left. The body's two columns scroll on
680+
their own, so the overview is exactly one screen whether there are two
681+
alerts or twenty -- and the head, with the search box in it, never scrolls
682+
away from under the cursor.
683+
684+
Only where there is height to divide. A short window and a phone get the
685+
plain stacked column back, because two 200px-tall scrolling panes side by
686+
side is a worse way to read this than one honest list. */
687+
@media (min-width: 1024px) and (min-height: 620px) {
688+
.ns-content > #panel-dashboard:not([hidden]) {
689+
display: flex;
690+
flex-direction: column;
691+
height: 100%;
692+
min-height: 0;
693+
}
694+
.dash-body {
695+
flex: 1 1 auto;
696+
min-height: 0;
697+
display: grid;
698+
grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
699+
gap: 14px;
700+
}
701+
.dash-col-main, .dash-col-side {
702+
min-height: 0;
703+
overflow-y: auto;
704+
overscroll-behavior: contain;
705+
/* Room for the scrollbar so a column that has one does not jump its
706+
contents sideways against one that does not. */
707+
padding-right: 4px;
708+
}
709+
/* Side by side the two recent lists are too narrow to read, so in the
710+
right-hand column they stack. */
711+
.dash-col-side .dashboard-cols { grid-template-columns: 1fr; }
712+
/* The last thing in a column needs no gap under it; the column's own
713+
scroll end is the edge. */
714+
.dash-col-main > *:last-child,
715+
.dash-col-side > *:last-child { margin-bottom: 0; }
716+
}
717+
.dashboard-col h3 { font-size: 0.85rem; margin-bottom: 5px; }
653718

654719
/* ---------- Finance ---------- */
655720
.finance-form {
@@ -792,10 +857,10 @@ label.finance-toggle {
792857
.modal-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px; margin-top: 6px; }
793858

794859
/* ---------- Dashboard: rental overview ---------- */
795-
.dashboard-subheading { font-size: 0.95rem; margin: 2px 0 10px; }
860+
.dashboard-subheading { font-size: 0.82rem; margin: 0 0 5px; }
796861
/* Was a fixed four columns. Auto-fill lets a wide screen show more and a
797862
narrow one fewer, without a breakpoint for each size. */
798-
.stat-grid-4 { grid-template-columns: repeat(auto-fill, minmax(158px, 1fr)); }
863+
.stat-grid-4 { grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); }
799864
.stat-card-deposit .stat-value { color: #8A5A00; }
800865

801866
/* ---------- Wide/tall modal variants ---------- */
@@ -813,10 +878,10 @@ label.finance-toggle {
813878
color: var(--danger); font-size: 0.85rem; font-weight: 600; margin: -6px 0 14px; min-height: 1.2em;
814879
}
815880
/* One box that finds anything, and the list it drops. */
816-
.search-wrap { position: relative; margin-bottom: 14px; }
881+
.search-wrap { position: relative; margin-bottom: 10px; }
817882
.search-box {
818-
width: 100%; padding: 11px 14px; font-size: 0.95rem;
819-
border: 1px solid var(--border); border-radius: 12px;
883+
width: 100%; padding: 8px 12px; font-size: 0.9rem;
884+
border: 1px solid var(--border); border-radius: 10px;
820885
background: var(--surface); color: var(--ink);
821886
}
822887
.search-box:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
@@ -843,34 +908,35 @@ label.finance-toggle {
843908
.search-empty { padding: 12px 14px; color: var(--ink-dim); font-size: 0.9rem; }
844909

845910
/* The six things started most often, each of which was three clicks away. */
846-
.quick-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
911+
.quick-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
847912

848913
/* The shape of the day. Apart from "Needs attention" on purpose: three
849914
pickups is not a problem, it is a morning. */
850-
.today-ops { margin-bottom: 22px; }
915+
.today-ops { margin-bottom: 12px; }
851916
.ops-grid {
852-
display: grid; gap: 10px; margin-top: 8px;
853-
grid-template-columns: repeat(auto-fit, minmax(135px, 1fr));
917+
display: grid; gap: 7px; margin-top: 6px;
918+
grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
854919
}
855920
.ops-card {
856-
border: 1px solid var(--border); border-radius: 12px;
857-
background: var(--surface); padding: 12px 14px;
858-
display: flex; flex-direction: column; gap: 2px;
921+
border: 1px solid var(--border); border-radius: 10px;
922+
background: var(--surface); padding: 7px 10px;
923+
display: flex; flex-direction: column-reverse; gap: 0;
859924
}
860-
.ops-value { font-size: 1.35rem; font-weight: 800; color: var(--ink); }
861-
.ops-label { font-size: 0.78rem; color: var(--ink-dim); }
925+
.ops-value { font-size: 1.1rem; font-weight: 800; color: var(--ink); line-height: 1.25; }
926+
.ops-label { font-size: 0.7rem; color: var(--ink-dim); line-height: 1.3; }
862927

863928
/* The amount a customer actually pays, beside the discount that made it. */
864929
.final-price { font-size: 1.25rem; font-weight: 800; color: var(--ink); margin: 6px 0 0; }
865930

866931
/* What needs doing, above the figures. Colour carries the urgency and a word
867932
carries it too, because colour alone is not a message to everyone. */
868-
.alerts { margin-bottom: 22px; }
869-
.alert-list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
933+
.alerts { margin-bottom: 12px; }
934+
.alert-list { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
870935
.alert-row {
871-
display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
872-
border: 1px solid var(--border); border-left-width: 4px; border-radius: 10px;
873-
padding: 9px 12px; background: var(--surface); font-size: 0.9rem;
936+
display: flex; align-items: center; gap: 8px; flex-wrap: nowrap;
937+
border: 1px solid var(--border); border-left-width: 4px; border-radius: 9px;
938+
padding: 5px 10px; background: var(--surface); font-size: 0.84rem;
939+
line-height: 1.35;
874940
}
875941
.alert-row.is-overdue { border-left-color: var(--danger); }
876942
.alert-row.is-soon { border-left-color: var(--accent); }
@@ -1902,13 +1968,54 @@ label.finance-toggle {
19021968
overscroll-behavior: contain;
19031969
}
19041970

1971+
/* ---- a list fills the screen, and scrolls inside itself ----
1972+
With the window no longer scrolling, a fixed 72vh left dead space under
1973+
the table on a tall monitor and still cut it off on a short one. The
1974+
panel, the view and the wrapper around the list are each a column that
1975+
gives its remaining height to the one below, so the table ends where the
1976+
screen does: the filters and the pager stay put, the rows move, and the
1977+
sticky header stays over them.
1978+
1979+
:has() does the choosing, so this reaches the bookings list and the
1980+
enquiries list without either of them needing a class of its own, and
1981+
leaves the detail view -- which is a page of prose and photographs, and
1982+
wants to scroll normally -- alone. */
1983+
@media (min-width: 1024px) {
1984+
.ns-content > .admin-panel:not([hidden]):has(> div:not([hidden]) .rec-tablewrap) {
1985+
display: flex;
1986+
flex-direction: column;
1987+
height: 100%;
1988+
min-height: 0;
1989+
}
1990+
.admin-panel > div:not([hidden]):has(.rec-tablewrap) {
1991+
display: flex;
1992+
flex-direction: column;
1993+
flex: 1 1 auto;
1994+
min-height: 0;
1995+
}
1996+
.admin-panel div:not([hidden]) > .rec-tablewrap {
1997+
flex: 1 1 auto;
1998+
min-height: 140px;
1999+
max-height: none;
2000+
}
2001+
/* The wrapper the list is written into sits between the two and would
2002+
otherwise be a block of its own height. */
2003+
#bookingListWrap, #inquiriesWrap {
2004+
display: flex;
2005+
flex-direction: column;
2006+
flex: 1 1 auto;
2007+
min-height: 0;
2008+
}
2009+
.rec-pager { flex: none; }
2010+
}
2011+
19052012
/* separate, not collapse: a collapsed border belongs to the table rather than
19062013
to the cell, and a sticky cell carries its own borders with it or carries
19072014
none at all. With no vertical rules in this table the two look identical. */
1908-
.rec-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 0.86rem; }
2015+
.rec-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 0.82rem; line-height: 1.35; }
19092016
.rec-table th {
1910-
text-align: left; padding: 13px 12px; white-space: nowrap;
1911-
font-size: 0.71rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
2017+
text-align: left; padding: 7px 10px; white-space: nowrap;
2018+
font-size: 0.67rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
19122019
color: var(--ink-faint); border-bottom: 1px solid var(--border); background: var(--surface);
19132020
/* Stays on screen while the rows go past it. A column of dates with no
19142021
heading is a column of numbers. */
@@ -1918,14 +2025,14 @@ label.finance-toggle {
19182025
.rec-table th.is-sortable:hover { color: var(--ink); }
19192026
.rec-table th .ord { opacity: 0.4; margin-left: 4px; font-size: 0.8em; }
19202027
.rec-table th.is-sorted .ord { opacity: 1; color: var(--accent-deep); }
1921-
.rec-table td { padding: 14px 12px; border-bottom: 1px solid var(--border); vertical-align: middle; }
2028+
.rec-table td { padding: 5px 10px; border-bottom: 1px solid var(--border); vertical-align: middle; }
19222029
.rec-table tbody tr { cursor: pointer; }
19232030
.rec-table tbody tr:hover { background: var(--bg); }
19242031
.rec-table tbody tr:last-child td { border-bottom: 0; }
19252032
.rec-table .rec-sno { color: var(--ink-faint); width: 1%; }
19262033
.rec-table .rec-id { font-weight: 800; color: var(--accent-deep); white-space: nowrap; }
1927-
.rec-table .rec-who { font-weight: 700; color: var(--ink); }
1928-
.rec-table .rec-sub { color: var(--ink-dim); font-size: 0.82rem; }
2034+
.rec-table .rec-who { font-weight: 700; color: var(--ink); line-height: 1.25; }
2035+
.rec-table .rec-sub { color: var(--ink-dim); font-size: 0.74rem; line-height: 1.2; }
19292036
.rec-table .rec-money { font-weight: 700; white-space: nowrap; }
19302037
/* A date is one thing. Left to wrap it became "28 Sept" over "2026", which
19312038
cost the row a line for nothing. */

‎admin/dashboard.php‎

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ function dial_select(string $id, string $selected = DIAL_DEFAULT): string
7373
<button class="btn btn-outline btn-sm" data-quick="finance">Finance</button>
7474
</div>
7575

76+
<!-- Everything below the search splits into two columns that each
77+
scroll on their own, so the overview is one screen however many
78+
alerts there are. It was a single narrow column of ten stacked
79+
sections, which on a wide monitor ran to twice the height of the
80+
screen with half the width unused. Counts on the left, because
81+
they are a fixed block you scan; what needs doing on the right,
82+
because it is the part that grows. -->
83+
<div class="dash-body">
84+
<div class="dash-col-main">
85+
7686
<!-- The shape of the day, before any of the totals. Three pickups is
7787
not a problem, it is a morning -- which is why these sit apart
7888
from "Needs attention". -->
@@ -89,14 +99,6 @@ function dial_select(string $id, string $selected = DIAL_DEFAULT): string
8999
</div>
90100
</section>
91101

92-
<!-- What needs doing, above the figures.
93-
A number tells you where the business stands; these tell you what
94-
is about to go wrong, and only one of the two has a deadline. -->
95-
<section class="alerts" id="alertsPanel" hidden>
96-
<h3 class="dashboard-subheading">Needs attention</h3>
97-
<ul class="alert-list" id="alertList"></ul>
98-
</section>
99-
100102
<div class="stat-grid">
101103
<div class="stat-card">
102104
<?= admin_icon3d('car', 34) ?>
@@ -154,10 +156,24 @@ function dial_select(string $id, string $selected = DIAL_DEFAULT): string
154156
<div class="stat-card stat-card-balance"><span class="stat-label">Net Revenue</span><span class="stat-value" id="statNetRevenue">₹0</span></div>
155157
</div>
156158

157-
<div class="dashboard-col">
158-
<h3>Active &amp; Upcoming Rentals</h3>
159-
<div id="activeUpcomingBookings"></div>
160-
</div>
159+
</div><!-- /dash-col-main -->
160+
161+
<div class="dash-col-side">
162+
<!-- What needs doing, above the figures.
163+
A number tells you where the business stands; these tell you
164+
what is about to go wrong, and only one of the two has a
165+
deadline. -->
166+
<section class="alerts" id="alertsPanel" hidden>
167+
<h3 class="dashboard-subheading">Needs attention</h3>
168+
<ul class="alert-list" id="alertList"></ul>
169+
</section>
170+
171+
<div class="dashboard-col">
172+
<h3>Active &amp; Upcoming Rentals</h3>
173+
<div id="activeUpcomingBookings"></div>
174+
</div>
175+
</div><!-- /dash-col-side -->
176+
</div><!-- /dash-body -->
161177
</section>
162178

163179
<!-- Bookings tab -->

‎build.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"publishedAt": "2026-10-15T08:00:27.860Z",
3-
"commit": "c6ec73a8",
4-
"commitAt": "2026-10-15T04:22:50+00:00",
5-
"sourceHash": "7bf1c69e6968ead1",
2+
"publishedAt": "2026-10-15T11:13:00.747Z",
3+
"commit": "05147656",
4+
"commitAt": "2026-10-15T08:00:46+00:00",
5+
"sourceHash": "d1a87e423f216b8e",
66
"assets": [
77
"About-DDYn5471.js",
88
"Bikes-1VLOLJDp.js",

0 commit comments

Comments
 (0)