-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathshell.css
More file actions
747 lines (648 loc) · 25.7 KB
/
Copy pathshell.css
File metadata and controls
747 lines (648 loc) · 25.7 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
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
/* ==========================================================================
game-console-ui-theme / shell
--------------------------------------------------------------------------
The dashboard: ground, tile rows, the focus ring, full-screen views, the
hint bar. This file is the shell class's equivalent of a register theme's
play layer, except it is not a garnish; it is the archetype. Delete it
and shell.js and you still have a readable page built from tokens.css and
components.css, which is the JS-last contract this class is built on.
HOW THE PROGRESSIVE ENHANCEMENT WORKS. The markup is an ordinary
document: a header, sections of anchor links, article sections in reading
order, a footer. This file styles that document twice:
1. The base styles below assume no JavaScript. Rows wrap instead of
scrolling sideways, every tile is a visible link, every view is a
titled section further down the page, and the page scrolls like a
page. Everything is reachable and readable.
2. shell.js puts `tv-live` on the <html> element as its first act.
Every rule scoped under .tv-live upgrades the same markup into a
dashboard: the page stops scrolling, rows run off the right edge
and scroll, views become full-screen surfaces that cover the browse
surface, and the hint bar rewrites itself to name the controls that
now exist. No markup is duplicated; the document is the dashboard.
THE ONE THING THIS SHELL IS ABOUT. Everything here is arranged so the
focus ring wins. Tiles rest quietly (a 1.16:1 fill step off the ground,
a 1.68:1 edge); the ring lands at 10.39:1 against the same ground. That
ordering is the design, not a detail of it, because a surface driven
without a pointer has to answer "where am I" from across a room.
There is not one colour literal in this file, and every length that
carries a design decision is a token from tokens.css. Audited: the file
contains exactly one px literal, `left: -9999px`, which is where the skip
link parks itself offscreen and is not a design value. The rest of what is
not a token is not a decision either: viewport units, the 767px
breakpoint, and the percentages inside one decorative gradient.
Requires tokens.css. Load it, then components.css, then this, then the
script:
<link rel="stylesheet" href="tokens/tokens.css" />
<link rel="stylesheet" href="components/components.css" />
<link rel="stylesheet" href="shell/shell.css" />
<script src="shell/shell.js" defer></script>
========================================================================== */
/* --------------------------------------------------------------------------
THE FIELD, AND THE GROUND SYSTEM
The body is the field the dashboard sits on, and that ground is a slot,
not a colour. Two axes stay independent: the register (tokens.css) says
what colours exist, the ground (a data attribute on body) says how the
field composes them. This is the mechanism the shell class's pilot
established as a wallpaper system; the axis is renamed here because what
varies on a room-scale screen is the light in the room, not a picture
hung behind the furniture.
THE MECHANISM (class standard for shell themes):
body[data-ground="flat"] the plain field, also what you get with no
attribute at all, so the no-JS document
carries it by plain CSS
body[data-ground="panes"] reveals the inline SVG scene layer
shell.js only toggles the attribute; every visual belongs to these
attribute-scoped rules. The picker, like all behaviour, exists only after
enhancement.
CONTRAST BY CONSTRUCTION, INVERTED. The pilot restricted its wallpaper to
the light end of the palette so dark ink stayed legible over it. This
register is dark, so the restriction inverts: the scene layer may paint
ONLY with ground, scene, chrome, surface, surface-raised and border. The
LIGHTEST of those is surface-raised, and full ink over it measures
11.79:1, so the worst text-over-scene pairing on the field (a row
heading, full ink) clears AA more than twice over without a scrim.
Measured over every allowed paint: chrome 17.96, ground 17.31, scene
15.62, surface 14.94, surface-raised 11.79, border 10.31. Tiles, views
and the hint bar are opaque surfaces with their own measured pairings, so
the scene cannot reach the text inside them. The class guarantee, should
a future variant want brighter paint: a token-driven scrim layer between
scene and content, with the ratios re-measured through the scrim. No
shipped variant needs one.
The flat field: a soft pool of light spilling from above the first row,
drawn with the scene tone in a single radial gradient. Decorative;
nothing depends on seeing it.
-------------------------------------------------------------------------- */
.tv-field {
margin: 0;
min-height: 100vh;
min-height: 100dvh;
background-color: var(--tv-ground);
background-image: radial-gradient(
130% 70% at 50% -8%,
var(--tv-scene),
transparent 62%
);
background-repeat: no-repeat;
color: var(--tv-ink);
font-family: var(--tv-font-sans);
font-size: var(--tv-text-body);
font-weight: var(--tv-weight-body);
line-height: var(--tv-leading-body);
}
/* The scene layer: an inline SVG in the page markup, because an SVG loaded
through an image or a data URI is an isolated document that cannot read
custom properties. Inlined, the paint rules below reach it, and a re-skin
of tokens.css re-skins the scene with everything else. Hidden until the
body attribute asks for it, so it costs the no-JS document nothing but
bytes. */
.tv-wallpaper {
display: none;
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
}
.tv-wallpaper > svg {
width: 100%;
height: 100%;
}
body[data-ground="panes"] .tv-wallpaper {
display: block;
}
/* Scene paint rules. Only the allowed tokens from the contrast contract
above. */
.tv-wallpaper .tv-art-line {
stroke: var(--tv-border);
fill: none;
}
.tv-wallpaper .tv-art-pane {
fill: var(--tv-scene);
stroke: var(--tv-border);
}
.tv-wallpaper .tv-art-lit {
fill: var(--tv-surface);
stroke: none;
}
.tv-wallpaper .tv-art-edge {
fill: var(--tv-surface-raised);
stroke: none;
}
/* Anyone who has asked for more contrast gets the flat field regardless of
what the picker says. Placed after the scene rule so it wins on source
order at equal specificity. */
@media (prefers-contrast: more) {
body[data-ground] .tv-wallpaper {
display: none;
}
}
/* --------------------------------------------------------------------------
THE LIVE LAYOUT
Live, the page stops being a page: the field becomes a three-band grid,
the middle band scrolls and the two chrome bands do not. Without the
script none of this applies and the document scrolls normally.
-------------------------------------------------------------------------- */
.tv-live,
.tv-live body {
height: 100vh;
height: 100dvh;
overflow: hidden;
}
/* minmax(0, 1fr) on the column, not a bare 1fr and not nothing at all. An
implicit auto column sizes to the widest thing inside it, and the widest
thing inside this one is a row of tiles that is meant to run off the edge,
so the whole dashboard would grow to the width of its longest row and the
row would never scroll. The 0 minimum is what lets the track shrink below
its content and hand the overflow back to the row where it belongs. */
.tv-live .tv-field {
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-columns: minmax(0, 1fr);
}
.tv-live .tv-main {
min-height: 0; /* lets the scrolling band actually shrink */
}
/* --------------------------------------------------------------------------
SKIP LINK
Above everything, including an open view.
-------------------------------------------------------------------------- */
.tv-skip {
position: absolute;
left: -9999px;
}
.tv-skip:focus {
left: var(--tv-space-4);
top: var(--tv-space-4);
z-index: var(--tv-z-skip);
padding: var(--tv-space-3) var(--tv-space-4);
color: var(--tv-ink);
background: var(--tv-surface);
border: var(--tv-border-width) solid var(--tv-border-strong);
border-radius: var(--tv-radius);
font-weight: var(--tv-weight-bold);
}
/* --------------------------------------------------------------------------
BRAND
The document header. As a page it is a normal masthead; live it stays a
masthead, because a screen read from ten feet has room for one and
because shrinking it to a chip would make it unreadable at the distance
this register is designed for. Only the tagline goes, which is a sentence
about the page rather than a part of the dashboard.
-------------------------------------------------------------------------- */
/* The brand block and the board are lifted into the positioned layer, above
the scene. A fixed element with z-index 0 paints above ordinary
block-level content no matter where it sits in the document, so without
these two lines the scene draws over every row heading: tiles survive it
only because they are positioned themselves. Found by looking at a render,
which is the only way this class of bug is ever found. */
.tv-brand,
.tv-rows {
position: relative;
z-index: var(--tv-z-rows);
}
.tv-brand {
padding: var(--tv-space-6) var(--tv-rail) var(--tv-space-3);
}
.tv-brand-name {
margin: 0;
font-size: var(--tv-text-h1);
font-weight: var(--tv-weight-display);
letter-spacing: var(--tv-tracking-tight);
line-height: var(--tv-leading-tight);
}
.tv-brand-tagline {
margin: var(--tv-space-3) 0 0;
max-width: var(--tv-measure);
color: var(--tv-ink-muted);
}
.tv-live .tv-brand-tagline {
display: none;
}
/* --------------------------------------------------------------------------
ROWS
The navigation surface. As a page: sections whose tile lists wrap, so
every tile is visible and nothing is hidden off an edge that cannot
scroll without a script. Live: each row is a single line that runs off
the right edge and scrolls, which is the shape this archetype is.
-------------------------------------------------------------------------- */
.tv-rows {
padding-bottom: var(--tv-space-4);
}
.tv-live .tv-rows {
height: 100%;
overflow-y: auto;
overscroll-behavior: contain;
}
/* While a view is open the browse surface is completely covered. It leaves
the tab order and the accessibility tree with it, so Tab inside a view
never lands on something the reader cannot see. visibility rather than
display, because visibility keeps the layout and therefore keeps every
row's horizontal scroll position for when the reader comes back. */
.tv-live .tv-rows[data-tv-hidden] {
visibility: hidden;
}
.tv-row {
margin: 0 0 var(--tv-row-gap);
}
.tv-row-title {
margin: 0;
padding: 0 var(--tv-rail);
font-size: var(--tv-text-h2);
font-weight: var(--tv-weight-display);
line-height: var(--tv-leading-tight);
letter-spacing: var(--tv-tracking-tight);
}
/* The track. Its padding is not decoration: a focused tile grows and wears
a ring held off its edge, and an overflow container clips both. The
padding is the room the ring needs, and the negative side margin pulls
the tiles back onto the rail so the row still lines up with its heading.
The scrollbar is hidden deliberately. Every tile in a row is in the tab
order and reachable with the arrow keys, so nothing here is reachable
only by dragging; what tells a reader the row continues is the next tile
showing at the edge, which is the affordance this shape has always used.
-------------------------------------------------------------------------- */
.tv-row-track {
display: flex;
flex-wrap: wrap;
gap: var(--tv-tile-gap);
margin: 0;
padding: var(--tv-space-3) var(--tv-rail);
list-style: none;
}
.tv-live .tv-row-track {
flex-wrap: nowrap;
overflow-x: auto;
scrollbar-width: none;
}
.tv-live .tv-row-track::-webkit-scrollbar {
display: none;
}
/* --------------------------------------------------------------------------
TILE
The unit of the whole archetype. A link, sized near 16:9 because what a
tile stands for on a screen like this usually is. Its resting state is
quiet on purpose; see the note at the top of this file.
-------------------------------------------------------------------------- */
.tv-tile {
position: relative; /* so a focused tile can lift above its neighbours */
flex: none;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: var(--tv-space-1);
width: var(--tv-tile-w);
height: var(--tv-tile-h);
padding: var(--tv-space-4);
color: var(--tv-ink);
text-decoration: none;
background: var(--tv-surface);
border: var(--tv-border-width) solid var(--tv-border);
border-radius: var(--tv-radius-tile);
box-shadow: var(--tv-shadow-tile);
transition:
transform var(--tv-duration) var(--tv-ease),
background-color var(--tv-duration) var(--tv-ease),
box-shadow var(--tv-duration) var(--tv-ease);
}
/* The mark. An original line glyph, painted with currentColor so it reads
the tokens like everything else, pushed to the top of the tile by the
auto margin so labels line up across a row whatever the glyph is. */
.tv-tile-glyph {
display: block;
width: var(--tv-glyph-size);
height: var(--tv-glyph-size);
margin-bottom: auto;
color: var(--tv-ink-muted);
}
.tv-tile-glyph > svg {
width: 100%;
height: 100%;
}
.tv-tile-label {
font-size: var(--tv-text-h3);
font-weight: var(--tv-weight-bold);
line-height: var(--tv-leading-snug);
letter-spacing: var(--tv-tracking-tight);
}
.tv-tile-note {
font-size: var(--tv-text-xs);
color: var(--tv-ink-muted);
line-height: var(--tv-leading-snug);
}
/* A chip in the tile's top corner, for the one word a tile sometimes has to
carry that is not its name. It borrows the badge component rather than
inventing a second chip, which is also what makes it show up honestly in
the re-skin failure CUSTOMIZE.md documents. */
.tv-tile-flag {
position: absolute;
top: var(--tv-space-3);
right: var(--tv-space-3);
}
.tv-tile:hover {
background: var(--tv-surface-raised);
}
.tv-tile:hover .tv-tile-glyph {
color: var(--tv-ink);
}
/* THE HERO ELEMENT. Five pixels of accent, held four off the tile, over a
tile that has lifted and grown. Nothing else on the browse surface is
allowed to look like this. */
.tv-tile:focus-visible {
z-index: 1;
outline: var(--tv-ring-width) solid var(--tv-ring);
outline-offset: var(--tv-ring-offset);
background: var(--tv-surface-raised);
box-shadow: var(--tv-shadow-raised);
transform: scale(var(--tv-focus-scale));
}
.tv-tile:focus-visible .tv-tile-glyph {
color: var(--tv-accent);
}
/* --------------------------------------------------------------------------
VIEW
What a tile opens. As a page it is a titled section in reading order,
which is the entire no-JS experience of this shell's content. Live it is
a full-screen surface that covers the browse surface and stops short of
the hint bar, so the control legend is true at every moment including
this one.
The view is its own scroll container AND the element that takes focus on
open, which means a keyboard reader can scroll it with the arrow keys the
moment it opens without hunting for a scroll region. shell.js adds the
dialog semantics at enhance time; the no-JS document claims none of it.
-------------------------------------------------------------------------- */
.tv-view {
max-width: var(--tv-view-width);
margin: 0 auto var(--tv-space-12);
padding: 0 var(--tv-rail);
}
.tv-view-head {
display: flex;
align-items: center;
gap: var(--tv-space-4);
margin: 0 0 var(--tv-space-6);
}
/* The slot the back control is injected into. display:contents so that an
empty slot in the no-JS document generates no box at all, and therefore
no stray flex gap in front of a title that has no button beside it. */
.tv-view-back {
display: contents;
}
.tv-view-title {
margin: 0;
font-size: var(--tv-text-h1);
font-weight: var(--tv-weight-display);
line-height: var(--tv-leading-tight);
letter-spacing: var(--tv-tracking-tight);
text-wrap: balance;
}
.tv-live .tv-view {
display: none;
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: var(--tv-hint-height);
z-index: var(--tv-z-view);
max-width: none;
margin: 0;
padding: var(--tv-space-12) var(--tv-rail) var(--tv-space-16);
overflow-y: auto;
overscroll-behavior: contain;
background: var(--tv-ground);
box-shadow: var(--tv-shadow-view);
}
.tv-live .tv-view.tv-open {
display: block;
}
/* The view takes focus when it opens, and it draws NO ring, which is the one
place in this theme where suppressing a focus outline is the correct call
rather than the lazy one. Three reasons, and all three have to hold:
The view is never user-focusable. It carries tabindex="-1" and is not in
the tab order; a reader cannot arrive here by pressing anything. Focus is
placed on it, once, by the script. That is the standard exception, and it
is why every dialog implementation does the same.
The feedback it would be duplicating is the entire screen changing. A
ring exists to answer "where am I" on a surface with many places to be.
A view has one place to be, and the reader just watched it arrive.
Most importantly, it would spend the budget. This theme's whole argument
is that the ring means "you are here" and is reserved for it. Drawn round
the whole viewport it means nothing, and a mark that sometimes means
nothing stops being trusted when it means something. Found by rendering
the state and looking at it; the assertion suite was perfectly happy.
Focus is not lost: the next Tab lands on the back control, which rings
normally, and every interactive thing inside the view rings normally. */
.tv-live .tv-view:focus {
outline: none;
}
/* Content inside a view is held to a measure even though the view is not,
because a 62-character line does not become more readable on a bigger
television. */
.tv-live .tv-view-inner {
max-width: var(--tv-view-width);
margin: 0 auto;
}
/* The back control is injected by shell.js, because a control that does
nothing must not exist in a document where the script never ran. It
carries a word as well as a mark: at ten feet, a lone glyph is a guess. */
.tv-back {
flex: none;
display: inline-flex;
align-items: center;
gap: var(--tv-space-2);
min-height: var(--tv-control-min);
padding: var(--tv-space-2) var(--tv-space-4);
font-family: var(--tv-font-sans);
font-size: var(--tv-text-sm);
font-weight: var(--tv-weight-medium);
color: var(--tv-ink);
background: var(--tv-surface);
border: var(--tv-border-width) solid var(--tv-border-strong);
border-radius: var(--tv-radius);
cursor: pointer;
}
.tv-back:hover {
background: var(--tv-surface-raised);
}
.tv-back:focus-visible {
outline: var(--tv-ring-width) solid var(--tv-ring);
outline-offset: var(--tv-ring-offset);
}
.tv-back > svg {
width: var(--tv-icon-sm);
height: var(--tv-icon-sm);
}
/* --------------------------------------------------------------------------
HINT BAR
The control legend, and the one piece of chrome that is on screen at
every moment. As a page it is a footer stating what is true without a
script: Tab and Enter. Live, shell.js rewrites the sentence to name the
controls enhancement just added, and rewrites it again when a view opens,
because the true sentence is different in the two states.
The vocabulary is deliberately neutral. This shell names keys and
actions; it never draws a lettered button in a circle, because the moment
it does it stops being a dashboard for a room-scale screen and starts
being an impression of somebody's console.
-------------------------------------------------------------------------- */
.tv-hint {
background: var(--tv-chrome);
border-top: var(--tv-border-width) solid var(--tv-border-strong);
}
.tv-hint-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--tv-space-4);
min-height: var(--tv-hint-height);
padding: var(--tv-space-2) var(--tv-rail);
}
.tv-hint-text {
margin: 0;
font-size: var(--tv-text-sm);
color: var(--tv-ink-muted);
}
.tv-hint-key {
color: var(--tv-ink);
font-weight: var(--tv-weight-medium);
}
.tv-hint-end {
flex: none;
display: flex;
align-items: center;
gap: var(--tv-space-3);
}
.tv-clock {
font-family: var(--tv-font-mono);
font-size: var(--tv-text-sm);
color: var(--tv-ink-muted);
}
/* A tray control: the hint bar's one square button, the ground picker that
shell.js injects. */
.tv-tray-btn {
display: grid;
place-items: center;
width: var(--tv-control-min);
height: var(--tv-control-min);
padding: 0;
color: var(--tv-ink-muted);
background: transparent;
border: var(--tv-border-width) solid transparent;
border-radius: var(--tv-radius);
cursor: pointer;
}
.tv-tray-btn:hover {
color: var(--tv-ink);
background: var(--tv-surface);
border-color: var(--tv-border);
}
.tv-tray-btn:focus-visible {
outline: var(--tv-ring-width) solid var(--tv-ring);
outline-offset: 0;
}
.tv-tray-btn > svg {
width: var(--tv-icon-sm);
height: var(--tv-icon-sm);
}
.tv-live .tv-hint {
position: relative;
z-index: var(--tv-z-chrome);
}
/* --------------------------------------------------------------------------
THE INDEX
The last block on the board, and the only navigation here that is not a
picture. Rows of tiles are this archetype's whole idea and they are also
its risk: a tile is a surface a reader has to recognise as navigation
before it works, and the reader who does not recognise it has no way
through the site at all. So the board ends with nine plain text links to
the same nine screens. Class decision 44 in desktop-os-theme's
docs/class-decisions.md is the rule and the argument.
It is the same with the script and without it, because it is nothing but
anchors. Live it is covered along with the rest of the board when a view
opens, which is correct: the board is where you are when you are choosing
where to go, and Esc or Back is what brings it back.
DISTANCE-LEGIBLE, WHICH IS WHY THIS IS NOT SMALL PRINT. A text index in a
ten-foot register cannot be a footer set in fine type. These are set at
the body size, in full ink, on targets --tv-control-min tall, so they can
be read and hit from where the tiles are read and hit. Full ink
measures 17.31:1 on the ground and 14.94:1 on the hover face; muted ink
would have been 8.74:1, which passes AA comfortably and still reads as a
row of things that are switched off, which is the wrong thing for the one
surface that has to look like navigation. The only thing quieter about an
index item than a tile is that it has no face until you reach it.
-------------------------------------------------------------------------- */
/* A rule above it, and room around the rule. The index is not another row
of the board and should not read as one: everything above it is a picture
you choose from, and this is the list of the same places in words. The
divider is also what makes the bottom edge of the browse surface look
deliberate at 1280, where the band scrolls and something has to be the
thing that is half on screen. */
.tv-index {
margin-top: var(--tv-space-8);
padding-top: var(--tv-space-6);
border-top: var(--tv-border-width) solid var(--tv-border);
}
.tv-index-list {
display: flex;
flex-wrap: wrap;
gap: var(--tv-space-2) var(--tv-space-3);
margin: 0;
padding: var(--tv-space-3) var(--tv-rail);
list-style: none;
}
.tv-index-item {
display: inline-flex;
align-items: center;
min-height: var(--tv-control-min);
padding: var(--tv-space-2) var(--tv-space-4);
font-size: var(--tv-text-body);
font-weight: var(--tv-weight-medium);
line-height: var(--tv-leading-snug);
color: var(--tv-ink);
text-decoration: none;
border: var(--tv-border-width) solid transparent;
border-radius: var(--tv-radius);
}
.tv-index-item:hover {
background: var(--tv-surface);
border-color: var(--tv-border);
}
.tv-index-item:focus-visible {
outline: var(--tv-ring-width) solid var(--tv-ring);
outline-offset: var(--tv-ring-offset);
}
/* --------------------------------------------------------------------------
THE RESPONSIVE TRANSFORM
Under 768px the ten-foot dashboard has to become something else, and the
choice of what is argued in the README rather than assumed here. In
short: rows become two-column grids and stack down the page. A single
column would have been simpler and would have killed the feature this
repo exists for, because with one tile per line the left and right arrow
keys stop meaning anything. Two columns keeps all four directions live,
and the spatial navigation in shell.js is written against geometry rather
than against row indexes precisely so that it works here unchanged.
The token overrides that come with this width live in tokens.css, next to
the values they replace.
-------------------------------------------------------------------------- */
@media (max-width: 767px) {
.tv-row-track {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
margin: 0;
padding: var(--tv-space-3) var(--tv-rail);
}
.tv-live .tv-row-track {
overflow-x: visible;
}
.tv-tile {
width: auto;
}
/* A view on a narrow screen is the whole screen, and the reading measure
is already the width of the device. */
.tv-live .tv-view {
padding: var(--tv-space-8) var(--tv-rail) var(--tv-space-12);
}
/* The hint bar's sentence and the clock stop fitting on one line long
before this width. The clock goes; the sentence is the part that
teaches the interface. */
.tv-clock {
display: none;
}
}