Files
mathew/frontend/app/assets/css/_book.scss
Aran Roig 68b74eb60b
All checks were successful
Build and Deploy Nuxt / build (push) Successful in 24s
Graph calculator improvmentes
2026-10-02 14:53:00 +02:00

327 lines
10 KiB
SCSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@use 'mixins' as *;
// ---------- Reader: an open book of article pages ----------
// Every article opened in this session is a page, laid out beside the last
// in a track you move along sideways (ArticleBookStack.vue). Pages are solid
// paper, and each sticks a spine's width further from the left edge than the
// one before it — left: calc(var(--i) * var(--spine-w)) — so turning to a
// newer page slides it over the older ones, which park along the edge with
// nothing but their spines showing: a stack of pages, titled along the edge
// like books on a shelf. A page is as wide as its share of the reading
// surface — minus the spines of every other page, but never more than a
// comfortable measure, and open across the whole desk when it is the only
// page in the book — so on a wide desk the next page waits fully in view
// beside the one you read, not only its spine; on a narrow one the rest wait
// out at the right edge. A spine the desk has no room for is copied to that
// edge — the dock, .book-dock below — so no page is ever more than a click
// away, though its own spine is off the end of the row. Click a spine to turn
// back to that page, the × to take it out of the book. Below 900px the book
// folds to the one page in view and its neighbours become chips along the
// top. Routes stay the source of truth — every page is a plain /wiki/<slug>
// URL, and useArticleTabs keeps the stack. (The --sidebar-w, --spine-w and
// --pane-max-w tokens live in _tokens.scss.)
// the surface the book lies open on
.book-layer {
position: fixed;
inset: 0 0 0 var(--sidebar-w);
transition: left 0.22s ease;
z-index: 70;
display: flex;
flex-direction: column;
background: var(--bg-elev);
box-shadow: -10px 0 26px rgb(5 7 13 / 9%);
}
// nothing lives at this URL: the route host says so over the open book
.reader-note {
position: fixed;
inset: 0 0 0 var(--sidebar-w);
transition: left 0.22s ease;
z-index: 74;
background: var(--bg-elev);
box-shadow: -10px 0 26px rgb(5 7 13 / 4%);
}
// the row of pages, moved along sideways
.book-track {
--pane-w: calc(100% - (var(--panes, 1) - 1) * var(--spine-w));
position: relative;
flex: 1;
min-height: 0;
display: flex;
align-items: stretch;
overflow-x: auto;
overflow-y: hidden;
overscroll-behavior: contain;
// the desk under the pages: once a page stops short of the reading
// surface, what's left of the desk shows beside it
background: var(--bg);
@include no-scrollbar;
}
// one page: solid paper, parked beside the pages under it
.book-pane {
position: sticky;
left: calc(var(--i) * var(--spine-w));
z-index: calc(var(--i) + 1);
flex: 0 0 var(--pane-w);
// A page takes every inch of the reading surface it can — the one in view
// opens across the whole desk, not just a column of it. Only once so many
// are open that each page's share falls below a comfortable reading width
// does it hold at that width and the book scroll sideways instead.
min-width: min(46rem, 100%);
// ...and on a desk wide enough for it to stretch past a page of paper, it
// holds at --pane-max-w and leaves the rest of the desk bare. The single
// page of a one-page book is exempt below.
max-width: var(--pane-max-w);
display: flex;
background: var(--bg-elev);
// the fore-edge of the paper, against the desk and the next page
border-right: 1px solid var(--border);
// the edge of this page lying on top of the ones beneath it
box-shadow: -10px 0 22px rgb(5 7 13 / 7%);
animation: page-turned-in 0.3s cubic-bezier(0.22, 0.8, 0.36, 1) both;
}
// a book with a single page in it lies open across the whole desk: there is
// no other page waiting for that space, so the page takes all of it
.book-pane:only-child { max-width: none; }
// a page that isn't in view asks to be turned to: the whole of it is
// clickable (clicks on its inert body retarget to the pane itself), not
// just the spine
.book-pane:not(.active) {
cursor: pointer;
.pane-body, .pane-body * { cursor: pointer; }
}
// a new page is laid down at the right and eased into the stack
@keyframes page-turned-in {
from { transform: translateX(56px); opacity: 0; }
}
// the page's fore-edge: its title standing on end, and the × that closes it.
// Once a newer page is turned over this one, the spine is all that's left in
// sight — so it is the handle for turning back. A page whose spine is off the
// edge of the desk has it copied at the edge itself, in the dock below, which
// borrows .spine-title and .spine-close as its own standing type.
.pane-spine {
flex: 0 0 var(--spine-w);
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 6px 0 10px;
background: var(--surface);
border-right: 1px solid var(--border);
color: var(--muted);
cursor: pointer;
user-select: none;
transition: color 0.15s, background-color 0.15s;
&:hover { color: var(--text); background: var(--surface-2); }
}
// the page in view has its spine marked, the way a ribbon marks a book
.book-pane.active .pane-spine {
color: var(--accent);
background: color-mix(in srgb, var(--accent) 10%, var(--surface));
}
.spine-close {
@include icon-target(15px, 5px);
color: var(--faint);
transition: color 0.15s, background-color 0.15s;
@include close-target;
svg { display: block; }
}
.spine-title {
writing-mode: vertical-rl;
flex: 1;
min-height: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.78rem;
font-weight: 700;
line-height: 1.35;
letter-spacing: 0.01em;
padding-top: 0;
}
// the page itself: the article, scrolling on its own
.pane-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.pane-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
padding-inline: clamp(16px, 3.5vw, 36px);
// the article takes the page as a single centered column
.article-page { max-width: 78ch; margin-inline: auto; }
}
.pane-skeleton { padding-top: 32px; }
// folded up narrow: the neighbours of the page in view, as chips along the
// top. Wide screens read the spines instead.
.book-chips {
display: none;
flex-shrink: 0;
gap: 8px;
padding: 9px 10px;
overflow-x: auto;
overscroll-behavior: contain;
background: var(--surface);
border-bottom: 1px solid var(--border);
@include no-scrollbar;
}
.book-chip {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 6px;
max-width: 46vw;
padding: 5px 7px 5px 11px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--surface-2);
color: var(--muted);
font-size: 0.79rem;
font-weight: 600;
cursor: pointer;
user-select: none;
transition: color 0.15s, background-color 0.15s, border-color 0.15s;
&:hover { color: var(--text); }
&.active {
color: var(--accent);
background: var(--bg-elev);
border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
}
}
.chip-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chip-close { @include icon-target(16px, 5px); color: var(--faint); @include close-target; }
// ---------- The dock: pages waiting beyond the right edge of the desk -----
// A page further along the book than the one in view lies somewhere off the
// right edge of the desk, reachable by dragging the row sideways. While it is
// out there, its spine is kept at the edge itself: a stack one spine wide, in
// the order the pages were opened, each one the standing title and × of the
// page it stands for. Click it and the book turns to that page as though you
// had scrolled to it; the page's own spine comes into view and its copy in the
// dock goes away (ArticleBookStack measures which pages are out there). It
// rides above every page of the book — the pages are numbered one apiece by
// --panes — and, being inside the reading surface, below anything popped over
// it.
.book-dock {
position: absolute;
inset-block: 0;
right: 0;
z-index: calc(var(--panes, 1) + 2);
display: flex;
flex-direction: column;
width: var(--spine-w);
padding: 6px 0;
background: var(--surface);
border-left: 1px solid var(--border);
box-shadow: -10px 0 22px rgb(5 7 13 / 7%);
color: var(--muted);
}
// the waiting spines share the height of the desk, so however many pages are
// out there every one of them keeps a place at the edge, its title cut short
// to fit — the whole title is in the tooltip
.dock-spine {
flex: 1 1 0;
min-height: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 4px 0;
cursor: pointer;
user-select: none;
transition: color 0.15s, background-color 0.15s;
+ .dock-spine { border-top: 1px solid var(--border); }
&:hover { color: var(--text); background: var(--surface-2); }
}
// ---------- A wiki page laid on the book ----------
// The link map and the graphing calculator are pages too: the search popup
// lays one of them on the book beside the articles (ToolPane.vue). It fills
// the page edge to edge — no column of prose scrolls here, the surface keeps
// its own docks and toolbars — and it takes the whole share of the desk, a
// plane being no narrower read than a page of text. Each stage's own sheet
// answers for the rest (_graph.scss, _calculator.scss).
.book-pane.pane-tools {
max-width: none;
overflow: hidden;
}
.pane-stage {
position: relative;
flex: 1;
min-width: 0;
min-height: 0;
}
// the surface's mark on the spine, under the ×, so a page of the map or the
// plane reads as itself fanned in among the articles
.pane-spine .spine-glyph { flex: none; opacity: 0.65; }
.book-pane.active .pane-spine .spine-glyph { opacity: 1; }
@media (max-width: 900px) {
.book-layer, .reader-note { inset: 0; box-shadow: none; }
.book-track { overflow-x: hidden; }
// folded: only the page in view is out, the rest are chips along the top
.book-pane {
position: static;
flex: 1 1 100%;
width: 100%;
min-width: 0;
max-width: 100%;
box-shadow: none;
&:not(.active) { display: none; }
}
.book-chips { display: flex; }
// folded, the book has no edges to stack spines against: the chips along
// the top are every page of it
.book-dock { display: none; }
.pane-scroll { padding-inline: 16px; }
}
@media (prefers-reduced-motion: reduce) {
.book-pane { animation: none; }
}