|
| 1 | +{% comment %}<!-- |
| 2 | +So this is all a bit silly. Ultimately, what we want to do is load either |
| 3 | +light.css or dark.css depending on whether light or dark theme should |
| 4 | +be selected. We base that decision on the prefers-color-scheme media query |
| 5 | +by default, but also allow the user to override that in javascript. In |
| 6 | +principle we only want to load *one* of the two files, but that doesn't |
| 7 | +seem possible in light of browsers that don't support prefers-color-scheme |
| 8 | +at al: the will fail even a query like "not (prefers-color-scheme: dark)" |
| 9 | +because any query with unknown bits fails regardless of the "not". |
| 10 | +
|
| 11 | +In JavaScript, however, we can load just one file since we can implement |
| 12 | +the "not" logic outside of the query, and originally that's how it worked: |
| 13 | +load either light.css or dark.css in the JavaScript, while the <noscript> |
| 14 | +version had to load light unconditionally then dark conditioned on the query. |
| 15 | +
|
| 16 | +Futher, we need to load the stylesheet using document.write() in the JS, |
| 17 | +since using other DOM manipulatoin techniques like appendChild causes a |
| 18 | +flash of unstyled content while changing pages. document.write() avoids |
| 19 | +this: apparently CSS files injected that way are treated as render-blocking |
| 20 | +in the same as those which appear directetly in the HTML document. |
| 21 | +
|
| 22 | +Now for whatever reason, this original approach seemed to cause the google |
| 23 | +spider to fail to load the CSS, probably because of heuristics where it |
| 24 | +tries to avoid loading resoures that aren't critical to indexing. This in |
| 25 | +turn causes it to fail the mobile experience audit, since an unstyled page |
| 26 | +has tons of problems on mobile such as too-small or too-wide text. |
| 27 | +
|
| 28 | +So finally we converge on the current solution: we specify <link> elements |
| 29 | +in the HTML regardless of whether javascript is enabled, for the light |
| 30 | +and dark files, with the dark CSS conditioned on the media query. |
| 31 | +This just works for everyone who doesn't override the theme. Then we run |
| 32 | +the js which implements the override and if an override is present, a |
| 33 | +third <link id="override-style" is added which point to either the light |
| 34 | +or dark CSS as the case may be. This override is add using document.write() |
| 35 | +to avoid FOUC as discusssed above. |
| 36 | +
|
| 37 | +The settings page may need to add this override <link> dynamically if we |
| 38 | +switch from not using to using an override. |
| 39 | +
|
| 40 | +Finally, we apply the theme-color *after* the script element, using the |
| 41 | +media query. Although I don't think it's defined in the spec, theme color |
| 42 | +seems to work in the opposite way from CSS: the first applicable <meta> |
| 43 | +tag with theme-color defines the color. So we need to have a media query |
| 44 | +for both light and dark variants, and if the query fails because it's |
| 45 | +not supported, that's fine since those browsers aren't going to support |
| 46 | +theme-color anyway. If the user has overridden the theme, we emit a |
| 47 | +theme-color <meta> tag in the javascript without any media query: this |
| 48 | +one comes first so it "wins". |
| 49 | +
|
| 50 | +Ugh. |
| 51 | +-->{% endcomment %} |
1 | 52 | <meta name="color-scheme" content="light dark">
|
2 | 53 | {%- capture lightcss -%}{{ '/assets/css/light.css' | relative_url }}{%- endcapture -%}
|
3 | 54 | {%- capture darkcss -%}{{ '/assets/css/dark.css' | relative_url }}{%- endcapture %}
|
| 55 | +{% assign dark-query = '(prefers-color-scheme: dark)' %} |
| 56 | +<link rel="stylesheet" href="{{ lightcss }}"> |
| 57 | +<link rel="stylesheet" href="{{ darkcss }}" media="{{ dark-query }}"> |
4 | 58 | <script>
|
5 |
| - |
6 |
| -var DARKMODE = (function() { |
| 59 | + var DARKMODE = (function() { |
7 | 60 | const i = {
|
8 | 61 | PROP: 'force-color',
|
| 62 | + OID: 'override-style', |
| 63 | + BANNER: '{{ site.darkmode.hide_banner }}', |
9 | 64 | getOverride: function () {
|
10 | 65 | try {
|
11 | 66 | return localStorage.getItem(i.PROP);
|
|
25 | 80 | gaTheme: function () {
|
26 | 81 | var o = i.getOverride();
|
27 | 82 | return o ? o + ' force' : i.get() + ' default';
|
| 83 | + }, |
| 84 | + makeLink: function (c) { |
| 85 | + e = document.createElement('link'); |
| 86 | + e.id = i.OID; |
| 87 | + e.rel = 'stylesheet'; |
| 88 | + e.href = (c === 'dark' ? '{{darkcss}}' : '{{lightcss}}'); |
| 89 | + return e; |
28 | 90 | }
|
29 |
| - }; |
30 |
| - return i; |
| 91 | + } |
| 92 | + return i; |
31 | 93 | }());
|
32 | 94 |
|
33 |
| -if (DARKMODE.get() == 'dark') { |
34 |
| - var link = '<meta name="theme-color" content="#181818"><link id="mainstyle" rel="stylesheet" href="{{ darkcss }}">'; |
35 |
| -} else { |
36 |
| - var link = '<meta name="theme-color" content="#fdfdfd"><link id="mainstyle" rel="stylesheet" href="{{ lightcss }}">'; |
37 |
| -} |
38 |
| -document.write(link); |
| 95 | +if (DARKMODE.getOverride()) { |
| 96 | + var dm_color = DARKMODE.get(); |
| 97 | + document.write(DARKMODE.makeLink(dm_color).outerHTML + |
| 98 | + '\n<meta name="theme-color" content="#' + (dm_color === 'dark' ? '181818' : 'fdfdfd') + '">'); |
| 99 | +} |
39 | 100 | </script>
|
40 |
| - |
| 101 | +<meta name="theme-color" content="#fdfdfd" media="not all and {{ dark-query }}"> |
| 102 | +<meta name="theme-color" content="#181818" media="{{ dark-query }}"> |
41 | 103 | <script defer src="{{ '/assets/dark-mode.js' | relative_url }}"></script>
|
42 | 104 |
|
43 |
| -<noscript> |
44 |
| - <link rel="stylesheet" href="{{ lightcss }}"> |
45 |
| - <link rel="stylesheet" href="{{ darkcss }}" media="(prefers-color-scheme: dark)"> |
46 |
| -</noscript> |
|
0 commit comments