Skip to main content

max / makenotwork

12.8 KB · 256 lines History Blame Raw
1 /* ============================================================================
2 Carousel: the widget tier's first consumer.
3
4 The markup is no longer written in a template. It comes from a description --
5 `crate::quasi::widgets::carousel` says "an ordered set of pictures, called a
6 carousel" -- and quasi-webview turns that into a region carrying
7 `data-widget="carousel"` with one `.picture` or `.picture-img` per frame.
8
9 So these rules key on the widget name rather than on a hand-written class.
10 That is what "a renderer that recognises the name draws it its own way"
11 means on this host: the name is the hook, and the stylesheet plus the island
12 are the browser's idea of a carousel.
13
14 THE FALLBACK RUNS THE OTHER WAY NOW. This used to render frame one and hide
15 the rest, because the controls that reach them are the scripted part -- so
16 with JS off, two of three screenshots were unreachable. Now the unenhanced
17 page is the whole gallery, stacked in order.
18
19 THE CHROME IS DESCRIBED TOO, as of makeover-layout 0.23.0. The description
20 says `Showing::One` -- an ordered set of pictures with one showing -- and
21 quasi-webview derives prev/position/next from that, once, for every widget
22 there will ever be. Nothing here or in any renderer matches on the name
23 "carousel" to get it. The island stopped injecting chrome and now only binds
24 what is already on the page; the dot strip and the overlaid arrows are gone.
25
26 The controls are still not shipped live to a reader with no script: makeover
27 keeps `.showing` hidden until something sets [data-ready], so the markup no
28 longer has to be absent to avoid lying.
29 ========================================================================= */
30
31 /* Public gallery carousel sections (item + project pages). */
32 .item-gallery,
33 .project-gallery {
34 margin: var(--gap-pane) 0;
35 }
36
37 /* <mnw-carousel> wraps the described region rather than being it: a custom
38 element is what the browser upgrades automatically after an htmx swap, and
39 the description emits a plain div because it does not know this host's tag
40 prefix. Default display for an unknown element is inline, so say otherwise. */
41 mnw-carousel {
42 display: block;
43 }
44
45 /* The default is ONE frame, which is what a visitor with JavaScript sees, so
46 the page is settled from first paint and nothing rearranges on load. Showing
47 all three and collapsing them was a 141px -> 58px jump on every load that
48 everyone with JS paid; `no-js.css`, loaded from a <noscript>, is what opens
49 the stack out for a visitor without scripting.
50
51 This is the one half of the carousel that stays local, and it is host policy
52 rather than a gap. makeover's generated sheet collapses the stack on
53 [data-ready], which is the honest default for a renderer that cannot know
54 whether this page has script: it ships every child and takes them away once
55 something binds them. Trading a load-time jump for that is a decision about
56 this site's landing page, and <noscript> is a mechanism a generated
57 stylesheet has no way to reach.
58
59 No padding-bottom and no position: relative any more. Both were for chrome
60 that is gone -- the strip that had to be reserved and the arrows that were
61 absolutely positioned over the frame. */
62 [data-widget="carousel"] > .showing-frame:not(.current) {
63 /* respec-ok: the generated rule is the same claim gated on [data-ready], and
64 what it cannot express is <noscript>. A renderer that cannot know whether a
65 page has script has to ship every child and take them away once something
66 binds them; this page collapses from first paint instead and lets no-js.css
67 open it back, which is a trade about this landing page rather than about
68 the vocabulary. */
69 display: none;
70 }
71
72 [data-widget="carousel"] .picture {
73 margin: 0;
74 }
75
76 /* Only the corner. `display`, `width`, `height` and the raised fill and bevel
77 all come from makeover's own `.picture-img` rule, which layout.css puts on
78 every page through crate::shell::head. Restating any of them here is a
79 components-layer declaration quietly beating the design system, which is what the
80 drift check in build.rs exists to catch -- and did, on the first attempt at
81 this file. */
82 [data-widget="carousel"] .picture-img {
83 border-radius: var(--radius-control);
84 }
85
86 /* No colour: makeover's `.picture-caption` already sets it to
87 --content-muted, and this said the same thing one layer louder. What is
88 local is the centring and the mono face, which are this widget's look and
89 not a claim about what a caption is. */
90 [data-widget="carousel"] .picture-caption {
91 margin-top: var(--gap-group);
92 font-family: var(--font-mono);
93 font-size: var(--text-note);
94 text-align: center;
95 }
96
97 /* Landing showcase only: close the top edge.
98
99 The three landing frames are screenshots of this site, so the parchment they
100 were shot on is the parchment they sit on, and a 1px border between two
101 identical grounds is not an edge anyone sees.
102
103 THE ELEVATION IS GONE, and that is the point of the port. This carried
104 `box-shadow: 0 4px 10px var(--elevation), 0 14px 36px var(--elevation)`,
105 which is the defect `c0b63ea9` names: in-flow depth is a bevel and elevation
106 is for what sits *over* the page. It survived this long because it was
107 invisible -- a declaration in a later layer on the same class makeover
108 sets, so it beat the design system's bevel silently. The drift check in build.rs found it
109 the moment the frame became a described `.picture-img`.
110
111 So the frame is raised by makeover's bevel now, from `Depth::Raised`, and
112 what is left here is the one thing that was never about depth: a stronger
113 border colour, because a shadow falling downward does nothing for the top
114 edge and the top edge is where a parchment screenshot dissolves into a
115 parchment page. */
116 .landing-showcase .picture-img {
117 border-color: var(--border-strong);
118 }
119
120 /* ---------------------------------------------------------------------------
121 Enhanced. Everything below needs the island, and the island sets
122 [data-ready] once it has upgraded and bound the controls.
123 --------------------------------------------------------------------------- */
124
125 [data-widget="carousel"][data-ready]:focus-visible {
126 outline: 2px solid var(--focus-ring);
127 outline-offset: 3px;
128 }
129
130 /* The control row is makeover's `.showing`, rendered by the description rather
131 than injected here, and it needs no rules: it is two `.button`s and a muted
132 readout, all three of which the generated sheet already styles. What used to
133 sit here was `.carousel-nav`, `.carousel-prev`, `.carousel-next`,
134 `.carousel-dots` and `.carousel-dot` -- 60 lines for chrome this host drew by
135 hand because nothing could describe it.
136
137 The arrows were absolutely positioned over the frame, which Max called
138 cluttered and which a terminal cannot honestly do at all; the dots had no
139 form past a handful of frames, and two of the three galleries here are
140 creator uploads of arbitrary length. Both halves of that were the same half. */
141
142
143 /* ---------------------------------------------------------------------------
144 Custom page editor (dashboard/custom_page_editor.html)
145 Moved out of an inline <style> block per the extract-shared-CSS rule.
146 --------------------------------------------------------------------------- */
147 .cp-editor { max-width: 1200px; margin: 0 auto; padding: var(--gap-section); }
148 .cp-editor-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--gap-section); flex-wrap: wrap; }
149 .cp-editor-actions { display: flex; gap: var(--gap-section); }
150 .cp-intro { color: var(--content-muted); margin: var(--gap-bound) 0 var(--gap-section); }
151 /* Form beside preview. A code textarea below about 380px is not editable, so
152 that number is the pane's, not the viewport's, and the pair stacks itself. */
153 .cp-editor-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: var(--gap-section); align-items: start; }
154 .cp-editor-form { display: flex; flex-direction: column; gap: var(--gap-peer); }
155 .cp-code { width: 100%; min-height: 220px; font-family: var(--font-mono, ui-monospace, monospace); font-size: var(--text-note); line-height: 1.4; resize: vertical; }
156 .cp-editor-buttons { display: flex; align-items: center; gap: var(--gap-section); margin-top: var(--gap-peer); }
157 .cp-preview-pane { border: 1px solid var(--border); border-radius: var(--radius-control); overflow: hidden; }
158 .cp-preview-bar { font-size: var(--text-fine); padding: var(--gap-bound) var(--gap-group); background: var(--surface-sunken); border-bottom: 1px solid var(--border); }
159 .cp-preview { width: 100%; height: 520px; border: 0; background: var(--primary-light); display: block; }
160 .cp-blocked { margin-top: var(--gap-section); }
161 .cp-blocked-title { font-weight: 600; margin-bottom: var(--gap-peer); }
162 .cp-blocked-empty { color: var(--content-muted); }
163 .cp-blocked-list { list-style: none; padding: 0; margin: 0; }
164 .cp-blocked-list li { padding: var(--gap-peer) 0; border-bottom: 1px solid var(--border); font-size: var(--text-note); display: flex; gap: var(--gap-peer); flex-wrap: wrap; align-items: baseline; }
165 .cp-blocked-kind { font-weight: 600; }
166 .cp-blocked-loc { color: var(--content-muted); }
167 .cp-blocked-val { background: var(--surface-sunken); padding: 0 var(--gap-bound); }
168 .cp-status.cp-ok { color: var(--success); }
169 .cp-status.cp-err { color: var(--warning); }
170 .cp-reset { margin-top: var(--gap-pane); }
171 .cp-locked-banner { margin: var(--gap-peer) 0 var(--gap-section); padding: var(--gap-section); border: 1px solid var(--warning); border-radius: var(--radius-control); background: color-mix(in oklch, var(--warning) 12%, var(--surface-page)); }
172 /* Layout-shift fix (audit Run 17 Frontend): reserve space for a cover image
173 whose class didn't already set an aspect-ratio / fixed size. */
174 .proj-image-preview img { width: 120px; height: 120px; object-fit: cover; }
175
176 /* ---- Discover search: how well a result matched -------------------------
177 Two surfaces, doing two different jobs (db/discover.rs, migration 179).
178
179 .results-tier-break is the single heading, and it sits exactly where the
180 claim it makes turns true: above it every row holds at least one word the
181 searcher typed, below it none of them do. .row-match-note is the per-row
182 half, "Matched 3 of 5 words", because coverage varies row to row and one
183 page can hold a 4-of-5 beside a 1-of-5.
184
185 Both are deliberately quiet. They explain a result the reader is already
186 looking at; competing with the title would make search feel like it is
187 apologising. Muted colour and small type, no accent, no border of their own
188 beyond the rule that separates the tiers. */
189 .results-tier-break {
190 display: flex;
191 align-items: baseline;
192 gap: var(--gap-peer);
193 flex-wrap: wrap;
194 padding: var(--gap-section) var(--gap-section) var(--gap-peer);
195 border-top: 1px solid var(--border);
196 background: var(--surface-page);
197 }
198
199 /* .results-table sets border-top: none and lets .table-row draw the rules, so
200 the break supplies its own top border. Mid-list that sits against the
201 preceding row's border-bottom and reads as one heavier 2px rule, which is
202 wanted here: it is a section change, not another row. At the top of an
203 all-fuzzy page there is no preceding row, and the same border supplies the
204 table's missing top edge. */
205 .results-tier-label {
206 font-family: var(--font-display);
207 font-weight: bold;
208 font-size: var(--text-fine);
209 text-transform: uppercase;
210 letter-spacing: 0.03em;
211 color: var(--content);
212 }
213
214 .results-tier-hint {
215 font-size: var(--text-fine);
216 color: var(--content-muted);
217 }
218
219 /* The grid is `repeat(auto-fill, minmax(280px, 1fr))`, so without this the
220 heading takes one card's slot and the tiers interleave visually. */
221 .results-container.results-grid .results-tier-break {
222 grid-column: 1 / -1;
223 padding-left: 0;
224 padding-right: 0;
225 background: none;
226 }
227
228 /* In list view this rides inside .row-info, under the creator line, not as
229 another child of .table-row-link: that is a fixed five-column grid and a
230 sixth child would invent a column and shift every row that carried one. */
231 .row-match-note {
232 font-family: var(--font-mono);
233 font-size: var(--text-fine);
234 color: var(--content-muted);
235 white-space: nowrap;
236 overflow: hidden;
237 text-overflow: ellipsis;
238 }
239
240 /* Grid cards have room to breathe; give the note its own line under the meta. */
241 .grid-card .row-match-note {
242 display: block;
243 margin-top: var(--gap-bound);
244 }
245
246 /* The narrow list layouts drop columns rather than wrap, and .row-info is the
247 column that survives. Keeping the note there would crowd the title on a
248 phone, where the tier heading above still carries the important half. */
249 @media (max-width: 599px) {
250 .table-row .row-match-note { /* respec-ok: hiding a cell at a breakpoint. The
251 generated sheet describes one `.table-row`; which of its children survive a
252 narrow viewport is this page's call, not the design system's. */ display: none; }
253 }
254
255
256