1@layer components {
2 /* merge requests: the review page. Hue only where it carries a verdict. */
3 .mr-h .show-kv { margin-top: 0; }
4 /* one card: the header (title, facts) a shade lighter, the description below */
5 .mr-card {
6 background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden;
7 & > .mr-h { background: var(--bg-raised); border: 0; border-radius: 0; padding-bottom: var(--s3); }
8 & > .mr-desc.prose { border: 0; border-top: 1px solid var(--border); border-radius: 0; }
9 }
10
11 .mr-footer {
12 border-top: 1px solid var(--border); padding: var(--s2) var(--s4);
13 /* the shade of the card header; the card's overflow clips the corners */
14 background: var(--bg-raised);
15 font-size: var(--fs-s);
16 /* the tabs are buttons: a thin border, the count in a darker badge;
17 the open one in the teal accent */
18 & nav { display: flex; flex-wrap: wrap; gap: var(--s2); }
19 & [data-mr-tabs] a {
20 display: inline-flex; align-items: center; gap: var(--s1);
21 padding: 2px var(--s2); border: 1px solid var(--border-strong); border-radius: var(--r-s);
22 color: var(--ink-dim); text-decoration: none; cursor: pointer;
23 }
24 & [data-checks-label] { color: var(--ink-dim); font-size: var(--fs-xs); }
25 & [data-mr-tabs] .count { margin-left: 0; color: var(--ink-hi); background: var(--bg-canvas); box-shadow: inset 0 0 0 1px var(--border); }
26 & [data-mr-tabs] a:hover { background: var(--bg-strong); color: var(--ink-hi); }
27 & [data-mr-tabs] a[aria-expanded="true"] { border-color: var(--teal); color: var(--teal); background: var(--bg-surface); }
28 & [data-mr-panel] { padding-block-start: var(--s2); overflow-x: auto; }
29 & [hidden] { display: none; }
30 /* Until mr.js drives the panels (data-panels, then [hidden]), only the
31 panel of the URL fragment shows: the tabs work as anchors without
32 scripts, and no panel flashes open before the script runs. */
33 &:not([data-panels]) [data-mr-panel]:not(:target, :has(:target)) { display: none; }
34 & section h2 { font-size: var(--fs-s); color: var(--ink-dim); }
35 & table { width: 100%; border-collapse: collapse; }
36 & th, & td { text-align: left; padding: var(--s2); border-bottom: 1px solid var(--border); }
37 & th { color: var(--ink-dim); font-weight: 500; }
38 & tbody tr:last-child > :is(td, th) { border-bottom: 0; }
39 }
40 .merge-settings { font-size: var(--fs-s); color: var(--ink-dim); }
41 .merge-settings h2 { font-size: var(--fs-s); }
42
43 .mr-state[data-state="open"]::before { content: "● "; color: var(--teal); }
44 .mr-state[data-state="merged"]::before { content: "✓ "; color: var(--teal); }
45 .mr-state[data-state="closed"]::before { content: "✗ "; color: var(--red); }
46 .mr-state[data-state="draft"]::before { content: "◌ "; color: var(--ink-dim); }
47 /* the description: Markdown as a README, as wide as the header card above;
48 the text inside keeps the reading measure */
49 .mr-desc.prose { max-width: none; padding: var(--s4) var(--s5); background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r); }
50 .mr-desc.prose > * { max-width: var(--measure); }
51 .mr-desc.prose > pre, .mr-desc.prose > table { max-width: 100%; }
52
53 .since {
54 display: inline-flex; flex-wrap: wrap; gap: var(--s1); padding: var(--s1);
55 background: var(--bg-raised); border-radius: var(--r-s);
56 & a { padding: var(--s1) var(--s3); border-radius: 4px; text-decoration: none; color: var(--ink-dim); font-size: var(--fs-s); }
57 & a:hover { color: var(--ink-hi); }
58 & a[aria-current] { color: var(--ink-hi); font-weight: 700; background: var(--bg-surface); box-shadow: 0 0 0 1px var(--border-strong); }
59 & .mute { font-weight: 400; font-family: var(--mono); }
60 }
61
62 /* the bar above the diff of a merge request: the diff tools, the checks and
63 the menus of the owner, on one line that follows the review */
64 /* on a narrow screen the bar takes two lines: the diff tools, then the
65 checks and the menus; the file headers stick under both */
66 @media (max-width: 860px) {
67 body:has(.mr-bar) { --bar-h: 96px; }
68 .mr-bar .grow { flex-basis: 100%; height: 0; }
69 }
70 .mr-bar {
71 gap: var(--s2);
72 & .mr-ck { margin-left: 0; height: 32px; padding: 0 var(--s2); border-radius: var(--r-s); text-decoration: none; color: var(--ink-dim); font-size: var(--fs-s); }
73 & .mr-ck:hover { background: var(--bg-raised); color: var(--ink-hi); }
74 }
75
76 /* a menu of the bar: a <details> whose panel opens under its button */
77 .menu {
78 position: relative;
79 & > summary { list-style: none; }
80 & > summary::-webkit-details-marker { display: none; }
81 & > summary.primary::after { content: ""; width: 0; height: 0; margin-left: 2px; border: 4px solid transparent; border-top-color: currentColor; transform: translateY(2px); }
82 & .menu-pop {
83 position: absolute; right: 0; top: calc(100% + var(--s1)); z-index: 6; width: 320px; max-width: calc(100vw - 2 * var(--pad));
84 display: grid; gap: var(--s3); padding: var(--s3) var(--s4);
85 background: var(--bg-surface); border: 1px solid var(--border-strong); border-radius: var(--r); box-shadow: 0 8px 24px rgb(0 0 0 / .18);
86 font-size: var(--fs-s);
87 }
88 & .kv { margin: 0; }
89 }
90 /* the view menu of a diff: the layout and the fold, one row each */
91 .vmenu {
92 & > summary::after { content: ""; width: 0; height: 0; margin-left: 2px; border: 4px solid transparent; border-top-color: currentColor; transform: translateY(2px); }
93 & .menu-pop { left: 0; right: auto; width: 220px; gap: var(--s1); padding: var(--s2); }
94 & .menu-pop .btn { width: 100%; justify-content: flex-start; border-color: transparent; background: none; }
95 & .menu-pop .btn:hover { background: var(--bg-raised); }
96 & .menu-pop .btn[aria-current="true"] { background: var(--bg-raised); color: var(--ink-hi); }
97 & .menu-pop .btn kbd { margin-left: auto; }
98 }
99 .mmenu {
100 & .menu-pop { box-sizing: border-box; min-width: 0; overflow-wrap: anywhere; }
101 & .menu-pop > *, & form, & .d-modes, & .d-btns { min-width: 0; max-width: 100%; box-sizing: border-box; }
102 & .d-acts { display: grid; gap: var(--s3); }
103 & .d-modes label, & .d-check { min-width: 0; }
104 & code { overflow-wrap: anywhere; }
105 & .d-btns .btn { min-width: 0; max-width: 100%; white-space: normal; }
106 & .d-modes { display: grid; gap: var(--s1); border: 0; padding: 0; margin: 0; }
107 & .d-modes label, & .d-check {
108 display: flex; gap: var(--s2); align-items: center; padding: 4px var(--s2); margin: 0 calc(-1 * var(--s2));
109 border-radius: var(--r-s); color: var(--ink); cursor: pointer;
110 }
111 & .d-modes label:hover, & .d-check:hover { background: var(--bg-raised); }
112 & .d-modes label:has(:checked) { background: var(--bg-raised); color: var(--ink-hi); font-weight: 600; }
113 & .d-modes kbd, & .d-check kbd { margin-left: auto; }
114 & input { accent-color: var(--ink-hi); margin: 0; }
115 & .d-check { color: var(--ink-dim); }
116 & .d-btns { display: flex; margin-top: var(--s1); }
117 & .d-btns .btn { flex: 1; justify-content: center; }
118 & .d-draft { color: var(--ink-dim); }
119 & .d-note { margin: 0; color: var(--ink-dim); }
120 & .d-err { margin: 0; padding: var(--s2) var(--s3); color: var(--ink-hi); border: 1px solid var(--red); border-left-width: 3px; border-radius: var(--r-s); overflow-wrap: anywhere; }
121 /* the badge is a live slot, but not a banner */
122 & #mr-mergeability:not(:empty) { padding: 0; font-weight: normal; background: none; border: 0; display: grid; gap: var(--s1); }
123 & .mg-paths { margin: 0; padding-left: 2ch; max-height: 8em; overflow: auto; }
124 & .mg-paths code { overflow-wrap: anywhere; }
125 & .d-modes label:has(:disabled) { color: var(--ink-mute); cursor: not-allowed; }
126 & .d-modes label:has(:disabled):hover { background: none; }
127 & .btn:disabled { opacity: .5; cursor: not-allowed; }
128 }
129 .badge[data-state] { display: inline-block; padding: 0 var(--s2); border: 1px solid var(--border-strong); border-radius: var(--r-s); font-weight: 600; color: var(--ink-hi); }
130 .badge[data-state="up_to_date"] { border-color: var(--teal); color: var(--teal); }
131 .badge[data-state="behind"] { border-color: var(--teal-2); }
132 .badge[data-state="conflict"] { border-color: var(--red); color: var(--red); }
133 .badge[data-state="checking"] { color: var(--ink-dim); border-style: dashed; }
134 .note[role="status"] { color: var(--ink-dim); }
135 .omenu {
136 & > summary { font-size: 18px; line-height: 1; }
137 & .menu-pop { width: 280px; }
138 & form .btn { width: 100%; justify-content: flex-start; }
139 & .btn kbd { margin-left: auto; }
140 & .d-or > summary { cursor: pointer; list-style: none; color: var(--ink-mute); }
141 & .d-or > summary::-webkit-details-marker { display: none; }
142 & .d-or > summary::before { content: "▸ "; }
143 & .d-or[open] > summary::before { content: "▾ "; }
144 & .d-or > summary:hover { color: var(--ink-hi); }
145 & .d-or[open] > .clone { margin-top: var(--s2); }
146 & .clone code { white-space: pre-wrap; overflow-wrap: anywhere; }
147 & #mr-signing code { overflow-wrap: anywhere; }
148 }
149
150 /* the rail of a merge request follows the review: it sticks under the
151 header, one screen high, and scrolls on its own to its verdicts. The list of files keeps its own scroll, under a height, so
152 that the blocks below it stay in reach. Only on a two-column layout. */
153 @media (min-width: 1081px) {
154 .rail:has(.r-files) {
155 position: sticky; top: calc(var(--hdr-h, 0px) + var(--s4));
156 max-height: calc(100dvh - var(--hdr-h, 0px) - var(--s4) * 2);
157 overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
158 }
159 /* the blocks keep their height: the rail scrolls, it does not squeeze them */
160 .rail:has(.r-files) > * { flex: none; }
161 .rail .r-files > .files { max-height: 55dvh; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
162 }
163 /* on one column the rail goes under the diff: the list of files keeps its
164 own scroll, under a height */
165 @media (max-width: 1080px) {
166 .rail .r-files > .files { max-height: 50dvh; overflow-y: auto; overscroll-behavior: contain; }
167 }
168 .r-files {
169 & > h2 kbd { margin-left: auto; }
170 & > h2 .fold { width: 24px; height: 24px; margin: -2px -6px -2px 0; background: none; border-color: transparent; color: var(--ink-dim); }
171 & > h2 .fold:hover { color: var(--ink-hi); border-color: var(--border-strong); }
172 & > h2 .fold .ico { transition: transform .15s; }
173 &[data-folded] > h2 { border-bottom: 0; }
174 &[data-folded] > h2 .fold .ico { transform: rotate(-90deg); }
175 &[data-folded] > :not(h2) { display: none; }
176 & > .ffilter { margin: var(--s2) var(--s3) 0; }
177 & > .ffilter input {
178 width: 100%; height: 30px; padding: 0 var(--s2); font: inherit; font-size: var(--fs-s); color: inherit;
179 background: var(--bg-inset); border: 1px solid var(--border-strong); border-radius: var(--r-s);
180 }
181 & > .files { margin: var(--s2) 0 !important; padding: 0 var(--s2); }
182 }
183
184 /* a file of the diff whose window is not loaded yet: about its room */
185 .dlazy {
186 display: flex; align-items: center; justify-content: center;
187 background: repeating-linear-gradient(var(--bg-surface) 0 var(--lh-code), var(--paper-alt) var(--lh-code) calc(var(--lh-code) * 2));
188 border: 1px solid var(--border); border-top: 0; border-radius: 0 0 var(--r) var(--r);
189 font-size: var(--fs-s);
190 & a { color: var(--ink-mute); }
191 &.dlazy-1 { height: 120px; } &.dlazy-2 { height: 360px; } &.dlazy-3 { height: 900px; } &.dlazy-4 { height: 1600px; }
192 /* with scripts the window comes by itself: the link of the page without
193 scripts would only flash */
194 html.js &:not([data-manual]) > a { visibility: hidden; }
195 &[data-manual] { height: auto; padding: var(--s3); }
196 &[data-manual][data-loading] > a { opacity: .5; }
197 & .notice { margin: 0; }
198 }
199 .dloose {
200 padding: var(--s2) var(--s3) var(--s3); background: var(--bg-surface);
201 border: 1px solid var(--border); border-top: 0; border-radius: 0 0 var(--r) var(--r);
202 }
203
204 .pcommits { font-size: var(--fs-s); display: grid; gap: var(--s1); }
205 .pcommits li { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 2px var(--s2); margin: 0 calc(-1 * var(--s2)); border-radius: 4px; }
206 .pcommits li a { font-family: var(--mono); }
207 .pcommits li[data-new] { background: var(--bg-raised); }
208 .pcommits .s { color: var(--ink); }
209
210
211 /* a note: a ruled block, under its line in the diff and in the list of comments */
212 .cmt {
213 padding: var(--s3) var(--s4); margin-top: var(--s3); max-width: var(--measure);
214 background: var(--bg-surface); border: 1px solid var(--border); border-left: 3px solid var(--ink-hi); border-radius: var(--r-s);
215 white-space: normal; font-family: var(--font); font-size: var(--fs-s); line-height: 22px;
216 & p { white-space: normal; }
217 & .c-who { font-weight: bold; }
218 & .c-state { color: var(--ink-dim); }
219 & .c-where { font-size: var(--fs-s); overflow-wrap: anywhere; }
220 & .c-act { margin-top: var(--s2); }
221 & .c-act button { height: 26px; font-size: var(--fs-xs); }
222 &[data-resolved="true"] { border-left-color: var(--border-strong); }
223 &[data-resolved="true"] .c-body { color: var(--ink-dim); }
224 }
225 .dt .fnote td { background: var(--bg-canvas); padding: var(--s2) var(--s3); white-space: normal; }
226 .dt .fnote .cmt, .dt .fnote .cform { margin: 0; }
227 .dt .fnote .cmt + .cmt, .dt .fnote .cmt + .cform { margin-top: var(--s2); }
228 .ln a { color: inherit; text-decoration: none; }
229 .ln a:hover, .ln a:focus-visible { text-decoration: underline; color: var(--ink-hi); }
230
231 /* the forms: a comment, a new merge request */
232 .cform {
233 display: grid; gap: var(--s2); margin-top: var(--s3); max-width: var(--measure);
234 font-family: var(--font); font-size: var(--fs-s);
235 & label { color: var(--ink-dim); font-weight: 600; }
236 & textarea, & input[type="text"] {
237 display: block; width: 100%; font: inherit; font-size: var(--fs); color: inherit; resize: vertical;
238 background: var(--bg-surface); border: 1px solid var(--border-strong); border-radius: var(--r-s); padding: var(--s2) var(--s3);
239 }
240 & textarea::placeholder { color: var(--ink-mute); opacity: 1; }
241 & .f-btns { display: flex; gap: var(--s2); align-items: center; margin-top: var(--s1); }
242 }
243
244 /* the public issue form: counters, field errors, the honeypot out of sight */
245 .cform output { justify-self: end; font: var(--fs-s) var(--mono); color: var(--ink-mute); }
246 .cform output.warn { color: var(--ink-hi); font-weight: 700; }
247 .cform output.over { color: var(--red); font-weight: 700; }
248 .cform .f-error { color: var(--red); margin: 0; }
249 .cform .hp { position: absolute; left: -10000px; top: auto; width: 1px; height: 1px; overflow: hidden; }
250
251 /* the inbox: items awaiting moderation, shown as text */
252 .pending-list { display: grid; gap: var(--s3); list-style: none; padding: 0; margin: 0; }
253 .pending { padding: var(--s3) var(--s4); background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r); display: grid; gap: var(--s2); }
254 .pending .p-head { display: flex; gap: var(--s3); flex-wrap: wrap; align-items: baseline; color: var(--ink-dim); font-size: var(--fs-s); }
255 .pending .p-title { margin: 0; font-weight: 700; color: var(--ink-hi); overflow-wrap: anywhere; }
256 .pending .p-body { margin: 0; color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
257 .pending .p-btns { display: flex; gap: var(--s2); flex-wrap: wrap; }
258 .pending .p-btns form { margin: 0; }
259
260 .list.mrs {
261 & .c-n { width: 64px; }
262 & .c-state { width: 110px; }
263 & .c-subject { overflow: hidden; text-overflow: ellipsis; }
264 & .c-who { width: 140px; }
265 & .c-when { width: 100px; }
266 & tr[data-state="open"] .c-state { font-weight: bold; }
267 & tr[data-state="open"] .c-state::before { content: "● "; color: var(--teal); }
268 & tr[data-state="merged"] .c-state::before { content: "✓ "; color: var(--teal); }
269 & tr[data-state="closed"] .c-state::before { content: "✗ "; color: var(--red); }
270 }
271 #mr-notice:empty { display: none; }
272
273 /* the owner's inbox: counters, repository filter, then the two lists */
274 .inbox {
275 display: flex; flex-direction: column; gap: var(--s4);
276 & .inbox-counts { font-size: var(--fs-l, var(--fs)); color: var(--ink-dim); }
277 & .inbox-counts strong { color: var(--ink-hi); font-family: var(--mono); }
278 & .since { align-self: flex-start; max-width: 100%; }
279 }
280
281 @media (max-width: 700px) {
282 .list.mrs .c-who { display: none; }
283 }
284}
285
286@layer components {
287 .c-body { white-space: normal; overflow-wrap: anywhere; }
288 .c-body.prose > * { margin-block: .5em; }
289 .c-body .ref { font-variant-numeric: tabular-nums; text-decoration: underline; }
290 .cmt > .cmt { margin-inline-start: 2ch; }
291}