aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/style.css
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/style.css')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/style.css301
1 files changed, 292 insertions, 9 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index 5e08a6a..774c9bc 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -51,6 +51,28 @@
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.3);
}
+/* ── Metrics ──────────────────────────────────────────────────────────────── */
+
+/* Not a palette: these are the layout's own numbers, so they sit outside the
+ theme blocks and neither theme overrides them.
+
+ `--nav-h` is the height of `.nav`, which is `position: sticky` at the top of
+ every page — the offset everything else pins beneath. The other two are
+ *measured* at runtime by sticky.js and published onto the page's root
+ element, which shadows the value declared here. They are declared anyway, and
+ as `0px` rather than as nothing: an undefined custom property invalidates the
+ whole `calc()` that names it (test_css_variables.py has the story), so
+ without these a toolbar would silently lose its `top` — and lose it exactly
+ in the window between the first paint and the first measurement, which is
+ also the window a screenshot is most likely to catch. At `0px` the band pins
+ directly under the navigation bar instead, which is where it belongs when
+ there is nothing above it. */
+:root {
+ --nav-h: 52px;
+ --chrome-h: 0px;
+ --toolbar-h: 0px;
+}
+
/* ── Reset ────────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
@@ -96,7 +118,12 @@ a:hover { text-decoration: underline; }
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg-raised);
- margin-bottom: 12px;
+ /* A pinned band keeps this gap: `--band-margin` is both the margin in
+ the flow and the width of the opaque ring the band paints around
+ itself once pinned, so the two can never disagree (style.css,
+ "Sticky chrome"). */
+ --band-margin: 12px;
+ margin-bottom: var(--band-margin);
}
.search-bar .icon { width: 16px; height: 16px; flex-shrink: 0; color: var(--text-dim); }
.search-bar input {
@@ -454,6 +481,103 @@ a:hover { text-decoration: underline; }
visible and removes the duplicated arithmetic rather than adding more. */
.main:has(> .page-center) { padding: 0; }
+/* ── Sticky chrome ────────────────────────────────────────────────────────── */
+
+/* Files, Videos, Music and Photos are read by scrolling, and until now the
+ controls that steer that reading went with the first screenful: which
+ application, where in the tree, which filter, which column sorts. They pin
+ under the navigation bar instead, in three bands, on the two pages that show
+ those applications — a group (`group-page.js`) and Search (`search-page.js`),
+ both of which mark their root `.sticky-chrome`:
+
+ .nav 52px, already sticky, --nav-h
+ .group-tabs / .search-bar which application / what is being searched
+ the application's toolbar where in the tree, the filter, the actions
+ .file-table th the column heads
+
+ The group's name and description are deliberately *not* in the stack. They
+ say nothing a reader needs while walking a directory, and the height they
+ would cost is height the list does not get.
+
+ Each band pins below the ones above it, so its `top` is their heights added
+ up — heights that change with the window (`.video-toolbar` wraps on a phone,
+ by design), with the state (naming a folder grows the toolbar; `hideFilter`
+ shrinks it) and with the theme's font. sticky.js measures them and publishes
+ `--chrome-h` and `--toolbar-h`; everything here reads those rather than
+ repeating a number that would be right in one file and wrong in another.
+
+ `>` throughout, and not by accident: a band publishes its height onto its
+ own parent, so these rules and sticky.js are asserting the same structural
+ fact — these elements are children of the page root. Nest one deeper and
+ both stop applying, rather than the stylesheet pinning something the script
+ is no longer measuring.
+
+ `.file-toolbar` is also the toolbar of the public directory
+ (`explore-page.js`), which is not one of these pages and is left alone. */
+
+.sticky-chrome > .group-tabs,
+.sticky-chrome > .search-bar {
+ position: sticky;
+ top: var(--nav-h);
+ z-index: 30;
+}
+
+.sticky-chrome > .file-toolbar,
+.sticky-chrome > .video-toolbar,
+.sticky-chrome > .photo-toolbar,
+.sticky-chrome > .photo-album-bar {
+ position: sticky;
+ top: calc(var(--nav-h) + var(--chrome-h));
+ z-index: 20;
+}
+
+.sticky-chrome > .file-table th {
+ position: sticky;
+ top: calc(var(--nav-h) + var(--chrome-h) + var(--toolbar-h));
+ z-index: 10;
+}
+
+/* A pinned band has the list running underneath it and must be opaque, which
+ most of these were not: only `.search-bar` and `.file-toolbar` came with a
+ surface of their own. So the bare bands take the page colour outright. */
+.sticky-chrome > .group-tabs,
+.sticky-chrome > .video-toolbar,
+.sticky-chrome > .photo-toolbar,
+.sticky-chrome > .photo-album-bar,
+.sticky-chrome > .file-table th {
+ background: var(--bg-base);
+}
+
+/* **The gap a band keeps when it pins.** In the flow every band is followed by
+ a margin — 16px under the tab bar, 12px under the search field and the file
+ toolbar, 14px under the media ones. Pinning absorbs it: the band below stops
+ at the bottom edge of the band above and the two come into contact. Reported
+ on both counts, and they are the same fault — two rounded panels flush
+ against each other in Files read as *encastrés*, and the row of controls in
+ Videos/Music/Photos reads as glued to the tab bar.
+
+ So each band paints its own margin as a ring of page colour around itself,
+ and the band below pins past it: `--chrome-h`/`--toolbar-h` are published by
+ sticky.js as **height plus that margin**, which is why the ring and the
+ offset can never disagree — they are the same number, declared once beside
+ the component's own `margin-bottom` and nowhere else. The pinned layout is
+ then pixel-identical to the flow layout, so nothing shifts at the moment a
+ band pins.
+
+ The ring must be exactly the margin, not more: it is painted at the band's
+ own z-index, above every band below it, so a ring wider than the gap would
+ paint page colour over the top of the next band down. It is also what covers
+ the four transparent corners a border radius leaves, where a row's rule
+ sliding past would read as a flicker nobody can reproduce. */
+.sticky-chrome > .group-tabs,
+.sticky-chrome > .search-bar,
+.sticky-chrome > .file-toolbar,
+.sticky-chrome > .video-toolbar,
+.sticky-chrome > .photo-toolbar,
+.sticky-chrome > .photo-album-bar {
+ box-shadow: 0 0 0 var(--band-margin) var(--bg-base);
+}
+
/* ── Cards ────────────────────────────────────────────────────────────────── */
.card {
@@ -583,7 +707,12 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
.group-tabs {
display: flex;
gap: 0;
- margin-bottom: 16px;
+ /* A pinned band keeps this gap: `--band-margin` is both the margin in
+ the flow and the width of the opaque ring the band paints around
+ itself once pinned, so the two can never disagree (style.css,
+ "Sticky chrome"). */
+ --band-margin: 16px;
+ margin-bottom: var(--band-margin);
border-bottom: 2px solid var(--border);
}
@@ -798,7 +927,12 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
display: flex;
align-items: center;
gap: 10px;
- margin-bottom: 12px;
+ /* A pinned band keeps this gap: `--band-margin` is both the margin in
+ the flow and the width of the opaque ring the band paints around
+ itself once pinned, so the two can never disagree (style.css,
+ "Sticky chrome"). */
+ --band-margin: 12px;
+ margin-bottom: var(--band-margin);
padding: 8px;
background: var(--bg-raised);
border: 1px solid var(--border);
@@ -924,7 +1058,15 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
.file-table {
width: 100%;
- border-collapse: collapse;
+ /* Separated, with no spacing, rather than collapsed. A collapsed border is
+ shared between two cells and so belongs to the table rather than to either
+ of them — it stays where the table's flow put it while a `position:
+ sticky` column head moves away, so the head loses its rule the moment it
+ pins. Only the bottom edge of any cell here carries a border, so nothing
+ was being shared and the drawn result is the same single line; it now
+ travels with the head. */
+ border-collapse: separate;
+ border-spacing: 0;
font-size: 0.9em;
}
@@ -954,10 +1096,30 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
.dir-row { cursor: pointer; }
.dir-row td { font-weight: 500; }
-.file-name {
+/* Every cell whose text is a name somebody else chose.
+
+ A table column is never narrower than its widest cell's minimum, and a name
+ with no spaces in it — `Rage_Against_The_Machine_Discography_1992-2000_FLAC`,
+ `musique_de_la_maison_2024` — is one unbreakable word. Left to itself such a
+ cell sets the column's minimum to the whole string and the table grows past
+ the window: measured on a phone at 390px, a 527px table from a folder name
+ and a 442px one from a group name. That is not merely ugly. **On Android a
+ document wider than the screen takes everything `position: sticky` out of
+ the visible area with it** — the navigation bar included — so the reported
+ symptom was "the header does not stay", and the cause was a table column.
+
+ Both cells used to be bare `<td>`s: `.file-name` was on a file's name and on
+ nothing else, so a *folder* row and the Search page's group column had no
+ wrapping rule at all. `test_sticky_header.py::test_no_view_scrolls_sideways`
+ measures the page against its window in both engines and at three widths. */
+.file-name,
+.td-group {
word-break: break-word;
min-width: 0;
}
+/* The column heads are `white-space: nowrap` like every other one; the group
+ head is two syllables and never needed the exception. */
+.file-table th.td-group { white-space: normal; }
/* Where a search result lives, under its name; click to open that folder
and clear the filter. */
.file-loc {
@@ -2127,6 +2289,13 @@ a.transfer-name {
.page-center { padding: 16px; }
.th-type, .td-type { display: none; }
.th-date, .td-date { display: none; }
+ /* And the group column, on the same grounds as those two: there is no room
+ for it, and the name column is what a phone is short of. Nothing is lost
+ with it. Search's Files view re-roots every result under a folder named
+ after its group, so you are always inside exactly one group and the
+ breadcrumb above the table already says which — the column repeated it on
+ every row. */
+ .td-group { display: none; }
/* The toolbar's three groups each take a line rather than competing for one.
`margin-left: auto` on the right-hand group is what pushed it off the
@@ -2145,9 +2314,104 @@ a.transfer-name {
.video-toolbar { flex-wrap: wrap; }
.video-toolbar .tb-search { flex-basis: 100%; margin-left: 0; }
+ /* ── Shared directories: not a table on a phone ────────────────────────
+ Four columns — name, Writable, Removable, and the eject/remove pair —
+ have a combined minimum of about 440px, and none of it is padding that
+ can be squeezed: the two switches are 90px apiece by design and the
+ buttons are touch targets. On a 390px screen that is ~100px hanging off
+ the right, with the two buttons the first thing to go over the edge.
+ Reported from a phone, with short directory names, so no amount of
+ wrapping the *name* would have helped.
+
+ So the row stops being a row. The head goes (each switch carries its own
+ label instead, see `.sdt-col-toggle .toggle-switch-label`), the name
+ takes a line of its own, and the two switches sit under it with the
+ actions pushed to the right margin:
+
+ 📁 Musique
+ [•] Writable [•] Removable ⏏ ✕
+
+ Everything that was on screen is still on screen, and nothing is
+ truncated. The rules are scoped to `.shared-directories-table` on
+ purpose: `folder-tree.js` borrows `.shared-dirs-tbl` for a two-column
+ list — a name and one button — which fits a phone as it is, and stacking
+ it would only make it taller. */
+ .shared-directories-table .shared-dirs-tbl,
+ .shared-directories-table .shared-dirs-tbl tbody,
+ .shared-directories-table .shared-dirs-tbl tr,
+ .shared-directories-table .shared-dirs-tbl td { display: block; }
+ .shared-directories-table .shared-dirs-tbl thead { display: none; }
+
+ /* Two rows, not three. Left to wrap, the eject/remove pair ends up alone on
+ a line of its own under the switches — legible, but a third of the height
+ for two buttons, and they read as belonging to nothing. A grid puts them
+ back beside the name they act on, which is where they were:
+
+ ┌──────────────────────────┬────────┐
+ │ 📁 Musique │ ⏏ ✕ │
+ ├──────────────────────────┴────────┤
+ │ [•] Writable [•] Removable │
+ └───────────────────────────────────┘
+
+ The switches are not placed: they fall into the second row on their own,
+ so a group with one switch (the create-group page has no Removable) or
+ none needs no rule of its own. */
+ .shared-directories-table .shared-dirs-tbl tr {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ align-items: center;
+ gap: 8px 16px;
+ padding: 10px 0;
+ }
+ .shared-directories-table .shared-dirs-tbl td { padding: 0; }
+ /* The separator moves to the row: with the cells turned into blocks, a
+ border on each of them draws four lines instead of one. */
+ .shared-directories-table .shared-dirs-tbl tbody tr + tr td { border-top: none; }
+ .shared-directories-table .shared-dirs-tbl tbody tr + tr {
+ border-top: 1px solid var(--border);
+ }
+
+ .shared-directories-table .sdt-col-dir {
+ grid-column: 1;
+ grid-row: 1;
+ min-width: 0;
+ }
+ /* No longer a single truncated line: it has the width of the row to itself
+ and there is no column left to protect from it. */
+ .shared-directories-table .sdt-col-path {
+ grid-column: 1 / -1;
+ max-width: none;
+ white-space: normal;
+ overflow-wrap: anywhere;
+ }
+ .shared-directories-table .sdt-col-toggle {
+ width: auto;
+ min-width: 0;
+ text-align: left;
+ }
+ .shared-directories-table .sdt-col-toggle .toggle-switch {
+ justify-content: flex-start;
+ gap: 8px;
+ }
+ .shared-directories-table .sdt-col-toggle .toggle-switch-label { display: inline; }
+ /* Beside the name, against the right margin, exactly where the wide layout
+ puts them. */
+ .shared-directories-table .sdt-col-actions {
+ grid-column: 2;
+ grid-row: 1;
+ justify-self: end;
+ }
+
/* Every pixel here is one the conversation does not get. */
.group-header { margin-bottom: 10px; gap: 8px; }
- .group-tabs { margin-bottom: 10px; }
+ .group-tabs { --band-margin: 10px; }
+ /* Six tabs at 18px of padding a side come to 380px, plus the main column's
+ own 32px — wider than a 360px phone, and a page wider than the screen is
+ what takes every pinned band out of the visible area on Android. They
+ share the row instead of each claiming a fixed width, which also means a
+ seventh application costs nothing: the icon is what identifies a tab, the
+ padding around it never did. */
+ .group-tab { flex: 1 1 0; min-width: 0; padding: 10px 0; }
.chat-messages { padding: 12px; }
/* The transfers panel stops hanging off its button.
@@ -2671,6 +2935,10 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
.sdt-col-toggle { width: 90px; text-align: center; }
.sdt-col-toggle th { text-align: center; }
.sdt-col-toggle .toggle-switch { justify-content: center; }
+/* The switch carries its own label for the phone layout below, where there is
+ no column head to read it from. Wherever the head *is* there, it would be
+ the same word twice on every row. */
+.sdt-col-toggle .toggle-switch-label { display: none; }
.sdt-col-actions { white-space: nowrap; text-align: right; }
.sdt-action-btn {
background: none; border: 1px solid var(--border); border-radius: 4px;
@@ -3093,7 +3361,12 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
display: flex;
align-items: center;
gap: 6px;
- margin-bottom: 14px;
+ /* A pinned band keeps this gap: `--band-margin` is both the margin in
+ the flow and the width of the opaque ring the band paints around
+ itself once pinned, so the two can never disagree (style.css,
+ "Sticky chrome"). */
+ --band-margin: 14px;
+ margin-bottom: var(--band-margin);
}
.video-toolbar .tb-search { margin-left: auto; }
@@ -3897,7 +4170,12 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
display: flex;
align-items: center;
gap: 6px;
- margin-bottom: 14px;
+ /* A pinned band keeps this gap: `--band-margin` is both the margin in
+ the flow and the width of the opaque ring the band paints around
+ itself once pinned, so the two can never disagree (style.css,
+ "Sticky chrome"). */
+ --band-margin: 14px;
+ margin-bottom: var(--band-margin);
}
.photo-toolbar .tb-search { margin-left: auto; }
@@ -3910,7 +4188,12 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
align-items: center;
justify-content: space-between;
gap: 12px;
- margin-bottom: 16px;
+ /* A pinned band keeps this gap: `--band-margin` is both the margin in
+ the flow and the width of the opaque ring the band paints around
+ itself once pinned, so the two can never disagree (style.css,
+ "Sticky chrome"). */
+ --band-margin: 16px;
+ margin-bottom: var(--band-margin);
}
.photo-album-heading {
display: flex;