返回 CodeWhale
shell.css
根目录 / web / app / styles / shell.css
1 /* ------------------------------------------------------------------ */
2 /* Site chrome — the header, the mobile sheet, the footer sea */
3 /* ------------------------------------------------------------------ */
4
5 /* ---------- header ---------- */
6 /* The header floats on the sky: it paints the same ground as the top of the
7 page, pinned to the viewport, so at rest there is no bar at all. No glass,
8 no blur. A hairline marks it once content scrolls beneath. */
9 .site-nav {
10 position: sticky;
11 z-index: 30;
12 top: 0;
13 background: var(--ground), var(--bg);
14 background-attachment: fixed;
15 background-repeat: no-repeat;
16 border-bottom: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
17 }
18
19 .site-nav-inner {
20 display: flex;
21 width: var(--container);
22 min-height: 4rem;
23 align-items: center;
24 justify-content: space-between;
25 gap: 1.5rem;
26 margin-inline: auto;
27 }
28
29 .paper-nav-inner { padding-block: var(--space-2); }
30
31 .site-wordmark {
32 display: inline-flex;
33 flex-shrink: 0;
34 align-items: center;
35 gap: var(--space-2);
36 color: var(--mark-ink);
37 border-radius: var(--radius-control);
38 }
39
40 .paper-wordmark {
41 flex: 0 1 auto;
42 max-width: 15rem;
43 min-width: 8.75rem;
44 overflow: hidden;
45 }
46
47 .paper-wordmark-text {
48 display: flex;
49 align-items: center;
50 gap: 0.5rem;
51 min-width: 0;
52 }
53
54 /* The mark is the logo gradient; the wordmark is one ink (--mark-ink),
55 drawn through a mask so both appearances re-ink it without a filter or a
56 second file. */
57 .paper-wordmark-mark {
58 display: block;
59 flex: 0 0 auto;
60 width: 34px;
61 height: 34px;
62 margin-inline: -3px;
63 }
64
65 .paper-wordmark-logo,
66 .wordmark {
67 display: block;
68 flex: 0 1 auto;
69 min-width: 0;
70 width: 128px;
71 max-width: 100%;
72 height: 20px;
73 background: var(--mark-ink);
74 -webkit-mask: url("/brand/wordmark.svg") no-repeat left center / contain;
75 mask: url("/brand/wordmark.svg") no-repeat left center / contain;
76 }
77 [dir="rtl"] .paper-wordmark-logo,
78 [dir="rtl"] .wordmark {
79 -webkit-mask-position: right center;
80 mask-position: right center;
81 }
82
83 @media (forced-colors: active) {
84 .paper-wordmark-logo,
85 .wordmark { background: CanvasText; }
86 }
87
88 /* The one primary action in the header, and the one identity door. */
89 .paper-install-cta {
90 align-items: center;
91 justify-content: center;
92 min-height: 2.25rem;
93 padding-inline: var(--space-4);
94 background: var(--brand-fill);
95 color: var(--on-brand);
96 font-size: var(--text-ui);
97 font-weight: 600;
98 border-radius: var(--radius-control);
99 transition: box-shadow var(--dur-state) var(--ease-out);
100 }
101 .paper-install-cta:hover {
102 background: var(--brand-fill-hover);
103 color: var(--on-brand);
104 box-shadow: var(--glow-focus);
105 }
106
107 .paper-auth-signin {
108 align-items: center;
109 justify-content: center;
110 min-height: 2.25rem;
111 padding-inline: var(--space-3);
112 color: var(--text);
113 font-size: var(--text-ui);
114 font-weight: 500;
115 border-radius: var(--radius-control);
116 transition: background-color var(--dur-state) var(--ease-out);
117 }
118 .paper-auth-signin:hover { background: var(--hover); }
119
120 /* Actions sit 2px apart, as related rail controls in the GPUI client; the
121 text door and primary action take a group gap before them. */
122 .site-nav-actions {
123 display: flex;
124 flex-shrink: 0;
125 align-items: center;
126 gap: 2px;
127 }
128 .site-nav-actions > * { flex-shrink: 0; }
129 .site-nav-actions > .paper-auth-signin { margin-inline-start: 0.5rem; }
130 .site-nav-actions > .paper-install-cta { margin-inline-start: 0.25rem; }
131
132 /* Quiet icon controls: a 40px target around a 20px glyph. Muted at rest,
133 full ink on hover and while expanded; a pressed or current control takes
134 the neutral selected fill. */
135 .nav-icon-button {
136 position: relative;
137 display: inline-flex;
138 width: 2.5rem;
139 height: 2.5rem;
140 align-items: center;
141 justify-content: center;
142 color: var(--muted);
143 border-radius: var(--radius-control);
144 cursor: pointer;
145 transition-property: background-color, color, box-shadow;
146 transition-duration: var(--dur-state);
147 transition-timing-function: var(--ease-out);
148 }
149 .nav-icon-button:hover,
150 .nav-icon-button[aria-expanded="true"] {
151 background: var(--hover);
152 color: var(--text);
153 }
154 .nav-icon-button[aria-pressed="true"],
155 .nav-icon-button[aria-current="page"] {
156 background: var(--selected);
157 color: var(--text);
158 }
159 .nav-icon-button:active { opacity: 0.75; }
160 .nav-icon-button:focus-visible { box-shadow: var(--glow-focus); }
161
162 .nav-icon-button-sm { width: 2rem; height: 2rem; }
163 .nav-icon-button-sm .nav-icon { width: 1rem; height: 1rem; }
164 @media (pointer: coarse) {
165 .nav-icon-button-sm { width: 2.75rem; height: 2.75rem; }
166 }
167
168 .nav-icon { display: block; width: 1.25rem; height: 1.25rem; }
169
170 /* The native <select> stays the control (keyboard, screen readers, the
171 platform picker) but is transparent over the globe glyph, so the icon is
172 the whole hit target. Its focus ring moves to the visible box. */
173 .nav-locale select {
174 position: absolute;
175 inset: 0;
176 width: 100%;
177 height: 100%;
178 opacity: 0;
179 cursor: pointer;
180 font-size: 1rem;
181 }
182 .nav-locale:has(select:focus-visible) {
183 box-shadow: 0 0 0 var(--gpui-focus-width) var(--ring), var(--glow-focus);
184 }
185
186 /* ---------- primary nav links ---------- */
187 /* Hover is a neutral wash. The current page keeps a 2px accent bar under
188 its label: the one place blue marks "here". */
189 .nav-link {
190 position: relative;
191 padding: 0.375rem 0.5rem;
192 margin-inline: -0.5rem;
193 color: var(--text);
194 font-size: var(--text-ui);
195 font-weight: 500;
196 border-radius: var(--radius-control);
197 transition: background-color var(--dur-state) var(--ease-out);
198 }
199 .nav-link:hover { background: var(--hover); }
200 .nav-link::after {
201 content: "";
202 position: absolute;
203 inset-inline: 0.5rem;
204 bottom: -2px;
205 height: 2px;
206 background: var(--accent);
207 box-shadow: var(--glow);
208 opacity: 0;
209 }
210 .nav-link[aria-current="page"]::after { opacity: 1; }
211
212 /* Compact viewports: the strip keeps the home control clickable. */
213 @media (max-width: 520px) {
214 .site-nav-inner { gap: 0.5rem; }
215 .site-nav-actions { min-width: 0; }
216 .paper-wordmark { max-width: 9.75rem; }
217 }
218
219 /* ---------- the mobile sheet ---------- */
220 .mm-list > li + li { border-top: 1px solid var(--line); }
221
222 .mm-link {
223 display: flex;
224 align-items: center;
225 gap: 0.75rem;
226 min-height: 3.25rem;
227 padding-inline: 0.5rem;
228 color: var(--text);
229 font-size: 1.0625rem;
230 border-radius: var(--radius-control);
231 transition: background-color var(--dur-state) var(--ease-out);
232 }
233 .mm-link:hover { background: var(--hover); }
234 .mm-link[aria-current="page"] { background: var(--selected); box-shadow: inset 3px 0 0 var(--accent); font-weight: 600; }
235 @media (forced-colors: active) {
236 .mm-link[aria-current="page"] { outline-style: solid; outline-width: 2px; outline-color: Highlight; outline-offset: -2px; }
237 }
238 .mm-link-chevron { margin-inline-start: auto; color: var(--muted); }
239
240 .mm-action {
241 display: flex;
242 width: 100%;
243 min-height: 2.75rem;
244 margin-top: 0.75rem;
245 }
246 .mm-action.paper-install-cta { margin-top: 1.5rem; }
247
248 /* ---------- footer — the sea below the horizon ---------- */
249 /* Every page ends on one horizon: a straight line of light, and below it
250 the sea, settling into deep water within a few rows, in both appearances. */
251 .site-footer {
252 position: relative;
253 color: var(--muted);
254 }
255 /* A page whose main already ends in the sea (home) runs straight on: no
256 second horizon, and the water continues from its floor colour. */
257 main:has(.sea-continues:last-child) + .site-footer .site-footer-sea > .horizon { display: none; }
258 main:has(.sea-continues:last-child) + .site-footer .site-footer-sea { background: var(--ocean-floor); }
259 .site-footer-sea {
260 position: relative;
261 overflow: hidden;
262 background: var(--sea), var(--ocean-floor);
263 }
264 .site-footer-sea > .horizon { position: absolute; inset: 0 0 auto; z-index: 1; }
265
266 .site-footer-main,
267 .site-footer-meta {
268 position: relative;
269 width: var(--container);
270 margin-inline: auto;
271 }
272
273 .site-footer-main {
274 display: grid;
275 grid-template-columns: minmax(14rem, 1fr) minmax(0, 2fr);
276 gap: var(--space-7);
277 padding-block: 5.5rem var(--space-7);
278 }
279
280 .site-footer-brand { display: grid; gap: var(--space-4); align-content: start; }
281 .site-footer-brand p {
282 max-width: 26rem;
283 color: var(--muted);
284 font-size: var(--text-ui);
285 line-height: 1.6;
286 }
287 .site-footer-brand .whale-pose { --pose-size: 3rem; }
288 .site-wordmark-footer { color: var(--text); justify-self: start; gap: var(--space-3); }
289
290 .site-footer-links {
291 display: grid;
292 grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
293 gap: var(--space-5);
294 }
295
296 .site-footer-group { display: grid; gap: var(--space-2); align-content: start; }
297
298 .site-footer-label {
299 color: var(--muted);
300 font-size: var(--text-caption);
301 font-weight: 500;
302 }
303
304 .site-footer-list { display: grid; gap: 2px; }
305 .site-footer-list a {
306 display: inline-flex;
307 align-items: center;
308 justify-self: start;
309 min-height: 1.75rem;
310 padding-inline: var(--space-2);
311 margin-inline: calc(-1 * var(--space-2));
312 color: var(--text);
313 font-size: var(--text-ui);
314 border-radius: var(--radius-control);
315 transition: background-color var(--dur-state) var(--ease-out);
316 }
317 .site-footer-list a:hover { background: var(--hover); }
318
319 .site-footer-meta {
320 display: flex;
321 flex-wrap: wrap;
322 align-items: center;
323 justify-content: space-between;
324 gap: var(--space-3) var(--space-5);
325 padding-block: var(--space-4) var(--space-6);
326 border-top: 1px solid var(--line);
327 color: var(--muted);
328 font-size: var(--text-caption);
329 }
330 .site-footer-meta p { min-width: 0; max-width: 70ch; overflow-wrap: anywhere; }
331 .site-footer-meta a {
332 color: var(--text);
333 text-decoration: underline;
334 text-decoration-color: var(--line-strong);
335 text-underline-offset: 0.2em;
336 }
337 .site-footer-meta a:hover { text-decoration-color: currentColor; }
338 .site-footer-meta-links { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
339
340 @media (max-width: 900px) {
341 .site-footer-main { grid-template-columns: 1fr; gap: var(--space-6); }
342 }
343
344 @media print {
345 .site-footer { color: inherit; border-top: 1px solid currentColor; }
346 .site-footer-sea { background: none; }
347 .site-footer > .waterline,
348 .site-footer-sea > .horizon { display: none; }
349 }
350
351 /* ---------- usage counting: the privacy page's control ---------- */
352 .usage-counting { display: grid; gap: var(--space-3); margin-top: var(--space-2); }
353 .usage-counting-status { color: var(--text); font-weight: 500; }
354 .usage-counting-elsewhere { color: var(--muted); font-size: var(--text-ui); }
355 .usage-counting-button {
356 display: inline-flex;
357 justify-self: start;
358 align-items: center;
359 min-height: 2.5rem;
360 padding-inline: var(--space-4);
361 color: var(--text);
362 background: var(--panel);
363 font-size: var(--text-ui);
364 font-weight: 500;
365 border: 1px solid var(--line-strong);
366 border-radius: var(--radius-control);
367 cursor: pointer;
368 transition: background-color var(--dur-state) var(--ease-out);
369 }
370 .usage-counting-button:hover { background: var(--hover); }
371
371 lines CSS