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