]> git.draconx.ca Git - homepage.git/blob - content/style.scss
2708bb462e00c701f9b563fe9dec8ac94f67d9e5
[homepage.git] / content / style.scss
1 /*!
2  * Nick's web site: default stylesheet
3  *
4  * Copyright © 2018-2022 Nick Bowler
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19
20 @import "lib/colourmap.scss";
21
22 @include defcolours
23     ( $background:  #ffffff #000000
24     , $foreground:  #000000 #ffffff
25
26     , $linkdefault: #0000cd #a3aaff
27     , $linkactive:  #ff0000
28     , $linkvisited: #800080 #e493f7
29     , $focusring:   #628cb2
30
31     , $annotation:  #708090
32     , $tableshade:  #f5f5f5 #101010
33
34     , $ruledefault: #d3d3d3 #494949
35     , $rulestrong:  #696969 #939393
36     );
37
38 @mixin header_size($maxwidth, $fontsize) {
39     font-size: $fontsize;
40     max-width: 1em * ($maxwidth / $fontsize);
41 }
42
43 html { @include usecolours($background-color: background); }
44 body {
45     @include usecolours($color: foreground);
46     font-family: sans-serif;
47     margin: 1em;
48 }
49
50 a:link {
51     @include usecolours($color: linkdefault, $border-color: linkdefault);
52 }
53 a:visited {
54     @include usecolours($color: linkvisited, $border-color: linkvisited);
55 }
56 a:active {
57     @include usecolours($color: linkactive, $border-color: linkactive);
58 }
59
60 @supports (outline-style: auto) {
61     a:link { border-width: 0; }
62     a:focus { @include usecolour(outline, focusring, auto); }
63     li, td, dt { &>a:link { border: solid 1px transparent; } }
64 }
65
66 h1 { @include header_size(60em, 2em); }
67 h2 { @include header_size(60em, 1.5em); }
68 h5 { @include header_size(60em, 1em); }
69
70 @supports (display: grid) {
71     .gallery {
72         display: grid;
73         grid-column-gap: 1em;
74         grid-template-columns: repeat( auto-fill, minmax(18em, 1fr) );
75         align-items: center;
76
77         p.img { margin: 0.5em 0; }
78     }
79 }
80
81 p.img {
82     text-align: center;
83
84     img {
85         vertical-align: bottom;
86         max-width: 40em;
87         width: 100%;
88         height: auto;
89     }
90
91     a {
92         text-decoration: none;
93         display: inline-block;
94         border: solid 2px;
95     }
96
97     small {
98         @include usecolours($color: foreground);
99         text-align: justify;
100         @media (max-width: 24em) { text-align: left; }
101         padding: 0.5ex;
102         display: block;
103         font-size: 0.9em;
104     }
105 }
106
107 p, dt, dd, li {
108     text-align: justify;
109     @media (max-width: 28em) { text-align: left; }
110     padding: 0;
111     margin: 0;
112 }
113
114 p, table, div, ul, ol, dl, hr {
115     max-width: 50em;
116     padding: 0;
117     margin: 0;
118 }
119
120 p, table, body>div, h5 { margin: 1em 0; }
121 blockquote {
122     @media (max-width: 28em) { margin: 1em 0.5em; }
123     margin: 1em;
124 }
125
126 li { margin: 0 0 0 2em; }
127 dd { margin: 0 0 0 1em; }
128
129 hr {
130     clear: both;
131     margin: 0.5em 0;
132     border: 0;
133     @include usecolour(border-top, ruledefault, 1px solid);
134 }
135
136 kbd {
137     font-family: monospace;
138     font-size: 0.95em;
139     &:before { content: "% "; }
140     &>span { white-space: nowrap; }
141
142     blockquote & {
143         display: block;
144         text-align: left;
145         padding-left: 3em;
146         text-indent: -3em;
147     }
148 }
149
150 .permalink {
151     @include usecolours($color: annotation);
152     font-size: small;
153
154     a:link, a:visited { color: inherit; }
155     a:active { @include usecolours($color: linkactive); }
156     @media not screen { visibility: hidden; }
157 }
158
159 // General table styles.
160 table {
161     @include usecolour(border-top, ruledefault, 1px solid);
162     border-collapse: collapse;
163     width: 100%;
164 }
165
166 table>* { font-size: 0.9em; }
167 caption {
168     caption-side: top;
169     font-weight: bold;
170     font-size: 1em;
171     text-align: left;
172     margin: 0 0 0.5em 0;
173 }
174
175 td, th {
176     vertical-align: middle;
177     text-align: left;
178     padding: 1ex;
179     margin: 0;
180 }
181
182 thead>tr, tbody>tr { @include usecolour(border, ruledefault, solid); }
183 th, thead>tr { @include usecolour(border-bottom, rulestrong, 1px solid); }
184 tbody+tbody { @include usecolour(border-bottom, ruledefault, 1px solid); }
185 *>table, *>th { border: none; }
186 thead>tr { border-width: 1px; }
187 tbody>tr { border-width: 0 1px; }
188
189 td + td {
190     @include usecolour(box-shadow, background, -1px 0);
191 }
192
193 tbody>tr {
194     &:nth-of-type(even) { @include usecolours($background-color: tableshade); }
195     &:last-child { @include usecolour(border-bottom, ruledefault, solid 1px); }
196 }
197
198 // Specific table styles
199 table.cc {
200     &>tr>*:first-child, &>*>tr>*:first-child {
201         &+* { text-align: center; }
202         text-align: center;
203     }
204 }
205
206 // CSS rules for stortable clicky table headers: Update the display of
207 // the /table based on the current state.  Each column has its own set
208 // nearly-identical rules, only the class names differ.
209 //
210 // The clickytables.xsl stylesheet generates two inputs for each column.
211 // These inputs are siblings of the table and all precede it in document
212 // order.  Moreover, the inputs for a column are ordered with respect to
213 // each other, in this sequence:
214 //
215 //   input.clicky-NAME     -- checked iff NAME is selected for sorting.
216 //   input.clicky-NAME-rev -- checked to select reverse order.
217 //
218 // One of the column selection inputs will have a 'checked' attribute to
219 // indicate the default order.  This input is always first in document
220 // order.  No other inputs begin checked.
221 //
222 // The table itself consists of a thead (where the header labels are
223 // located) and two tbody elements.  The bulk of these rules relate
224 // to updating the headers to visually indicate the current state.
225 //
226 // A sortable column's th element has the .clicky-NAME class, matching
227 // its corresponding inputs, and has two label children.  The first label
228 // is visible only when the column is unselected, and is linked to the
229 // .clicky-NAME input to activate that column.  The second label is visible
230 // only on the selected column and is linked to the .clicky-NAME-rev input
231 // to toggle the reverse order.
232 //
233 // For the table body, the first tbody contains the default ordering
234 // and is not styled by these rules (except to hide it when alternate
235 // orderings are selected).  The second tbody contains rows for all
236 // alternate orderings, and is revealed by these rules.  When revealed,
237 // rows with the NAMEfwd or NAMErev class (for the forward and reverse
238 // orderings, respectively) are shown and other rows are hidden.
239
240 $clickynames: name, date, size;
241 @each $col in $clickynames {
242     input.clicky-#{$col} {
243         &:checked {
244             &~table {
245                 // Update table header state
246                 & th.clicky-#{$col} {
247                     label~label {
248                         display: -moz-inline-box !important;
249                         display: inline-block !important;
250                     }
251                     label { display: none; }
252                 }
253
254                 // Show only appropriate items from the sort body (forward)
255                 &>tbody+tbody>tr.#{$col}fwd { display: table-row; }
256                 &>tbody+tbody>tr { display: none; }
257
258                 // Unhide sort body
259                 &>tbody {
260                     &+tbody { display: table-row-group !important; }
261                     display: none;
262                 }
263             }
264
265             // reverse state for selected sort column
266             &~input.clicky-#{$col}-rev {
267                 &:checked ~ table {
268                     // Show only appropriate items from sort body (reversed)
269                     &>tbody+tbody>tr.#{$col}rev { display: table-row; }
270                     &>tbody+tbody>tr { display: none; }
271
272                     // Unhide sort body
273                     &>tbody {
274                         &+tbody { display: table-row-group !important; }
275                         display: none;
276                     }
277                 }
278
279                 // Unhide to allow keyboard navigation to this input
280                 display: block !important;
281             }
282         }
283
284         // If default input element is the only one selected, match it to
285         // return to default view (overriding the changes above).  It is
286         // always the first input element among all the sibling elements.
287         // This seems to interoperate better than using the [checked] or
288         // :first-of-type selectors.
289         @at-root &:first-child, :not(input)+& {
290             &:checked~table>tbody {
291                 &+tbody { display: none !important; }
292                 display: table-row-group;
293             }
294         }
295
296         // Unhide to allow keyboard navigation
297         display: block !important;
298         pointer-events: none;
299         position: absolute;
300         opacity: 0;
301         z-index: -1;
302     }
303
304     input.clicky-#{$col}-rev {
305         &:checked ~ table {
306             // Update table header state
307             & th.clicky-#{$col} {
308                 .svg+.svg {
309                     display: -moz-inline-box !important;
310                     display: inline-block !important;
311                 }
312                 .svg { display: none; }
313             }
314         }
315
316         pointer-events: none;
317         position: absolute;
318         opacity: 0;
319         z-index: -2;
320     }
321
322     $focuslabel: ":focus ~ table th.clicky-#{$col}>label~label";
323     #{"input.clicky-#{$col+$focuslabel}"}>span:first-child
324     , #{"input.clicky-#{$col}-rev#{$focuslabel}"} .svg
325     {
326         @include usecolours($border-color: foreground);
327         @at-root { @supports (outline-style: auto) { & {
328             @include usecolour(outline, focusring, auto);
329             border-color: transparent !important;
330         }}}
331     }
332 }
333
334 thead.clicky label {
335     white-space: nowrap;
336     line-height: 1.5em;
337     cursor: pointer;
338
339     &>* {
340         display: -moz-inline-box;
341         display: inline-block;
342         border: 1px dotted transparent;
343         vertical-align: middle;
344     }
345
346     // Expand the first label a bit so the table (hopefully)
347     // does not reshape as columns are selected.
348     &:first-child {
349         margin-right: 1.75em;
350         padding-right: 2px;
351     }
352
353     &:active { @include usecolours($color: linkactive); }
354     &:first-child:active>span, &~label:active>.svg {
355         @include usecolours($border-color: linkactive);
356         @at-root { @supports (outline-style: auto) { & {
357             @include usecolour(outline, focusring, auto);
358             border-color: transparent;
359         }}}
360     }
361
362     .svg {
363         margin-left: 0.25em;
364     }
365
366     .svg, svg, img.svgfallback {
367         height: 1.5em;
368         width: auto;
369     }
370     .svg svg { width: 1.5em; }
371 }
372
373 table.filelist {
374     &>*>tr>*:first-child {
375         &+* { width: 50%; }
376         // chrome doesn't like width: 0 for some reason
377         width: 0.1px;
378     }
379
380     tbody {
381         .svg, svg, img.svgfallback {
382             vertical-align: middle;
383             height: 1.5em;
384             width: auto;
385         }
386
387         .svg {
388             svg { width: 1.5em; }
389             display: inline-block;
390         }
391     }
392 }
393
394 // Site header rules
395 #breadcrumbs>*, #sitetitle>* { font-size: 0.8em; }
396 #breadcrumbs {
397     * {
398         display: inline;
399         list-style-type: none;
400         vertical-align: top;
401         padding: 0;
402         margin: 0;
403     }
404
405     li + li:before { content: "/ "; }
406 }
407 #sitetitle * {
408     display: inline-block;
409     float: right;
410 }
411
412 // Site footer rules
413 #footer, #article-info {
414     text-align: center;
415     max-width: 44em;
416     padding: 0 3em;
417     margin: 0;
418
419     p {
420         display: inline-block;
421         font-size: 0.8em;
422         max-width: 100%;
423         margin: 0.2em 0;
424     }
425 }
426
427 #footer p { @include usecolours($color: annotation); }
428 #article-info p { font-style: italic; }
429
430 .wbr:after { content: "\200b"; }
431
432 // "unordered" lists with explicit ordering in content
433 ul.ordered > {
434     li { list-style: none; }
435     li>span:first-child, li>*:first-child>span:first-child {
436         display: inline-block;
437         text-align: right;
438         margin-left: -1.8em;
439         min-width: 1.8em;
440     }
441 }
442
443 @media (max-width: 512px) {
444     body { margin: 0.6em; }
445     ul ul { margin-left: -1.2em; }
446     dd { margin: 0; }
447 }
448
449 @media (max-width: 35em) {
450     #sitetitle * { float: none; }
451     #footer { padding: 0 1em; }
452 }
453
454 // lighten icon shadows in dark mode
455 @media (prefers-color-scheme: dark) {
456     svg.icons { path.shadow, g.shadow>* { opacity: 0.7; } }
457     svg.icons .shadow>stop { stop-color: #aaa; }
458     svg.return path.shadow { opacity: 0.45; }
459 }
460
461 // page-specific dark mode styles
462 @media (prefers-color-scheme: dark) and (min-width: 35em) {
463     #page_weblog_responsive_tables {
464         @each $tN in t6 t7 t8 {
465             ##{$tN}>tbody>tr.#{$tN}-split {
466                 @include usecolour_dark_(border-bottom, ruledefault);
467
468                 &:nth-of-type(odd) ~ tr:nth-of-type(odd) {
469                     @include usecolour_dark_(background-color, tableshade);
470                 }
471             }
472         }
473     }
474 }