Repository with sources and generator of https://larlet.fr/david/ https://larlet.fr/david/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="fr">
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Covidoudou — David Larlet</title>
  13. <meta name="description" content="Journal de déconfinement.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f0f0ea">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f0f0ea">
  26. <!-- Documented, feel free to shoot an email. -->
  27. <link rel="stylesheet" href="/static/david/css/style_2020-06-19.css">
  28. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  29. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  30. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  31. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  32. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  35. <script type="text/javascript">
  36. function toggleTheme(themeName) {
  37. document.documentElement.classList.toggle(
  38. 'forced-dark',
  39. themeName === 'dark'
  40. )
  41. document.documentElement.classList.toggle(
  42. 'forced-light',
  43. themeName === 'light'
  44. )
  45. }
  46. const selectedTheme = localStorage.getItem('theme')
  47. if (selectedTheme !== 'undefined') {
  48. toggleTheme(selectedTheme)
  49. }
  50. </script>
  51. <body class="remarkdown h1-underline h2-underline h3-underline hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  52. <article>
  53. <header>
  54. <h1>Covidoudou</h1>
  55. </header>
  56. <nav>
  57. <p class="center">
  58. <a rel="prev" href="/david/2020/05/20/" title="Publication précédente : Bikepacking">←</a> •
  59. <a href="/david/" title="Aller à l’accueil">🏠</a>
  60. • <a rel="next" href="/david/2020/06/12/" title="Publication suivante : Vrac">→</a>
  61. </p>
  62. </nav>
  63. <hr>
  64. <main>
  65. <p><em>Journal de déconfinement.</em></p>
  66. <h2 id="preambule">Préambule <a href="#preambule" title="Ancre vers cette partie">#</a></h2><p>Il est rare que j’écrive sur mon travail avec aussi peu de recul. J’avais déjà partagé une <a href="/david/2020/01/17/">expérimentation avec data.gouv.fr</a> et <a href="/david/2020/04/18/">l’aventure Zam</a> sur ces deux dernières années. Ici, le contexte est un peu particulier et s’apparente davantage à un <em>sprint</em>. Il concerne potentiellement 68 millions de français·es (<abbr title="private joke">👋🐢🤔</abbr>), ce qui met la barre assez haute.</p>
  67. <p>Récit de ce qu’il se passe parfois derrière le rideau de vos écrans.</p>
  68. <h2 id="jour-1-5-mai-un-demarrage-etrange">Jour 1 (5 mai) : Un démarrage étrange <a href="#jour-1-5-mai-un-demarrage-etrange" title="Ancre vers cette partie">#</a></h2><p>Cela fait quelques temps avec <a href="/david/blog/2019/faire-equipe/">l’équipe</a> que l’on se demande quoi faire car la <a href="/david/2020/04/18/#suite">suite annoncée</a> n’est pas aussi réactive que ce que l’on eu espéré, contexte sanitaire oblige. On essaye de se positionner sur des produits où l’on pense pouvoir être utiles et qui ne soient pas du solutionnisme technologique.</p>
  69. <p>On nous propose de faire un questionnaire pour le Ministère des Solidarités et de la Santé qui soit à l’opposé d’un autre produit développé en parallèle par le gouvernement :</p>
  70. <ul>
  71. <li><em>open-source</em> dès le premier jour</li>
  72. <li>sans aucune donnée collectée</li>
  73. <li>avec un algorithme public</li>
  74. </ul>
  75. <p>Jusque là, c’est plutôt alléchant mais le <em>timing</em> est quand même serré : le déconfinement est prévu pour le 11 mai. Soit 6 jours plus tard. <em>Gloups.</em></p>
  76. <p>En plus de l’urgence, d’autres voyants sont au rouge : on évoque une <q>équipe en mode commando</q> et il y a déjà beaucoup d’attentes bien qu’aucune ligne de code n’ait été produite… sans compter le fait que nous ne sommes ni des développeurs JavaScript, ni familiers des plateformes de distribution des <abbr title="Google, Apple, Facebook, Amazon, Microsoft et consorts.">GAFAM+</abbr>. Par choix.</p>
  77. <p>On finit par accepter car l’intention nous semble louable. Opération : <strong>Covidoudou</strong>.</p>
  78. <h2 id="jour-3-7-mai-doutes-techniques">Jour 3 (7 mai) : Doutes techniques <a href="#jour-3-7-mai-doutes-techniques" title="Ancre vers cette partie">#</a></h2>
  79. <blockquote lang="en">
  80. <p>What is clear: right now, if you’re using a framework to build your site, <mark>you’re making a trade-off in terms of initial performance</mark>—even in the best of scenarios.</p>
  81. <p><em>Some</em> trade-off may be acceptable in the right situations, but it’s important that we make that exchange consciously.</p>
  82. <p><cite><em><a href="https://timkadlec.com/remembers/2020-04-21-the-cost-of-javascript-frameworks/">The Cost of Javascript Frameworks</a></em> (<a href="/david/cache/2020/be8e81e9337d81e7a31a5cc1f4d38435/">cache</a>)</cite></p>
  83. </blockquote>
  84. <p>Beaucoup d’interrogations relatives aux performances et à la complexité que l’on souhaite déléguer dans ce domaine. On s’attend à avoir potentiellement une charge non négligeable à encaisser. On évalue plusieurs solutions mais aucune ne nous satisfait pleinement, aussi on décide de faire un premier jet avec une succession de vues en JavaScript à la main qui reprennent des éléments <code>template</code> pré-chargés dans le HTML. Lorsqu’on fait un compromis, il s’agit toujours de tenter de trouver la solution la moins pire <em>pour un contexte donné</em>.</p>
  85. <blockquote lang="en">
  86. <p>But I think there are a lot of problems that are better solved some other way.</p>
  87. <p><cite><em><a href="https://macwright.org/2020/05/10/spa-fatigue.html">Second-guessing the modern web</a></em> (<a href="/david/cache/2020/ebaa216561b046ae17b29b399305b294/">cache</a>)</cite></p>
  88. </blockquote>
  89. <p>En parallèle, on commence à implémenter l’algorithme. Il est décrit dans des fichiers du genre <code>Copie de algo_deconf_final_V3.xlsx</code> (véridique) et mis à jour par un comité d’experts médecins qui se l’échangent par courriel. On reçoit plusieurs versions par jour !</p>
  90. <p>On fait une première démo à l’équipe rapprochée.</p>
  91. <h2 id="jour-5-9-mai-un-algorithme-recalcitrant">Jour 5 (9 mai) : Un algorithme récalcitrant <a href="#jour-5-9-mai-un-algorithme-recalcitrant" title="Ancre vers cette partie">#</a></h2><p>L’objectif est simple : on a une série de questions et en fonction des réponses on affiche des conseils personnalisés en fonction de la localisation, des caractéristiques, des antécédents et des symptômes ou contact à risque potentiels.</p>
  92. <p>Sauf que tout bouge très vite, autant les questions que les conseils ou l’arbre logique qui permet de passer des unes aux autres. Sans compter qu’il a été écrit par des médecins dans une feuille de calcul et qu’il faut aller repêcher les informations à la main. Pas évident.</p>
  93. <p>En parallèle, on <a href="https://github.com/Delegation-numerique-en-sante/mesconseilscovid/">publie le code source</a> sous licence MIT et on est maintenant en capacité de faire une démo à l’équipe élargie. Les retours sont plutôt bons et ça tranquillise beaucoup de monde.</p>
  94. <h2 id="jour-7-11-mai-tenir-la-charge">Jour 7 (11 mai) : Tenir la charge <a href="#jour-7-11-mai-tenir-la-charge" title="Ancre vers cette partie">#</a></h2>
  95. <blockquote lang="en">
  96. <p>If you are in charge of a web site that provides even slightly important information, or important services, <strong>it’s time to get static</strong>.</p>
  97. <p><cite><em><a href="https://meyerweb.com/eric/thoughts/2020/03/22/get-static/">Get Static</a></em> (<a href="/david/cache/2020/4f88ece170719f58ce09ba4b1818730a/">cache</a>)</cite></p>
  98. </blockquote>
  99. <p>Ronan s’occupe de faire en sorte que l’architecture tienne le coup, on évoque une annonce à la télévision et apparemment ça peut faire un gros pic dans les courbes. Surtout dans cette période anxiogène. Heureusement que l’on ne sert qu’une page statique <a href="https://adactio.com/journal/16797">relativement légère</a> (<a href="/david/cache/2020/24f52bba99b1423102f93cf86b948c5b/">cache</a>) au final. Sans <a href="https://www.kryogenix.org/days/2020/05/06/hammer-and-nails/">recompiler</a> (<a href="/david/cache/2020/73f93e0e8e7810a36d88555c2cbfa573/">cache</a>) trop de choses dans le navigateur.</p>
  100. <p>De mon côté, j’extrais toutes les chaines de caractères <a href="https://github.com/Delegation-numerique-en-sante/mesconseilscovid/tree/master/contenus">dans des fichiers markdown</a> et je <a href="https://github.com/Delegation-numerique-en-sante/mesconseilscovid/tree/master/contenus#algorithme">documente l’algorithme</a> en faisant des liens vers chaque fichier. Je ne sais pas où je vais mais j’ai l’intuition que ça sera moins galère que l’historique des révisions d’une (ou plusieurs !) feuille(s) de calcul.</p>
  101. <p>On commence à avoir des retours d’associations de patients pour nous aider à rendre le contenu intelligible par des non-médecins/techniciens. C’est chouette.</p>
  102. <h2 id="jour-9-13-mai-lancement-officiel">Jour 9 (13 mai) : Lancement officiel <a href="#jour-9-13-mai-lancement-officiel" title="Ancre vers cette partie">#</a></h2><p>On a enfin une adresse officielle : <a href="https://mesconseilscovid.sante.gouv.fr/">mesconseilscovid.sante.gouv.fr</a>, en <em>seulement</em> 9 jours ! Ça peut vous sembler long mais obtenir un sous-domaine en <code>.gouv.fr</code> est parfois un très très long périple.</p>
  103. <figure>
  104. <img src="/static/david/2020/mesconseilscovid.png"
  105. alt="Capture d’écran du site"
  106. loading="lazy" width="1280" height="800" />
  107. <figcaption>Capture d’écran du site.</figcaption>
  108. </figure>
  109. <p>On peaufine le cache mais aussi sa mise à jour. Il faut que l’application soit conforme à des doctrines gouvernementales changeantes dans un contexte où la réactivité est cruciale. Notre connaissance de la maladie évolue et les conseils pour y échapper aussi.</p>
  110. <p>Même avec les <em>polyfills</em> qui vont bien, il semblerait que l’élément <code>template</code> soit un peu capricieux avec Internet Explorer 11 et on espère que le site soit utilisable par le plus de personnes possibles. Cela occasionne une veillée à revenir sur notre façon d’écrire les pages/vues. Pas vraiment la partie la plus fun…</p>
  111. <h2 id="jour-11-15-mai-structuration-et-consolidation">Jour 11 (15 mai) : Structuration et consolidation <a href="#jour-11-15-mai-structuration-et-consolidation" title="Ancre vers cette partie">#</a></h2><p>Jusqu’à présent, on a favorisé la vitesse au détriment de la robustesse. On ne peut pas avoir les deux en si peu de temps. Mais par contre, il faut être suffisamment lucide pour savoir s’arrêter à temps et consolider l’existant. Alors on écrit plus de tests, on ré-arrange le code pour le rendre plus compréhensible.</p>
  112. <p>Côté contenus, la proposition de passer par Microsoft Github pour éditer les contenus (et peut-être à terme l’algorithme lui-même ?!) a été bien acceptée et on commence à recevoir des <em>pull-requests</em> de médecins.</p>
  113. <p>Poussés par le besoin, on a créé une nouvelle façon technique d’interagir. Un <em>framework</em> nous aurait probablement trop contraint pour avoir cette liberté d’innovation. Difficile de se prononcer avec certitude toutefois.</p>
  114. <h2 id="jour-15-19-mai-un-cms-statique">Jour 15 (19 mai) : un CMS statique <a href="#jour-15-19-mai-un-cms-statique" title="Ancre vers cette partie">#</a></h2><p>Suite à une réunion de travail, on se rend compte qu’il est difficile d’avoir accès à l’intégralité des contenus disponibles pour pouvoir rester cohérent dans les formulations. Aussi, un découpage par dossier s’impose et permet d’avoir un fichier <em>README.md</em> qui va compiler <a href="https://github.com/Delegation-numerique-en-sante/mesconseilscovid/tree/master/contenus/statuts#statuts">l’ensemble de ces contenus</a> par type. Cela fournit un lien pour aller éditer le bon contenu depuis ces index.</p>
  115. <p>On sent naître aussi la nécessité d’un <em>CHANGELOG</em> afin que toutes l’équipe élargie sache ce qui est <a href="https://github.com/Delegation-numerique-en-sante/mesconseilscovid/blob/master/CHANGELOG.md#changelog">implémenté et déployé</a>. C’est intéressant — et un peu effrayant — de voir à quel point on a réussi à faire contribuer des médecins peu techniques en utilisant nos outils de développeurs·ses.</p>
  116. <p><em>Aussi, je m’éclipse pour <a href="/david/2020/05/20/">une vingtaine d’heures</a> en forêt.</em></p>
  117. <h2 id="jour-18-22-mai-prise-de-recul">Jour 18 (22 mai) : Prise de recul <a href="#jour-18-22-mai-prise-de-recul" title="Ancre vers cette partie">#</a></h2>
  118. <blockquote lang="en">
  119. <p>Prioritisation isn’t easy, and it gets harder the more factors come into play: user needs, business needs, technical constraints. But it’s worth investing the time to get agreement on the priority of your constituencies. And then formulate that agreement into design principles.</p>
  120. <p><cite><em><a href="https://adactio.com/journal/16811">Principles and priorities</a></em> (<a href="/david/cache/2020/f3be13f0057c9ff350b6e0bf3c3be90b/">cache</a>)</cite></p>
  121. </blockquote>
  122. <p>Voici venu le temps de se ré-interroger sur nos intentions, les attentes, les outils employés, nos pratiques. C’est pour moi l’un des meilleurs moments d’un produit, ce petit coup d’œil régulier dans le rétroviseur. Celui qui permet de réajuster une trajectoire naissante en désignant un nouveau cap.</p>
  123. <p>Particulièrement côté technique, on a intentionnellement évité tout les outils JS jusqu’à présent. Il y a peut-être des petites briques à récupérer de-ci de-là sans télécharger la moitié du web. Je me réjouis que l’on ait réussi à conserver une page performante et relativement accessible pour l’instant :</p>
  124. <figure>
  125. <img src="/static/david/2020/andouillette_web.png"
  126. alt="Une série de A depuis l’outil WebPageTest"
  127. loading="lazy" width="724" height="222" />
  128. <figcaption>L’andouillette du développeur Web.</figcaption>
  129. </figure>
  130. <p>Autre victoire non négligeable, tout reste dans le navigateur. Les seules statistiques que l’on ait sont celles de téléchargement de la page et toutes les dix minutes un <em>ping</em> pour vérifier que les conseils sont bien à jour. Vous pouvez refaire le test 15 fois et on ne le saura pas. Vous pouvez mettre n’importe quoi et ça ne changera rien de notre côté. Bon, ça peut planter et on ne le sait pas non plus…</p>
  131. <p>En parallèle, des démo sont faites à des personnes plus ou moins importantes qui vont déboucher sur des annonces plus ou moins officielles/diffusées. Jusqu’ici ça tient.</p>
  132. <p><em>J’ai travaillé 14,5 jours sur ces 18 derniers jours.</em></p>
  133. <h2 id="jour-25-29-mai-peaufinage-et-outillage">Jour 25 (29 mai) : peaufinage et outillage <a href="#jour-25-29-mai-peaufinage-et-outillage" title="Ancre vers cette partie">#</a></h2>
  134. <blockquote>
  135. <p>Dans ce « désert sémantique emplis d’échos confus », nous avons besoin d’un « quelconque fétiche prestigieux » qui puisse servir de « <mark>doudou</mark> ».</p>
  136. <p><cite><em><a href="https://lundi.am/Sur-la-pandemie-actuelle-d-apres-le-point-de-vue-d-Ivan-Illich">Sur la pandémie actuelle, d’après le point de vue d’Ivan Illich</a></em> (<a href="/david/cache/2020/be1507ea944c6b8d0abd2a6c09efdc43/">cache</a>)</cite></p>
  137. </blockquote>
  138. <p>On intègre des illustrations qui donnent des repères visuels aux différentes sections. Cela permet de faire un rappel entre la question et les conseils associés. Ces illustrations sont issues de <a href="https://undraw.co/">unDraw</a> et sont libres de droits.</p>
  139. <p>On passe aussi une partie de la semaine à finalement s’outiller en JavaScript avec <a href="https://parceljs.org/">Parcel</a> et à ré-arranger notre code afin de le rendre plus résilient. Fini le fichier fourre-tout avec des variables globales et des dépendances non explicites. Cela nous permet aussi de passer à des tests automatisés, ce qui nous met suffisamment en confiance pour reprendre l’algorithme.</p>
  140. <p>En parallèle, on essaye de savoir <em>via</em> les logs HTTP et <a href="https://goaccess.io/">goaccess</a> d’où viennent nos visiteur·ses. Ce n’est pas si simple car il faut agréger ceux-ci depuis plusieurs machines.</p>
  141. <p><em>Mine de rien, le site commence à être utilisé.</em></p>
  142. <h2 id="jour-33-10-juin-orientation-et-pediatrie">Jour 33 (10 juin) : orientation et pédiatrie <a href="#jour-33-10-juin-orientation-et-pediatrie" title="Ancre vers cette partie">#</a></h2><p>Les retours des testeuses et testeurs nous indiquent qu’il serait pertinent d’ajouter des informations relatives à la pédiatrie. On ajoute une page dédiée pour les moins de 15 ans.</p>
  143. <p>Autre amélioration, on implémente <a href="https://github.com/Delegation-numerique-en-sante/covid19-algorithme-orientation">l’algorithme d’orientation</a> pour les personnes présentant actuellement des symptômes afin de prodiguer des conseils plus pertinents dans ce cas là.</p>
  144. <p>Je résume mais il y a bien évidemment une somme de petites retouches apportées ici et là pour rendre le site plus utile.</p>
  145. <hr />
  146. <p>Je vais m’arrêter là pour le moment, je ferai sûrement une nouvelle entrée dédiée lorsqu’on décidera de mettre un terme au produit. D’ici là vous pouvez suivre le <a href="https://github.com/Delegation-numerique-en-sante/mesconseilscovid/blob/master/CHANGELOG.md#changelog">CHANGELOG</a> dédié.</p>
  147. </main>
  148. <nav>
  149. <p class="center">
  150. <a rel="prev" href="/david/2020/05/20/" title="Publication précédente : Bikepacking">←</a> •
  151. <a href="/david/2020/" title="Liste des publications récentes">↑</a>
  152. • <a rel="next" href="/david/2020/06/12/" title="Publication suivante : Vrac">→</a>
  153. </p>
  154. </nav>
  155. </article>
  156. <hr>
  157. <footer>
  158. <p>
  159. <a href="/david/" title="Aller à l’accueil">🏠</a> •
  160. <a href="/david/log/" title="Accès au flux RSS">🤖</a> •
  161. <a href="http://larlet.com" title="Go to my English profile" data-instant>🇨🇦</a> •
  162. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">📮</a> •
  163. <abbr title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340">🧚</abbr>
  164. </p>
  165. <template id="theme-selector">
  166. <form>
  167. <fieldset>
  168. <legend>Thème</legend>
  169. <label>
  170. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  171. </label>
  172. <label>
  173. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  174. </label>
  175. <label>
  176. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  177. </label>
  178. </fieldset>
  179. </form>
  180. </template>
  181. </footer>
  182. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module" defer></script>
  183. <script type="text/javascript">
  184. function loadThemeForm(templateName) {
  185. const themeSelectorTemplate = document.querySelector(templateName)
  186. const form = themeSelectorTemplate.content.firstElementChild
  187. themeSelectorTemplate.replaceWith(form)
  188. form.addEventListener('change', (e) => {
  189. const chosenColorScheme = e.target.value
  190. localStorage.setItem('theme', chosenColorScheme)
  191. toggleTheme(chosenColorScheme)
  192. })
  193. const selectedTheme = localStorage.getItem('theme')
  194. if (selectedTheme && selectedTheme !== 'undefined') {
  195. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  196. }
  197. }
  198. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  199. window.addEventListener('load', () => {
  200. let hasDarkRules = false
  201. for (const styleSheet of Array.from(document.styleSheets)) {
  202. let mediaRules = []
  203. for (const cssRule of styleSheet.cssRules) {
  204. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  205. continue
  206. }
  207. // WARNING: Safari does not have/supports `conditionText`.
  208. if (cssRule.conditionText) {
  209. if (cssRule.conditionText !== prefersColorSchemeDark) {
  210. continue
  211. }
  212. } else {
  213. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  214. continue
  215. }
  216. }
  217. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  218. }
  219. // WARNING: do not try to insert a Rule to a styleSheet you are
  220. // currently iterating on, otherwise the browser will be stuck
  221. // in a infinite loop…
  222. for (const mediaRule of mediaRules) {
  223. styleSheet.insertRule(mediaRule.cssText)
  224. hasDarkRules = true
  225. }
  226. }
  227. if (hasDarkRules) {
  228. loadThemeForm('#theme-selector')
  229. }
  230. })
  231. </script>
  232. </body>
  233. </html>