detached 59d5a0b

30-list.css RawHistoryFolder

1@layer components {
2  /* tables in a card, with a row cursor (j/k) */
3  .list {
4    width: 100%; table-layout: fixed;
5    background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r);
6    border-collapse: separate; overflow: hidden;
7    & th {
8      height: 40px; padding: 0 var(--s3); white-space: nowrap;
9      font: 600 var(--fs-xs) / 1 var(--font); text-transform: uppercase; letter-spacing: .05em; color: var(--ink-mute);
10      background: var(--bg-raised); border-bottom: 1px solid var(--border);
11    }
12    & th a { text-decoration: none; }
13    & th a:hover { color: var(--ink-hi); }
14    & th[aria-sort="ascending"], & th[aria-sort="descending"] { color: var(--ink-hi); }
15    & td { height: 40px; padding: 0 var(--s3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-top: 1px solid var(--border); }
16    & tbody tr:first-child td { border-top: 0; }
17    & tbody tr:hover { background: var(--bg-raised); }
18    /* the cursor: the row raised, framed by one even line. The frame is drawn
19       on the cells (a row takes no radius), so it can follow the card's
20       curve when the row sits at a corner. */
21    & tbody tr[data-cursor] > td {
22      background: var(--bg-raised); border-top-color: transparent;
23      box-shadow: inset 0 1px var(--accent), inset 0 -1px var(--accent);
24    }
25    & tbody tr[data-cursor] > td:first-child { box-shadow: inset 1px 0 var(--accent), inset 0 1px var(--accent), inset 0 -1px var(--accent); }
26    & tbody tr[data-cursor] > td:last-child { box-shadow: inset -1px 0 var(--accent), inset 0 1px var(--accent), inset 0 -1px var(--accent); }
27    & > tbody:first-child > tr[data-cursor]:first-child > td:first-child { border-top-left-radius: calc(var(--r) - 1px); }
28    & > tbody:first-child > tr[data-cursor]:first-child > td:last-child { border-top-right-radius: calc(var(--r) - 1px); }
29    & > tbody:last-child > tr[data-cursor]:last-child > td:first-child { border-bottom-left-radius: calc(var(--r) - 1px); }
30    & > tbody:last-child > tr[data-cursor]:last-child > td:last-child { border-bottom-right-radius: calc(var(--r) - 1px); }
31    & tbody tr[hidden] { display: none; }
32    & td > a { text-decoration: none; }
33    & td > a:first-child { color: var(--ink-hi); }
34    & tr:hover td > a:first-child, & tr[data-cursor] td > a:first-child { text-decoration: underline; }
35    & .c-sha, & .c-ref, & .c-kind, & .c-size, & .c-n { font-family: var(--mono); font-size: var(--fs-s); }
36    & .c-sha a { color: var(--ink-dim); }
37    & .c-when, & .c-who { font-size: var(--fs-s); }
38    & .c-sub { color: var(--ink-dim); }
39  }
40
41  /* home: the repositories as rows in a vertical list; the page is a stack of
42     sections, so others can sit beside or below */
43  .home {
44    display: flex; flex-direction: column; gap: var(--s5);
45    & .home-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s3) var(--s4); margin-bottom: var(--s4); }
46    & .home-bar .filter { flex: 1 1 260px; }
47    & .sort { display: flex; align-items: center; gap: var(--s1); font-size: var(--fs-s); }
48    & .sort > .mute { margin-right: var(--s1); }
49    & .sort a {
50      display: inline-flex; align-items: center; gap: 4px; height: 28px; padding: 0 var(--s2);
51      color: var(--ink-dim); text-decoration: none; border: 1px solid transparent; border-radius: var(--r-s);
52    }
53    & .sort a:hover { color: var(--ink-hi); background: var(--bg-raised); }
54    & .sort a[aria-current="true"] { color: var(--ink-hi); background: var(--bg-raised); border-color: var(--border-strong); }
55  }
56  /* a row: name, description and facts on the left; the state of the latest
57     run at the top right, the newest commits under it. Stacked on a phone. */
58  .repos {
59    display: flex; flex-direction: column; gap: var(--s3);
60    margin: 0; padding: 0; list-style: none;
61    & .repo {
62      display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); grid-template-areas: "main head" "main recent";
63      grid-template-rows: auto 1fr; column-gap: var(--s6); row-gap: var(--s3); min-width: 0;
64      padding: var(--s4); background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r);
65    }
66    & .repo:hover { background: var(--bg-raised); border-color: var(--border-strong); }
67    & .repo[data-cursor] { background: var(--bg-raised); border-color: var(--accent); }
68    & .repo[hidden] { display: none; }
69    & .repo-main { grid-area: main; display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }
70    & .repo-name { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s1) var(--s2); font-family: var(--mono); font-size: 17px; line-height: 26px; overflow-wrap: anywhere; }
71    & .repo-name a { color: var(--ink-hi); text-decoration: none; }
72    & .repo:hover .repo-name a, & .repo[data-cursor] .repo-name a { text-decoration: underline; }
73    & .repo-name .pill { margin-left: 0; }
74    /* Every card has the same height: two lines of description, one of topics
75       (reserved when there are none), five of commits. */
76    & .repo-desc {
77      color: var(--ink-dim); min-height: calc(2 * var(--lh));
78      display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
79    }
80    & .repo-desc.mute { color: var(--ink-mute); font-style: italic; }
81    /* topics: small quiet chips, as on GitHub, on one line. The chips that do
82       not fit wrap out of sight; ermite.fit (ermite.js) puts a … chip after
83       the last one shown, the hidden topics in its title. */
84    & .repo-topics {
85      display: flex; flex-wrap: wrap; gap: 6px; height: 22px; overflow: hidden;
86      margin: 0; padding: 0; list-style: none;
87    }
88    & .repo-topics li {
89      flex: none; padding: 1px 9px; border: 1px solid var(--border-strong); border-radius: 999px;
90      font-family: var(--mono); font-size: 12px; line-height: 18px; color: var(--teal); background: var(--bg-raised);
91    }
92    & .repo-topics li[hidden] { display: none; }
93    & .repo-topics li.more { padding: 0; }
94    & .repo-topics li.more button {
95      padding: 1px 7px; border: 0; border-radius: 999px; background: none; cursor: pointer;
96      font: inherit; line-height: 18px; color: var(--ink-mute);
97    }
98    & .repo-topics li.more button:hover { color: var(--accent); }
99    & .repo-topics[data-open] { height: auto; }
100    /* the pulse: the commits per day over 90 days, smoothed, under the text */
101    & .repo-pulse { margin: auto 0 0; padding-top: var(--s2); }
102    /* as tall as a drawn pulse: the padding comes on top of the ratio */
103    & div.repo-pulse { aspect-ratio: 600 / 64; box-sizing: content-box; }
104    & .repo-pulse svg { display: block; width: 100%; height: auto; overflow: visible; }
105    & .pulse-grid { fill: none; stroke: var(--border); stroke-width: 1; stroke-dasharray: 2 4; }
106    & .pulse-stop-top { stop-color: var(--teal); stop-opacity: .32; }
107    & .pulse-stop-bottom { stop-color: var(--teal); stop-opacity: 0; }
108    & .pulse-line { fill: none; stroke: var(--teal); stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
109    & .pulse-dot { fill: var(--teal); stroke: var(--bg-surface); stroke-width: 1.5; }
110    & .repo:hover .pulse-dot, & .repo[data-cursor] .pulse-dot { stroke: var(--bg-raised); }
111    & .repo-pulse:not([data-live]) .pulse-line { stroke: var(--ink-mute); }
112    & .repo-pulse:not([data-live]) .pulse-dot { fill: var(--ink-mute); }
113    & .repo-pulse:not([data-live]) .pulse-stop-top { stop-color: var(--ink-mute); stop-opacity: .18; }
114    /* the right column: figures, then the CI state, over the commits */
115    & .repo-head { grid-area: head; display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s3) var(--s5); min-width: 0; }
116    /* the figures: a small label over a mono value, side by side */
117    & .repo-stats { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); margin: 0; min-width: 0; }
118    & .repo-stats div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
119    & .repo-stats dt { font-size: 11px; line-height: 14px; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-mute); white-space: nowrap; }
120    & .repo-stats dd { margin: 0; font-family: var(--mono); font-size: 15px; line-height: 20px; color: var(--ink-hi); font-variant-numeric: tabular-nums; white-space: nowrap; }
121    & .repo-stats .rs-tag dd { max-width: 18ch; overflow: hidden; text-overflow: ellipsis; }
122    & .repo-stats dd.mute { color: var(--ink-mute); }
123    & .repo-stats dd a { color: inherit; text-decoration: none; }
124    & .repo-stats dd a:hover { text-decoration: underline; }
125    & .repo-ci {
126      flex: none; display: inline-flex; align-items: center; gap: var(--s1); padding-top: 2px;
127      font-size: var(--fs-s); color: var(--ink-dim); text-decoration: none; white-space: nowrap;
128    }
129    & .repo-ci:hover { color: var(--ink-hi); text-decoration: underline; }
130    & .repo-ci .ck { margin: 0; }
131    & .repo-commits {
132      grid-area: recent; min-width: 0; margin: 0; padding: 0; list-style: none; font-size: var(--fs-s);
133      min-height: calc(5 * (var(--lh) + 4px)); /* five rows, full or not */
134    }
135    & p.repo-commits { font-style: italic; }
136    & .repo-commits li { display: flex; align-items: baseline; gap: var(--s2); min-width: 0; padding: 2px 0; }
137    & .rc-sha { flex: none; font-family: var(--mono); color: var(--ink-dim); text-decoration: none; }
138    & .rc-sha:hover { color: var(--ink-hi); text-decoration: underline; }
139    & .rc-subject { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-hi); }
140    & .repo-commits time { flex: none; white-space: nowrap; }
141    /* a full list goes on: its last commit fades */
142    & .repo-commits[data-full] li:last-child { opacity: .65; }
143    @media (max-width: 720px) {
144      & .repo { grid-template-columns: minmax(0, 1fr); grid-template-areas: "main" "head" "recent"; grid-template-rows: none; }
145      & .repo-head { flex-wrap: wrap; }
146      & .repo-commits { min-height: 0; }
147    }
148  }
149  /* Home layouts. The server renders "cards" (the rules above); a script
150     sets .home[data-layout] from the stored choice. The rows carry no markup
151     of their own for a layout, so a row swapped in by a live update fits. */
152  .home .layout-switch { display: flex; align-items: center; gap: 2px; }
153  .home .layout-switch .btn {
154    width: 28px; height: 28px; color: var(--ink-dim); background: none; border-color: transparent; border-radius: var(--r-s);
155    &:hover { color: var(--ink-hi); background: var(--bg-raised); }
156    &[aria-pressed="true"] { color: var(--ink-hi); background: var(--bg-raised); border-color: var(--border-strong); }
157  }
158  .home .home-bar .sort { margin-left: auto; }
159
160  /* split: two denser cards side by side. Name and CI state on top, the
161     description, the pulse as a band from edge to edge, the figures in one
162     row and the two newest commits. One column on a narrow screen. */
163  .home[data-layout="split"] {
164    & .repos { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s4); }
165    & .repo {
166      grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--s3); row-gap: 0;
167      grid-template-areas: "name ci" "desc desc" "topics topics" "pulse pulse" "stats stats" "recent recent";
168      grid-template-rows: auto 1fr auto auto auto auto;
169      overflow: hidden;
170    }
171    & .repo-main, & .repo-head { display: contents; }
172    & .repo-name { grid-area: name; align-self: center; font-size: 16px; }
173    & .repo-ci { grid-area: ci; align-self: center; padding-top: 0; }
174    & .repo-desc { grid-area: desc; margin-top: var(--s2); }
175    & .repo-topics { grid-area: topics; margin-top: var(--s2); }
176    & .repo-topics:empty { display: none; }
177    & .repo-pulse { grid-area: pulse; margin: var(--s3) calc(-1 * var(--s4)) 0; padding: 0; border-block: 1px solid var(--border); }
178    /* one row while it fits; a narrow card wraps the figures instead of
179       letting them overlap */
180    & .repo-stats { grid-area: stats; flex-wrap: wrap; justify-content: space-between; gap: var(--s2) var(--s3); margin-top: var(--s3); }
181    & .repo-stats div { flex: none; }
182    & .repo-stats dd { font-size: var(--fs); }
183    & .repo-stats .rs-tag dd { max-width: 12ch; }
184    & .repo-commits { grid-area: recent; min-height: calc(2 * (var(--lh) + 4px)); margin-top: var(--s2); }
185    & .repo-commits li:nth-child(n+3) { display: none; }
186    & .repo-commits[data-full] li:last-child { opacity: 1; }
187    & .repo-commits[data-full] li:nth-child(2) { opacity: .65; }
188  }
189  @media (max-width: 900px) {
190    .home[data-layout="split"] .repos { grid-template-columns: minmax(0, 1fr); }
191  }
192
193  /* compact: a line per repository. Name, description, figures, CI mark and
194     a small pulse sit in one grid row; the commits and topics are left out.
195     The rows share the columns of the list (subgrid): the name column is as
196     wide as the longest name, the figures line up, and the description takes
197     what is left. */
198  .home[data-layout="compact"] {
199    & .repos {
200      display: grid; grid-template-columns: fit-content(18rem) minmax(0, 1fr) auto auto 140px;
201      gap: var(--s2) var(--s4);
202    }
203    & .repo {
204      grid-column: 1 / -1; grid-template-columns: subgrid;
205      grid-template-areas: "name desc stats ci pulse";
206      /* normal: the row takes the gaps of the list */
207      grid-template-rows: none; column-gap: normal; row-gap: 0; align-items: center;
208      padding: var(--s2) var(--s4);
209    }
210    & .repo-main, & .repo-head { display: contents; }
211    & .repo-topics, & .repo-commits { display: none; }
212    /* min-width 0: a long name is cut, it does not widen the column past its cap */
213    & .repo-name { grid-area: name; min-width: 0; flex-wrap: nowrap; gap: var(--s2); font-size: var(--fs); line-height: 24px; overflow-wrap: normal; }
214    & .repo-name a { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
215    & .repo-name .pill { flex: none; }
216    & .repo-desc {
217      grid-area: desc; display: block; min-height: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
218      font-size: var(--fs-s); color: var(--ink-mute);
219    }
220    /* figures inline, the value before its label; the label of the date and
221       of the tag is left to screen readers */
222    & .repo-stats { grid-area: stats; flex-wrap: nowrap; gap: var(--s3); overflow: hidden; }
223    & .repo-stats div { flex-direction: row; align-items: baseline; gap: 4px; flex: none; }
224    & .repo-stats dt { order: 1; font-size: var(--fs-xs); line-height: 16px; letter-spacing: 0; text-transform: none; }
225    & .repo-stats dd { font-size: var(--fs-xs); line-height: 16px; color: var(--ink-dim); }
226    & .repo-stats .rs-active dt, & .repo-stats .rs-tag dt {
227      position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%);
228    }
229    & .repo-stats .rs-tag dd { max-width: 10ch; }
230    & .repo-stats .rs-tag dd::before {
231      content: ""; display: inline-block; width: 12px; height: 12px; margin-right: 3px; vertical-align: -2px;
232      background: currentColor; mask: url(icons/tag.svg) center / contain no-repeat;
233    }
234    /* the CI state is its mark; the word is in the tooltip and for screen readers */
235    & .repo-ci { grid-area: ci; justify-self: center; padding-top: 0; }
236    & .repo-ci .ci-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
237    /* the pulse: the same curve, small, with a bigger dot to stay seen */
238    & .repo-pulse { grid-area: pulse; width: 100%; height: 100%; min-height: 18px; margin: 0; padding: 0; }
239    & div.repo-pulse { aspect-ratio: auto; box-sizing: border-box; }
240    & .repo-pulse svg { position: relative; top: 50%; transform: translateY(-50%); }
241    & .pulse-grid { display: none; }
242    & .pulse-dot { r: 9px; stroke: none; }
243  }
244  @media (max-width: 1100px) {
245    .home[data-layout="compact"] .repos { grid-template-columns: fit-content(14rem) minmax(0, 1fr) auto auto 96px; column-gap: var(--s3); }
246    .home[data-layout="compact"] .repo-stats .rs-tag { display: none; }
247  }
248  /* on a phone: name and CI state on a line, the description and the figures
249     under it; the pulse is dropped */
250  @media (max-width: 720px) {
251    .home[data-layout="compact"] .repo {
252      grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "name ci" "desc desc" "stats stats"; column-gap: var(--s3); row-gap: 2px;
253    }
254    .home[data-layout="compact"] .repo-pulse { display: none; }
255    .home[data-layout="compact"] .repo-stats .rs-tag { display: flex; }
256  }
257  .list.passkeys {
258    & .c-name { width: auto; font-weight: 600; }
259    & .c-when { width: 120px; font-size: var(--fs-s); }
260    & .c-sync { width: 80px; font-size: var(--fs-s); }
261    & .c-act { width: 110px; text-align: right; }
262  }
263  .passkey-add {
264    display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; margin-top: var(--s4);
265    & input {
266      flex: 1; min-width: 12ch; max-width: 320px; height: 32px; padding: 0 var(--s3);
267      background: var(--bg-surface); border: 1px solid var(--border-strong); border-radius: var(--r-s);
268    }
269    & input:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; border-color: transparent; }
270    & input::placeholder { color: var(--ink-mute); opacity: 1; }
271  }
272  .list.commits, .list.refs {
273    & .c-sha { width: 96px; }
274    & .c-who { width: 140px; }
275    & .c-when { width: 100px; }
276  }
277
278  /* filter: a search field above a list */
279  .filter {
280    position: relative; display: flex; max-width: 420px;
281    &::before {
282      content: ""; position: absolute; left: 11px; top: 10px; width: 16px; height: 16px; pointer-events: none;
283      background: var(--ink-mute); mask: url(icons/search.svg) center / contain no-repeat;
284    }
285    &::after {
286      content: "/"; position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
287      font: 600 var(--fs-xs) / 18px var(--mono); color: var(--ink-mute); padding: 0 6px;
288      border: 1px solid var(--border-strong); border-bottom-width: 2px; border-radius: 4px; pointer-events: none;
289    }
290    & input {
291      flex: 1; min-width: 0; height: 36px; padding: 0 36px 0 34px;
292      background: var(--bg-surface); border: 1px solid var(--border-strong); border-radius: var(--r-s);
293    }
294    & input:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; border-color: transparent; }
295    & input::placeholder { color: var(--ink-mute); opacity: 1; }
296    &:focus-within::after { display: none; }
297  }
298
299  /* file finder: the results replace the tree while the field has a query */
300  #finder {
301    & #finder-status { padding: var(--s3) var(--s4) var(--s2); font-size: var(--fs-s); }
302    & #finder-list { padding: 0 var(--s2) var(--s2); }
303  }
304
305  /* the Files view: the finder on top, the tree in a card under it */
306  .tree-find {
307    display: flex; align-items: center; gap: var(--s4); margin-bottom: var(--s3);
308    & .filter { flex: 1; max-width: none; }
309    & .filter::before { top: 14px; left: 14px; }
310    & .filter::after { right: 12px; }
311    & .filter input { height: 44px; padding-left: 42px; font-size: var(--fs); background: var(--bg-surface); }
312  }
313  .tree-keys { flex: none; color: var(--ink-mute); font-size: var(--fs-xs); white-space: nowrap; }
314  .tree-card {
315    background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden;
316  }
317  .tree-head {
318    display: flex; align-items: center; gap: var(--s3); min-height: 40px; padding: 0 var(--s4);
319    background: var(--bg-raised); border-bottom: 1px solid var(--border); font-size: var(--fs-s);
320    & .tree-path { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 700 var(--fs-s) / 1 var(--mono); color: var(--ink-hi); }
321    & .tree-up { flex: none; font: 600 var(--fs-s) / 1 var(--mono); color: var(--ink-dim); text-decoration: none; }
322    & .tree-up:hover { color: var(--ink-hi); text-decoration: underline; }
323    & .tree-count { flex: none; color: var(--ink-mute); }
324  }
325  .tree-card > :is(.lede, .note) { margin: var(--s3) var(--s4); }
326
327  /* The tree: one flat list, a row per entry, each as deep as --depth says
328     (files.js sets it on the rows it opens in place). A row draws, from the
329     left: a vertical guide per level above it, the branch tick to its node,
330     the node, its name; then size, last commit and date, which are secondary. */
331  ul.tree {
332    --indent: 22px; --rowh: 32px; --guide: var(--border-strong);
333    & > li {
334      display: grid; grid-template-columns: minmax(0, 1fr) 72px minmax(0, 1.2fr) 96px; align-items: center;
335      min-height: var(--rowh); padding-right: var(--s4); font-size: var(--fs-s);
336    }
337    & > li:hover, & > li[data-cursor] { background: var(--bg-raised); }
338    & > li[data-cursor] { outline: 1px solid var(--accent); outline-offset: -1px; }
339    & > li:focus-visible { outline-offset: -2px; border-radius: 0; }
340    & .t-main { display: flex; align-items: stretch; min-width: 0; min-height: var(--rowh); padding-left: var(--s3); }
341    & .t-twig {
342      flex: none; width: calc(var(--depth, 0) * var(--indent));
343      background: repeating-linear-gradient(90deg,
344        transparent 0 calc(var(--indent) / 2), var(--guide) calc(var(--indent) / 2) calc(var(--indent) / 2 + 1px),
345        transparent calc(var(--indent) / 2 + 1px) var(--indent));
346    }
347    & .t-node { position: relative; flex: none; width: var(--indent); }
348    & .t-node::before, & .t-node::after { content: ""; position: absolute; }
349    /* the branch tick, from the guide of the directory to this node */
350    & .t-node::before { top: 50%; left: calc(var(--indent) / -2); right: calc(50% + 6px); height: 1px; background: var(--guide); }
351    & > li[data-depth="0"] .t-node::before { display: none; }
352    /* an open directory continues its guide down, to its first entry */
353    & > li[aria-expanded="true"] .t-node {
354      background: linear-gradient(var(--guide), var(--guide)) center bottom / 1px calc(50% - 8px) no-repeat;
355    }
356    /* the node: a ring for a file, a chevron for a directory (it turns down
357       once open), a diamond for a link, a square for a submodule */
358    & .t-node::after {
359      top: 50%; left: 50%; width: 7px; height: 7px; margin: -4px 0 0 -4px;
360      background: var(--bg-surface); border: 1px solid var(--ink-mute); border-radius: 50%;
361    }
362    & > li:hover .t-node::after, & > li[data-cursor] .t-node::after { background: var(--bg-raised); border-color: var(--ink-hi); }
363    & > li[data-kind="dir"] .t-node::after {
364      width: 16px; height: 16px; margin: -8px 0 0 -8px; border: 0; border-radius: 0; background: var(--ink-dim);
365      mask: url(icons/chevron.svg) center / contain no-repeat; transform: rotate(-90deg);
366    }
367    & > li[data-kind="dir"][aria-expanded="true"] .t-node::after { transform: none; background: var(--accent); }
368    & > li[data-kind="link"] .t-node::after { border-radius: 1px; transform: rotate(45deg); }
369    & > li[data-kind="module"] .t-node::after { border-radius: 1px; }
370    & > li[aria-busy="true"] .t-node { opacity: .4; }
371    & .t-name {
372      align-self: center; min-width: 0; padding: 4px var(--s1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
373      font-family: var(--mono); font-size: var(--fs); color: var(--ink); text-decoration: none; cursor: pointer;
374    }
375    & > li[data-kind="dir"] .t-name { font-weight: 700; color: var(--ink-hi); }
376    & > li[data-kind="link"] .t-name, & > li[data-kind="module"] .t-name { color: var(--ink-dim); cursor: default; }
377    & > li:hover .t-name, & > li[data-cursor] .t-name { text-decoration: underline; }
378    & .t-size { padding-right: var(--s4); text-align: right; font: var(--fs-xs) / 1 var(--mono); color: var(--ink-mute); font-variant-numeric: tabular-nums; }
379    & .t-sub { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-dim); }
380    & .t-sub a { color: inherit; text-decoration: none; }
381    & .t-sub a:hover { color: var(--ink-hi); text-decoration: underline; }
382    & .t-when { text-align: right; white-space: nowrap; color: var(--ink-mute); font-size: var(--fs-xs); }
383    & > li.tree-more { grid-template-columns: minmax(0, 1fr); }
384    & > li.tree-more .t-name { font-size: var(--fs-s); font-weight: 400; color: var(--ink-dim); text-decoration: underline; }
385    & > li.tree-more .t-node::after { display: none; }
386  }
387  @media (max-width: 900px) {
388    ul.tree > li { grid-template-columns: minmax(0, 1fr) 64px 80px; }
389    ul.tree .t-sub { display: none; }
390  }
391  @media (max-width: 720px) {
392    .tree-keys { display: none; }
393    ul.tree { --indent: 18px; }
394    ul.tree > li { grid-template-columns: minmax(0, 1fr) 72px; }
395    ul.tree .t-size { display: none; }
396  }
397
398  /* section headings inside a page */
399  .rule-h {
400    display: flex; align-items: center; gap: var(--s2); margin-top: var(--s5);
401    font-size: var(--fs);
402    & > :first-child { color: var(--ink-hi); }
403  }
404  main > .rule-h:first-child { margin-top: 0; }
405
406  /* empty / 404 / error / sign in: one card in the middle */
407  .state {
408    max-width: 560px; margin: var(--s7) auto 0 !important; padding: var(--s6);
409    background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r);
410    & .eyebrow { font: 700 var(--fs-s) / 1 var(--mono); color: var(--ink-mute); text-transform: uppercase; letter-spacing: .08em; margin-bottom: var(--s2); }
411    & h1 { margin-bottom: var(--s3); }
412    & p.dim { color: var(--ink-dim); }
413    & .state-act { margin-top: var(--s5); }
414    & .state-back { margin-top: var(--s5); display: flex; gap: var(--s4); font-size: var(--fs-s); }
415    & .clone { margin-top: var(--s4); }
416  }
417
418  /* copyable shell lines */
419  .clone {
420    display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s2); min-width: 0;
421    & .clone-line {
422      min-width: 0;
423      display: flex; gap: var(--s2); align-items: center; padding: var(--s1) var(--s1) var(--s1) var(--s3);
424      background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--r-s);
425      font-size: var(--fs-code);
426    }
427    & .clone-line > .mute { user-select: none; font-family: var(--mono); }
428    & code { flex: 1; min-width: 0; overflow-x: auto; white-space: pre; scrollbar-width: none; }
429    & .copy { height: 26px; padding: 0 var(--s2); font-size: var(--fs-xs); }
430  }
431}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close