Skip to main content

max / makenotwork

20.9 KB · 463 lines History Blame Raw
1 /* ===========================================
2 DISCOVER PAGE
3 =========================================== */
4
5 .discover-layout { display: grid; grid-template-columns: 200px 1fr; gap: var(--gap-pane); }
6 .discover-layout.no-sidebar { grid-template-columns: 1fr; }
7 .discover-layout.no-sidebar .discover-sidebar { display: none; }
8 .discover-sidebar { font-size: var(--text-note); }
9 .filter-section { margin-bottom: var(--gap-section); }
10 .filter-title { font-family: var(--font-display); font-weight: bold; font-size: var(--text-note); margin-bottom: var(--gap-peer); color: var(--content); }
11 .filter-list { list-style: none; }
12 /* The row carries selection (`.is-selected` recipe) and hover; the filter itself
13 is a real <button> child so Enter/Space work without scripting. */
14 .filter-item { display: flex; align-items: center; gap: var(--gap-bound); transition: opacity 0.1s ease; }
15 .filter-item:hover { opacity: 0.6; }
16 .filter-item.is-selected { font-weight: bold; }
17 .filter-item .count { opacity: 0.4; font-size: var(--text-fine); }
18 .tag-follow-btn { background: none; border: 1px solid var(--border); cursor: pointer; font-family: var(--font-mono); font-size: var(--text-fine); opacity: 0.55; padding: var(--gap-bound) var(--gap-peer); color: var(--content); }
19 .tag-follow-btn:hover { opacity: 1; }
20 .tag-follow-btn.is-selected { opacity: 0.85; border-color: var(--focus-ring); background: var(--highlight-faint); }
21 .filter-checkbox { display: flex; align-items: center; gap: var(--gap-peer); padding: var(--gap-bound) 0; cursor: pointer; }
22 .filter-checkbox input { width: auto; margin: 0; }
23 .price-inputs { display: flex; gap: var(--gap-bound); align-items: center; margin-bottom: var(--gap-peer); }
24 .price-inputs input { width: 60px; padding: var(--gap-bound); font-size: var(--text-fine); background: var(--surface-sunken); border: none; }
25 .price-inputs span { opacity: 0.4; }
26 .discover-main { min-width: 0; }
27
28 /* ---- Discover sidebar: tag spine ----------------------------------------
29 Tags are the sidebar's structure, not one facet among several, so the spine
30 reads as the primary surface and .filter-refine below it is subordinate.
31 Everything here composes existing primitives: .filter-item carries the row
32 and its .is-selected recipe, the described field draws its own list, and
33 .breadcrumb carries the crumb idiom. */
34 .tag-spine { padding-bottom: var(--gap-section); border-bottom: 1px solid var(--border); }
35
36 /* Selected tags. Also the active-filter summary, so they use the canonical
37 selected tint rather than a new colour. */
38 .tag-chips { list-style: none; display: flex; flex-wrap: wrap; gap: var(--gap-bound); margin-bottom: var(--gap-section); }
39 .tag-chip { display: flex; align-items: center; gap: var(--gap-bound); padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-fine); background: var(--highlight-faint); border: 1px solid var(--focus-ring); }
40 .tag-chip-label { overflow-wrap: anywhere; }
41 .tag-chip-remove { text-decoration: none; color: var(--content-secondary); font-size: var(--text-body); line-height: 1; padding: 0 var(--gap-bound); }
42 .tag-chip-remove:hover { color: var(--content); }
43 .tag-chip-clear { background: none; border: 1px dashed var(--border-strong); }
44 .tag-chip-clear a { text-decoration: none; color: var(--content-secondary); font-size: var(--text-fine); }
45 .tag-chip-clear a:hover { color: var(--content); }
46
47 /* Typeahead. The box, the list and everything that opens and closes it are
48 the described field's, so all this carries is the positioning context the
49 list is drawn against. */
50 .tag-combobox { position: relative; margin-bottom: var(--gap-section); }
51
52 /* Drill-down. A rung either selects (assignable leaf) or navigates (category);
53 rows with both put the chevron after the label. */
54 .tag-crumbs { list-style: none; display: flex; flex-wrap: wrap; font-family: var(--font-mono); font-size: var(--text-fine); color: var(--content-muted); margin-bottom: var(--gap-peer); }
55 .tag-crumbs li + li::before { content: "/"; opacity: 0.4; margin: 0 var(--gap-bound); }
56 .tag-crumbs a { color: var(--content); text-decoration: none; }
57 .tag-crumbs a:hover { text-decoration: underline; }
58 .tag-drill-select { flex: 1; display: flex; align-items: center; gap: var(--gap-peer); cursor: pointer; padding: var(--gap-bound) 0; }
59 .tag-drill-select input { width: auto; margin: 0; }
60 .tag-drill-select span:first-of-type { flex: 1; }
61 .tag-drill-into { display: flex; align-items: center; gap: var(--gap-peer); flex: 1; text-decoration: none; color: inherit; padding: var(--gap-bound) 0; }
62 .tag-drill-into span:first-child { flex: 1; }
63 .tag-drill-chevron { opacity: 0.4; }
64
65 /* ---- Discover sidebar: refine block -------------------------------------
66 Subordinate to the spine: same controls, quieter. */
67 .filter-refine { margin-top: var(--gap-section); }
68 .filter-fieldset { border: none; padding: 0; margin: 0; min-width: 0; }
69 .filter-check { flex: 1; display: flex; align-items: center; gap: var(--gap-peer); cursor: pointer; padding: var(--gap-bound) 0; }
70 .filter-check input { width: auto; margin: 0; }
71 .filter-check span:first-of-type { flex: 1; }
72
73 /* A zero-count facet stays visible but inert, so the shape of the catalog
74 stays legible as filters narrow. No existing rule covers a disabled input
75 inside a label, so this is the checkbox counterpart of the button rule.
76 `.is-empty` covers the drill rungs, which are links and carry no input at
77 all; they were rendering identically to rungs with items. */
78 .filter-item:has(input:disabled), .filter-item.is-empty { opacity: 0.5; }
79 .filter-item:has(input:disabled) .filter-check { cursor: not-allowed; }
80
81 /* Price buckets are links carrying a range, not form state. */
82 .price-buckets { margin-top: var(--gap-peer); }
83 .price-bucket { flex: 1; display: flex; align-items: center; justify-content: space-between; gap: var(--gap-peer); text-decoration: none; color: inherit; padding: var(--gap-bound) 0; }
84
85 .mode-toggle { display: flex; gap: var(--gap-peer); margin-bottom: var(--gap-section); }
86 .toggle-btn { background: var(--surface-sunken); border: none; padding: var(--gap-peer) var(--gap-section); font-size: var(--text-note); font-family: inherit; cursor: pointer; transition: background 0.1s ease, opacity 0.1s ease; }
87 .toggle-btn:hover { opacity: 0.7; }
88 .toggle-btn.is-selected { background: var(--primary-dark); color: var(--primary-light); }
89 .table-controls { background: var(--surface-sunken); display: flex; align-items: center; gap: var(--gap-section); padding: var(--gap-peer) var(--gap-section); }
90 .table-meta { font-size: var(--text-fine); opacity: 0.6; white-space: nowrap; flex-shrink: 0; }
91 .sort-select { background: var(--surface-page); border: none; padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-fine); font-family: inherit; cursor: pointer; width: auto; flex-shrink: 0; }
92 .table-header { display: grid; grid-template-columns: 50px 1fr 100px 70px 70px; gap: var(--gap-peer); padding: var(--gap-peer) var(--gap-section); background: var(--surface-overlay); font-size: var(--text-fine); opacity: 0.7; text-transform: uppercase; letter-spacing: 0.03em; }
93 .table-header.projects-header { grid-template-columns: 1fr 100px 80px 70px; }
94 /* Column headers describe the list layout only; hidden when the grid view is active. */
95 .table-header.is-hidden { display: none; }
96 .col-right { text-align: right; }
97 .results-table { border: 1px solid var(--border); border-top: none; }
98 .table-row { /* respec-ok: a five-column grid template. The generated `.table-row` sets a display
99 but has no vocabulary for column tracks, so the tracks and the display that carries
100 them are stated together here. */ display: grid; grid-template-columns: 50px 1fr 100px 70px 70px; gap: var(--gap-peer); padding: var(--gap-peer) var(--gap-section); align-items: center; text-decoration: none; color: var(--content); font-size: var(--text-note); border-bottom: 1px solid var(--border); transition: background 0.1s ease; }
101 .table-row-item { grid-template-columns: 1fr 30px; padding: 0; gap: 0; }
102 .table-row-item .table-row-link { display: grid; grid-template-columns: 50px 1fr 100px 70px 70px; gap: var(--gap-peer); padding: var(--gap-peer) var(--gap-section); align-items: center; text-decoration: none; color: inherit; flex: 1; }
103 .table-row-item .row-save { display: flex; align-items: center; justify-content: center; }
104 .table-row.project-row { grid-template-columns: 1fr 100px 80px 70px; }
105 .table-row:last-child { border-bottom: none; }
106 .table-row:hover { background: var(--surface-sunken); text-decoration: none; }
107 .row-type { font-size: var(--text-fine); background: var(--surface-sunken); padding: var(--gap-bound) var(--gap-peer); text-align: center; opacity: 0.7; }
108 .row-info { min-width: 0; display: flex; flex-direction: column; gap: var(--gap-bound); }
109 .row-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
110 .row-creator { font-size: var(--text-fine); opacity: 0.5; }.row-category { font-size: var(--text-fine); opacity: 0.6; }
111 .row-price { text-align: right; }
112 .row-items { text-align: right; opacity: 0.6; font-size: var(--text-fine); }
113 .row-date { text-align: right; opacity: 0.5; font-size: var(--text-fine); }.pagination { display: flex; gap: var(--gap-bound); }
114 .pagination button { background: var(--surface-page); border: none; padding: var(--gap-bound) var(--gap-group); font-size: var(--text-fine); cursor: pointer; }
115 .pagination button:disabled { opacity: 0.3; cursor: not-allowed; }
116 .pagination button.is-selected { background: var(--primary-dark); color: var(--primary-light); }
117
118 /* Issue tabs (open / closed) inside the git browser */
119 .issue-tabs { display: flex; gap: var(--gap-peer); margin-bottom: var(--gap-section); }
120 .issue-tab {
121 padding: var(--gap-peer) var(--gap-section);
122 border: 1px solid var(--border);
123 background: var(--surface-overlay);
124 font-family: var(--font-mono);
125 font-size: var(--text-note);
126 color: var(--content);
127 text-decoration: none;
128 opacity: 0.7;
129 }
130 .issue-tab:hover { opacity: 1; }
131 .issue-tab.is-selected {
132 opacity: 1;
133 background: var(--highlight-faint);
134 border-color: var(--focus-ring);
135 }
136 .page-info { opacity: 0.6; }
137 .padded-page .page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--gap-section); padding-bottom: var(--gap-section); border-bottom: 1px solid var(--border); }
138 .padded-page .page-header h1 { font-size: var(--text-head); text-align: center; position: absolute; left: 50%; transform: translateX(-50%); }
139 .padded-page .page-header nav { margin: 0; }
140 .padded-page .page-header .nav-links { gap: var(--gap-pane); }
141 .discover-filter-toggle {
142 display: none;
143 font-family: var(--font-mono);
144 font-size: var(--text-note);
145 padding: var(--gap-peer) var(--gap-section);
146 background: var(--surface-overlay);
147 border: 1px solid var(--border);
148 cursor: pointer;
149 margin-bottom: var(--gap-section);
150 }
151
152 .discover-filter-toggle.active { background: var(--surface-sunken); border-color: var(--border-strong); }
153
154 .discover-filter-toggle .filter-count {
155 background: var(--action);
156 color: var(--primary-light);
157 font-size: var(--text-fine);
158 padding: var(--gap-bound) var(--gap-peer);
159 border-radius: var(--radius-control);
160 margin-left: var(--gap-bound);
161 }
162
163 @media (max-width: 839px) {
164 .discover-filter-toggle { display: inline-block; }
165 .discover-layout { grid-template-columns: 1fr; }
166 .discover-sidebar { display: none; }
167 /* Open state lives on the layout, not the sidebar: the sidebar is replaced
168 by an out-of-band swap on every filter change and would lose the class. */
169 .discover-layout.filters-open .discover-sidebar { display: block; margin-bottom: var(--gap-section); }
170 .table-header, .table-row { grid-template-columns: 40px 1fr 60px; }
171 .table-row-item { grid-template-columns: 1fr 30px; }
172 .table-row-item .table-row-link { grid-template-columns: 40px 1fr 60px; }
173 .table-header.projects-header, .table-row.project-row { grid-template-columns: 1fr 70px; }
174 .row-date, .row-items, .row-category { display: none; }
175 .table-header span:nth-child(3), .table-header span:nth-child(4), .table-header span:nth-child(5) { display: none; }
176 .table-header.projects-header span:nth-child(3), .table-header.projects-header span:nth-child(4) { display: none; }
177 }
178
179 @media (max-width: 599px) {
180 .table-header, .table-row { grid-template-columns: 1fr 60px; }
181 .table-row-item { grid-template-columns: 1fr 30px; }
182 .table-row-item .table-row-link { grid-template-columns: 1fr 60px; }
183 .row-type { display: none; }
184 .table-header span:first-child { display: none; }
185 .table-header.projects-header, .table-row.project-row { grid-template-columns: 1fr; }
186 .table-header.projects-header span:nth-child(2) { display: none; }
187 .row-price { font-size: var(--text-fine); }
188 .table-controls { flex-wrap: wrap; }
189 .mode-toggle { flex-wrap: wrap; }
190 .table-footer { flex-direction: column; gap: var(--gap-peer); align-items: flex-start; }
191 }
192
193 /* ── Feature Cards & Suggestion Input (shared by wizard + settings) ── */
194
195 .type-grid {
196 display: grid;
197 grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
198 gap: var(--gap-section);
199 margin-bottom: var(--gap-section);
200 }
201
202 /* Canonical selectable-card primitive. One recipe; per-context size modifiers.
203 Used by wizard type pickers, pricing-model pickers, content-choice pickers,
204 monetization-mode pickers, anywhere a radio-like choice is rendered as a card. */
205 .card--selectable {
206 cursor: pointer;
207 display: block;
208 }
209 .card--selectable input[type="radio"],
210 .card--selectable input[type="checkbox"] {
211 position: absolute;
212 opacity: 0;
213 pointer-events: none;
214 }
215
216 .card--selectable-inner {
217 display: flex;
218 flex-direction: column;
219 align-items: center;
220 justify-content: center;
221 padding: var(--gap-section);
222 border: 2px solid var(--border);
223 border-radius: var(--radius-square);
224 background: var(--surface-page);
225 text-align: center;
226 transition: border-color 0.15s, background 0.15s;
227 min-height: 60px;
228 }
229 /* Variant: column-aligned content (text-heavy pricing cards). */
230 .card--selectable.is-text-heavy .card--selectable-inner {
231 align-items: stretch;
232 text-align: left;
233 padding: var(--gap-section);
234 }
235
236 .card--selectable input:checked + .card--selectable-inner,
237 .card--selectable.is-selected .card--selectable-inner {
238 border-color: var(--action);
239 background: var(--highlight-faint);
240 }
241
242 .card--selectable:hover .card--selectable-inner {
243 background: var(--surface-sunken);
244 }
245
246 /* Focus. The card is a <label> wrapping a visually hidden radio, so the focus
247 lands on the input and the ring has to be drawn on the inner surface. The
248 <a> form of the card takes focus itself. */
249 .card--selectable:focus-visible,
250 .card--selectable input:focus-visible + .card--selectable-inner {
251 outline: 2px solid var(--focus-ring);
252 outline-offset: 2px;
253 }
254
255 .card--selectable-label {
256 font-weight: bold;
257 font-size: var(--text-note);
258 }
259
260 .card--selectable-desc {
261 font-size: var(--text-fine);
262 color: var(--content-muted);
263 margin-top: var(--gap-bound);
264 }
265
266 /* Disabled selectable card, visually dimmed, ignores pointer events. */
267 .card--selectable.is-disabled {
268 opacity: 0.7;
269 pointer-events: none;
270 }
271
272 .suggestion-input {
273 position: relative;
274 }
275
276 .suggestion-dropdown {
277 display: none;
278 position: absolute;
279 top: 100%;
280 left: 0;
281 right: 0;
282 background: var(--surface-page);
283 border: 1px solid var(--border);
284 border-top: none;
285 border-radius: 0 0 var(--radius-square) var(--radius-square);
286 max-height: 200px;
287 overflow-y: auto;
288 z-index: var(--z-dropdown);
289 box-shadow: var(--elevation-overlay);
290 }
291
292 .suggestion-dropdown.open {
293 display: block;
294 }
295
296 .suggestion-item {
297 padding: var(--gap-peer) var(--gap-section);
298 cursor: pointer;
299 font-size: var(--text-note);
300 }
301
302 .suggestion-item:hover {
303 background: var(--surface-overlay);
304 }
305
306 .suggestion-create {
307 font-style: italic;
308 opacity: 0.8;
309 border-top: 1px solid var(--border);
310 }
311
312 /* The tip offer is described (`src/quasi/tip.rs`), so its button, its
313 disclosure, its two fields and its submit are the design system's. What was
314 here restated them, and a restated rule fails the drift check. */
315
316 /* ===========================================
317 COLLECTION PICKER (shared dropdown)
318 =========================================== */
319
320 .collection-picker-anchor { position: relative; }
321 .collection-picker {
322 position: absolute; left: 0; right: 0; top: 100%; z-index: var(--z-picker);
323 background: var(--surface-page); border: 1px solid var(--border);
324 box-shadow: var(--elevation-overlay); min-width: 220px;
325 }
326 .collection-picker-list { max-height: 200px; overflow-y: auto; padding: var(--gap-bound) 0; }
327 .collection-picker-item {
328 display: flex; align-items: center; gap: var(--gap-peer);
329 padding: var(--gap-peer) var(--gap-section); cursor: pointer; font-size: var(--text-note);
330 }
331 .collection-picker-item:hover { background: var(--surface-sunken); }
332 .collection-picker-loading,
333 .collection-picker-create { border-top: 1px solid var(--border); padding: var(--gap-peer) var(--gap-section); }
334 .collection-picker-create form { display: flex; gap: var(--gap-peer); }
335 .collection-picker-create input { flex: 1; padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-note); min-width: 0; }
336 .collection-picker-create button { font-size: var(--text-note); white-space: nowrap; }
337
338 /* Save button on discover cards */
339 .row-save, .grid-card-save {
340 background: none; border: none; cursor: pointer;
341 font-size: var(--text-note); padding: var(--gap-bound) var(--gap-peer); opacity: 0.4;
342 color: var(--content); transition: opacity 0.15s;
343 }
344 .row-save:hover, .grid-card-save:hover { opacity: 1; }
345 .grid-card-save {
346 position: absolute; top: 0.4rem; right: 0.4rem; z-index: var(--z-raised);
347 background: var(--surface-page); border-radius: var(--radius-control); padding: var(--gap-bound) var(--gap-peer);
348 box-shadow: var(--elevation-overlay); opacity: 0;
349 }
350 .grid-card:hover .grid-card-save { opacity: 0.7; }
351 .grid-card-save:hover { opacity: 1; }
352
353 /* Item page save button saved state */
354 button.saved { border-color: var(--action); color: var(--action); }
355
356 /* ===========================================
357 SEARCH BOX
358 ===========================================
359 The box and its suggestion list are a described Field (N8), which draws its
360 own control and its own list. What is left here is where the toolbar puts
361 it: a flex child that takes the rest of the row, and the positioning context
362 the list is absolutely placed against. */
363
364 .search-wrapper { position: relative; flex: 1; min-width: 0; }
365
366 /* ===========================================
367 DOC UI EXAMPLES (embedded live UI in docs)
368 =========================================== */
369
370 .doc-ui {
371 margin: var(--gap-pane) 0; border: 1px solid var(--border); border-radius: var(--radius-control); overflow: hidden;
372 }
373 .doc-ui-frame {
374 padding: var(--gap-section); background: var(--surface-overlay); pointer-events: none; user-select: none;
375 }
376 .doc-ui figcaption {
377 padding: var(--gap-peer) var(--gap-section); font-size: var(--text-fine); opacity: 0.6;
378 border-top: 1px solid var(--border); background: var(--surface-overlay);
379 font-family: var(--font-mono);
380 }
381 .doc-ui-missing {
382 padding: var(--gap-section); opacity: 0.4; font-style: italic;
383 }
384
385 /* Library "New" badge for items with undownloaded versions. Not a status, so
386 it takes no tone; it is the plain badge with the accent hue and a little
387 more weight, since "New" is the one badge meant to catch the eye. */
388 .badge-new {
389 color: var(--action); font-weight: bold;
390 margin-left: var(--gap-peer); vertical-align: middle;
391 }
392
393 /* ===========================================
394 BENEFIT LIST (bulleted feature lists, e.g. Stripe Connect prompt)
395 =========================================== */
396 .benefit-list {
397 list-style: none;
398 padding: 0;
399 margin: 0 auto var(--gap-pane);
400 max-width: 280px;
401 text-align: left;
402 display: flex;
403 flex-direction: column;
404 gap: var(--gap-peer);
405 }
406 .benefit-list li {
407 position: relative;
408 padding-left: var(--gap-section);
409 font-size: var(--text-note);
410 }
411 .benefit-list li::before {
412 content: "";
413 position: absolute;
414 left: 0.15rem;
415 top: 0.55rem;
416 width: 6px;
417 height: 6px;
418 border-radius: var(--radius-round);
419 background: var(--success);
420 }
421
422 /* ===========================================
423 CHECKLIST (setup / onboarding step lists)
424 =========================================== */
425 .checklist {
426 margin-bottom: var(--gap-pane);
427 padding: var(--gap-section) var(--gap-pane);
428 background: var(--surface-overlay);
429 border: 1px solid var(--border);
430 }
431 .checklist-header {
432 display: flex;
433 justify-content: space-between;
434 align-items: center;
435 margin-bottom: var(--gap-section);
436 }
437 .checklist-title {
438 margin: 0;
439 font-family: var(--font-display);
440 font-weight: bold;
441 }
442 .checklist-mark {
443 width: 1.1rem;
444 height: 1.1rem;
445 border-radius: var(--radius-round);
446 border: 2px solid var(--border);
447 flex-shrink: 0;
448 box-sizing: border-box;
449 }
450 .checklist-mark--done {
451 border-color: var(--success);
452 background: var(--success);
453 }
454 .checklist-label {
455 flex: 1;
456 font-size: var(--text-note);
457 }
458 .checklist-label--done {
459 opacity: 0.5;
460 text-decoration: line-through;
461 }
462
463