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
/* Card
Flat surface, no border, radius-lg. `--gl-color-surface-card` is one
neutral step lighter than the page around it — that tonal step, not an
outline, is what marks the edge. `[data-elevated]` is opt-in and reserved
for the one focused/primary card in a view — a dialog, the primary
capture card — flat is the default everywhere else.
A <div>/<article> is too generic to select bare, so Card keeps one
sanctioned class. When Card's job is holding a list of rows — the common
case, entry lists, task lists, settings rows — it is a real <ul class="
card">, and CardRow is a bare <li> underneath it: no second class needed,
the row look comes from the `.card > li` descendant selector alone. The
first row's top border is removed automatically so callers never have to
override it by hand. */
.card {
display: flex;
flex-direction: column;
margin: 0;
padding: 0;
list-style: none;
background: var(--gl-color-surface-card);
border: none;
border-radius: var(--gl-radius-lg);
box-shadow: none;
overflow: hidden;
}
.card[data-elevated] { box-shadow: var(--gl-shadow-float); }
.card > li {
display: flex;
align-items: center;
gap: var(--gl-space-xs);
padding: var(--gl-space-xs) var(--gl-space-s);
border-top: 1px solid var(--gl-color-border-hairline);
color: var(--gl-color-text-primary);
font-family: var(--gl-font-sans);
font-size: var(--gl-step--1);
}
.card > li:first-child { border-top: none; }
/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
accessible way to make a row clickable — fills the row edge-to-edge and
picks up a hover fill and pointer cursor. `:only-child` is what tells this
apart from a row that merely contains a small button among other content
(a leading checkbox next to a title and a trailing value, say): only a
button/anchor that IS the row's one and only child means "the whole row
is the control." */
.card > li:has(> button:only-child),
.card > li:has(> a:only-child) {
padding: 0;
}
.card > li > button:only-child,
.card > li > a:only-child {
all: unset;
display: flex;
align-items: center;
gap: var(--gl-space-xs);
width: 100%;
padding: var(--gl-space-xs) var(--gl-space-s);
box-sizing: border-box;
color: inherit;
font: inherit;
cursor: pointer;
transition: background-color var(--gl-dur-fast) var(--gl-ease);
}
.card > li > button:only-child:hover,
.card > li > a:only-child:hover { background: var(--gl-color-surface-fill); }
/* Documentation-only extras: a body/footer pair and a responsive grid, used
by the prose pages (rationale.md, index.md, refusals.md) to show a Card
as a plain content tile rather than a data row. Neither is part of
Card's props — they exist because a content tile's inner layout has no
element of its own to hang a selector on. .card-body ul is the same idea
applied to a checklist tile (refusals.md) instead of a single paragraph. */
.card-body {
display: flex;
flex-direction: column;
gap: var(--gl-space-2xs);
padding: var(--gl-space-m);
}
.card-body h3 { margin: 0; font: 600 var(--gl-step-0)/1.3 var(--gl-font-sans); }
.card-body p,
.card-body ul { margin: 0; color: var(--gl-color-text-secondary); font-size: var(--gl-step--1); }
.card-body ul { padding-left: 1.125em; }
.card-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--gl-space-xs);
margin-top: auto;
padding: var(--gl-space-xs) var(--gl-space-m);
border-top: 1px solid var(--gl-color-border-hairline);
background: var(--gl-color-surface-fill);
}
.card[data-link] { transition: box-shadow var(--gl-dur-fast) var(--gl-ease); }
.card[data-link]:hover { box-shadow: var(--gl-shadow-sm); }
.card[data-link]:focus-within { outline: 2px solid var(--gl-color-accent); outline-offset: 2px; }
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
gap: var(--gl-space-s);
}
/* Avatar — lives here because it almost never appears outside a card or a
row (the Loop dashboard's initials chip, Archive's file-type chip). A
<span> is too generic for a bare selector, so it keeps one class. */
.avatar {
--_size: 1.875rem;
--gl-icon-start: var(--gl-icon);
/* Sized from the box, not the initials' small font, so it follows data-size. */
--gl-icon-style-size: calc(var(--_size) * 0.55);
--gl-icon-style-spacing: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--_size);
height: var(--_size);
flex: none;
border-radius: var(--gl-radius-full);
background: var(--gl-color-accent-soft);
color: var(--gl-color-accent-ink);
font-family: var(--gl-font-mono);
font-size: var(--gl-step--2);
font-weight: 600;
overflow: hidden;
}
.avatar[data-square] { border-radius: var(--gl-radius-md); background: var(--gl-color-surface-fill); color: var(--gl-color-text-secondary); border: 1px solid var(--gl-color-border-hairline); }
.avatar[data-size="sm"] { --_size: 1.5rem; }
.avatar[data-size="lg"] { --_size: 2.25rem; }