forked from OurHike/OurHike
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdesktop.css
More file actions
446 lines (397 loc) · 18.6 KB
/
Copy pathdesktop.css
File metadata and controls
446 lines (397 loc) · 18.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
/* The app on a big screen.
*
* Every layout rule in this file sits inside a media query, and that is the
* design rather than a coincidence. WEBSITE.md §8 sets the constraint: the
* desktop layout must not regress the phone layout, which is the one that gets
* used on a mountain. A rule that can only match above 900px, or only where
* there is a real pointer, cannot reach the phone at all - so the guarantee is
* structural instead of something to re-check by hand every time this file
* changes.
*
* There is exactly one unguarded rule, at the bottom, and it is deliberate:
* focus rings. See the note there for why gating those would be a bug rather
* than consistency.
*
* The breakpoint is WEBSITE.md §6's 900px, and it is also in
* lib/useDesktop.ts. The few things CSS cannot do - the legend announcing
* itself as a dialog, a component that renders null when closed - are handled
* there, and the two numbers have to agree or there is a band of widths with a
* sidebar and a modal legend in it.
*
* What §6 asks for and what is here:
* layout the tab bar becomes a left sidebar done
* branding the mark at the foot of that sidebar done
* legend a persistent panel, not a dismissable sheet done
* search a field over the map, not a full sheet done
* (still opened from the header - Search renders
* nothing until it is, so "permanent field in the
* chrome" is not what this is)
* input hover, focus rings, relaxed touch targets done
* elevation the full interactive chart NOT done
* download UX reframed for a machine with signal done
* routing deep links, back button NOT done
*
* The two that are missing are named in the file that omits them, on purpose.
* The elevation chart is a new component rather than a restyle of an existing
* one, and routing is called "the largest single change in this workstream" by
* §6 itself - neither belongs in a layout pass.
*/
@media (min-width: 900px) {
/* ---------- Shell: the tab bar becomes a sidebar ---------- */
/* The bar's position at the bottom of a phone is a thumb-reach decision, and
a thumb is not involved here. Reordering in CSS rather than moving it in
the DOM keeps it last in the source, where a keyboard reaches the screen's
content before its navigation - the order a screen reader wants too. */
.app__screen,
.map-screen {
flex-direction: row;
}
.app__screen > .tab-bar,
.map-screen > .tab-bar {
order: -1;
flex-direction: column;
justify-content: flex-start;
flex: 0 0 13rem;
border-top: 0;
/* Pine instead of chrome.css's white - the "Chrome colour" section below
is the why. The phone keeps its light bar not by promise but because
nothing in this file can match below 900px. */
background: var(--bg-chrome);
border-right: 1px solid var(--border-chrome);
/* The home-indicator inset is a notched-phone concern and nothing else. */
padding-bottom: 0;
padding-top: var(--space-3, 12px);
}
/* The tabs run down the sidebar rather than across it. Left flexible rather
than sized: growing to fill the column is what pushes the brand mark below
it onto the bottom edge, with no height for either one to be told. */
.app__screen > .tab-bar .tab-bar__tabs,
.map-screen > .tab-bar .tab-bar__tabs {
flex-direction: column;
}
.app__screen > .tab-bar .tab-bar__tab,
.map-screen > .tab-bar .tab-bar__tab {
flex: 0 0 auto;
text-align: left;
padding: var(--space-3, 12px) var(--space-4, 16px);
font-size: 14px;
/* Worth a number: chrome.css's resting label is stone-500 on white,
3.6:1 - under WCAG AA's 4.5:1 floor. Sage on pine is 11.4:1, so the
sidebar fixes in passing what the phone bar still owes. */
color: var(--fg-chrome-2);
border-top: 0;
/* The selected marker moves to the inside edge, where the eye already is
for a vertical list. */
border-left: 3px solid transparent;
}
/* ---------- Brand mark: the foot of the sidebar ---------- */
/* The bottom-left corner of the page, which exists only in this layout - on a
phone this element is not drawn at all (chrome.css).
It is the LAST child of a column whose tab list grows, so it is carried to
the bottom edge by the layout rather than pinned there: no absolute
positioning, and no way for it to end up on top of the tabs if the tab set
ever grows past the height of the screen. */
.app__screen > .tab-bar .tab-bar__brand,
.map-screen > .tab-bar .tab-bar__brand {
display: flex;
/* Stacked, not side by side. At a 64px icon and a 32px wordmark the
horizontal lockup is about 206px wide, against 176px of usable width in
a 13rem sidebar - so laying it out in a row would either overflow the
column or force the wordmark back down to a size that reads as a
caption. */
flex-direction: column;
gap: var(--space-2, 8px);
padding: var(--space-4, 16px);
/* Aligned with the tab labels above it rather than centred in the column.
The sidebar has one left edge and everything in it reads off that. */
align-items: flex-start;
/* Back to DOM order. On a phone the mark is pulled ahead of the tabs to
reach the left end of the row; here it belongs after them, at the foot
of the column. */
order: 0;
}
/* Room for the real mark once the bar is a 13rem column rather than a strip
shared with three thumb targets. */
.app__screen > .tab-bar .tab-bar__brand-icon,
.map-screen > .tab-bar .tab-bar__brand-icon {
width: 64px;
height: 64px;
/* The artwork's own 20/96 at this size, as on the phone. */
border-radius: 13px;
}
.app__screen > .tab-bar .tab-bar__brand-wordmark,
.map-screen > .tab-bar .tab-bar__brand-wordmark {
display: block;
/* chrome.css colours the wordmark with --fg-1, which is stone-900 under
the light theme - near-black type on a pine-900 column. The chrome's
own foreground, in both themes. */
color: var(--fg-chrome-1);
}
.app__screen > .tab-bar .tab-bar__tab[aria-selected='true'],
.map-screen > .tab-bar .tab-bar__tab[aria-selected='true'] {
border-top-color: transparent;
color: var(--fg-chrome-1);
/* The rail is the accent, not the brand green: on pine the two greens
melt together, and a blaze on a dark trunk is what the mark on the
sidebar's own foot is drawn as. 14% where the light theme used 8% -
a wash needs more of itself to register on a dark ground. */
border-left-color: var(--accent-chrome);
background: color-mix(in srgb, var(--accent-chrome) 14%, transparent);
}
/* ---------- Chrome colour: the frame, not the paper ---------- */
/* The sidebar was the one pure-white surface in the app, beside a header on
near-white paper, beside a map drawn on paper - four lightnesses within a
few percent of each other, and the brightest of them was the navigation.
Painted pine, the chrome recedes to a frame and the map becomes the
single light surface on the screen, which is the right hierarchy for a
map app: terrain is the object, everything else holds it.
Everything here reads the --*-chrome aliases (tokens/colors.css) rather
than hues - themeTokens.test.ts fails this file the moment it names a
base colour - so the dark theme re-points the same rules at its own inks
and night is left essentially as it was.
Colour only, and inside the same 900px guard as the layout. Whether the
phone's bar should follow the sidebar off white is a real question, but
it is its own decision with its own thumb-zone stakes, not something to
smuggle in with the desktop's. */
.map-screen .status-strip {
background: var(--bg-chrome);
color: var(--fg-chrome-3);
}
/* The light theme's --danger is blaze-orange-dark: 2.2:1 on pine, a
closure flag nobody sees. The on-chrome danger is the one the dark theme
already uses for the same job. */
.map-screen .status-strip__flag {
color: var(--danger-on-chrome);
}
.map-screen .map-header {
background: var(--bg-chrome);
border-bottom-color: var(--border-chrome);
}
.map-screen .map-header__eyebrow {
color: var(--fg-chrome-2);
}
.map-screen .map-header__position {
color: var(--fg-chrome-1);
}
/* chrome.css fills these with --bg-surface - the brightest possible square
on the darkest surface in the app. An outline drawn from the chrome's own
foreground holds its shape on pine and on ink alike. */
.map-screen .map-header__button {
color: var(--fg-chrome-2);
background: transparent;
border-color: color-mix(in srgb, var(--fg-chrome-2) 40%, transparent);
}
.map-screen .map-header__button:hover {
background: color-mix(in srgb, var(--fg-chrome-1) 10%, transparent);
color: var(--fg-chrome-1);
}
/* The global focus ring at the bottom of this file is --brand-primary:
forest on pine is 1.9:1, a ring that cannot be seen on exactly the
surfaces this section darkens. Outline colour only - width, offset and
everywhere else on the page stay the global rule's. */
.app__screen > .tab-bar :focus-visible,
.map-screen > .tab-bar :focus-visible,
.map-screen .map-header :focus-visible {
outline-color: var(--fg-chrome-1);
}
/* The screen's own content takes the rest of the frame. `min-width: 0` for
the same reason .app__screen > :first-child needs `min-height: 0` on a
phone: a flex item floors at its content width, and a map that cannot
shrink pushes the sidebar off the side instead of scrolling. */
.app__screen > :first-child {
min-width: 0;
}
/* ---------- Map screen: chrome down the side, map in the middle ---------- */
.map-screen__main {
/* Already a flex column from chrome.css; this is only the width floor,
which matters once it is a row item rather than a column one. */
min-width: 0;
}
/* The legend sits beside the map rather than over it - §6's "nothing that
covers the map should need dismissing to see the map". */
.map-screen__body {
flex-direction: row;
}
/* ---------- Legend: a panel, not a sheet ---------- */
.legend--persistent {
/* Relative, not static: in flow exactly the same, but still a containing
block. The background picker at this panel's foot hides its real
radios with absolute 1px boxes, and the sheet's `position: absolute`
was what anchored them on a phone - un-positioning the panel entirely
handed them to the document, where a legend long enough to scroll
would stretch the page the way Settings' pickers did (#631). */
position: relative;
max-height: none;
flex: 0 0 17rem;
border-top-left-radius: 0;
border-top-right-radius: 0;
border-left: 1px solid var(--border-1);
box-shadow: none;
/* A column, so the download block at the end of it can be pushed to the
foot of the panel below. On a phone the legend is a sheet that ends
where its content ends and that block is already the bottom of it; here
the panel is full height, and left in normal flow it would sit against
the pin list with a screen of empty paper under it. */
display: flex;
flex-direction: column;
}
/* The rule that does the pushing. The block, not the link inside it: the
background choice and the way to the download are one thing at the foot of
this panel (chrome.css), and pushing only the link would put the panel's
full height between them - which is the arrangement they were moved out of.
It collapses to nothing the moment the content is taller than the panel,
which is the right answer then too - the block goes back to being the last
thing you scroll to. */
.legend--persistent .legend__downloads {
margin-top: auto;
}
/* A panel that is always there has nothing to dismiss, and a close button
that reopens nothing is a trap. The component drops the control; this is
belt and braces for a release where the two disagree. */
.legend--persistent .legend__close {
display: none;
}
/* With the legend permanently visible, the button that opens it has nothing
to do. */
.map-header__button--legend {
display: none;
}
/* ---------- Search: a field, not a sheet ---------- */
/* Still out of flow. `position: static` here was a real bug:
.map-screen__canvas is a flex container (chrome.css), so a statically
positioned search stops overlaying the map and becomes a flex sibling of
it, taking real space. The map does not give that space back, so the
canvas grows past the frame and everything after it goes with it - back
when the canvas was a flex ROW, that showed up in Chromium at 1440px as
327px of horizontal overflow, with the legend panel starting at x=1462,
entirely off the screen. It is a column now (the credit line sits under
the map), so the same mistake would spend height instead - a different
symptom of the same bug.
So only the shape changes: a field anchored over the map's top-left,
sized to its contents, rather than a sheet covering the whole canvas.
Nothing it does can move the map or the legend. */
.search {
inset: auto;
top: var(--space-3, 12px);
left: var(--space-3, 12px);
width: 22rem;
max-width: calc(100% - 2 * var(--space-3, 12px));
/* The results list already scrolls (.search__results), so capping the
height here keeps a long list inside the canvas instead of running it
off the bottom. */
max-height: calc(100% - 2 * var(--space-3, 12px));
border: 1px solid var(--border-1);
border-radius: 8px;
}
/* ---------- The download window: a panel, not a takeover ---------- */
/* WEBSITE.md §6's "download UX reframed for a machine with signal" is copy
(screens/Downloads.tsx already says the download is for the phone you will
be carrying); this is its shape. A 1440px screen filled edge to edge by
three radio buttons is a dialog pretending to be an app, and it hides the
map that is the reason anyone opened it. */
.downloads-dialog {
align-items: center;
justify-content: center;
padding: var(--space-5, 20px);
}
.downloads-dialog__panel {
flex: 0 1 auto;
width: min(28rem, 100%);
max-height: 100%;
border-radius: 14px;
/* The head's bottom border and the panel's corners have to agree, and the
body is what would otherwise square them off when it scrolls. */
overflow: hidden;
box-shadow: 0 12px 40px rgb(0 0 0 / 24%);
}
/* ---------- First run: a card on the map, not a band across it ---------- */
/* On a phone the entry card is anchored to the bottom edge, where the thumb
is. A 1440px browser has no thumb and no bottom edge worth reaching, and a
full-width strip of it there would bury the map under a band of paper for
three paragraphs of text. Centred instead, the same shape the download
window takes on this screen - so the map is visible all the way round the
steps rather than only above them. */
.onboarding {
justify-content: center;
align-items: center;
padding: var(--space-5, 20px);
}
.onboarding__card {
width: min(28rem, 100%);
max-height: 100%;
border-radius: 14px;
box-shadow: 0 12px 40px rgb(0 0 0 / 24%);
}
/* The dim a floating card needs and a bottom sheet does not. On a phone the
card is attached to the bottom edge and reads as part of the frame, the
way the legend sheet does; here it is over the middle of a wide map, and
without something behind it the page reads as a card that has come loose.
Lighter than the download window's 35% (.downloads-dialog): that one is a
decision the map is no part of, and this one is about the map. */
.onboarding::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: rgb(0 0 0 / 22%);
}
}
/* ---------- Input: what a mouse and a keyboard get ---------- */
/* Keyed on the POINTER, not the width. A 1024px tablet is a wide touch screen
and still needs 44px targets; a 900px window on a laptop does not. Tying
these to the breakpoint would shrink the targets on exactly the device where
that hurts most. */
@media (pointer: fine) {
:root {
/* WCAG 2.5.5's 44px floor is a touch-target rule. A mouse pointer is
precise, and the tighter spacing is what makes a desktop UI stop looking
like a phone app that was stretched. */
--min-touch-target: 32px;
}
}
@media (hover: hover) {
.tab-bar__tab:hover {
color: var(--fg-1);
background: color-mix(in srgb, var(--brand-primary) 5%, transparent);
}
.legend__row:hover {
background: color-mix(in srgb, var(--brand-primary) 5%, transparent);
}
.legend__toggle:hover {
color: var(--fg-1);
}
}
/* The sidebar's hover, restated for pine. The wash above is 5% forest over
white; over pine-900 the same mix is nothing anyone can see. A lift of the
chrome's own foreground reads as "raised" on any dark ground, in both
themes. Gated on the width as well as the pointer, because below 900px the
bar is the phone's light strip whatever the pointer is - and there the
rules above are the right ones. */
@media (hover: hover) and (min-width: 900px) {
.app__screen > .tab-bar .tab-bar__tab:hover,
.map-screen > .tab-bar .tab-bar__tab:hover {
color: var(--fg-chrome-1);
background: color-mix(in srgb, var(--fg-chrome-1) 8%, transparent);
}
}
/* Focus rings, everywhere, with no media query around them.
*
* :focus-visible mostly matches when the browser judges a focus ring
* warranted - keyboard navigation, essentially - so for buttons and links it
* is silent for a tap. (Not for text inputs: browsers match those on ANY
* focus, including a tap into the search field, so this rule does fire on
* phones.) Gating it on pointer or width would still be both unnecessary and
* harmful: a phone with a bluetooth keyboard, or a screen-reader user
* driving a touch device, needs to see where focus is, and neither shows up
* as `pointer: fine`.
*
* Outline ONLY - nothing here may restyle the control itself. :where()
* contributes zero specificity, which leaves this selector at (0,1,0), a
* cascade TIE with every single-class rule in the app; this file is imported
* last, so a tie is a win, and a border-radius here once snapped the 12px
* search field square under focus. The outline follows each control's own
* corner rounding by itself. */
:where(button, a, input, select, [tabindex]):focus-visible {
outline: 2px solid var(--brand-primary);
outline-offset: 2px;
}