refacto: Remake the interface so it is more 'vintage' looking + Adding a full calendar page

This commit is contained in:
gauvainboiche
2026-10-25 12:01:00 +01:00
parent 2539b6c756
commit f6738972f1
45 changed files with 1433 additions and 361 deletions
+50
View File
@@ -0,0 +1,50 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 527 713" role="img" aria-label="Jours complémentaires">
<defs>
<radialGradient id="sky" cx="50%" cy="40%" r="75%">
<stop offset="0" stop-color="#efe3c5"/>
<stop offset="1" stop-color="#d3bf93"/>
</radialGradient>
</defs>
<rect width="527" height="713" fill="url(#sky)"/>
<!-- laurel wreath -->
<g fill="none" stroke="#5f6b3c" stroke-width="3" stroke-linecap="round">
<path d="M263 640 C140 620 90 520 100 400 C108 300 150 220 210 180"/>
<path d="M263 640 C386 620 436 520 426 400 C418 300 376 220 316 180"/>
</g>
<g fill="#7b8a4c" stroke="#4d5830" stroke-width="1.5">
<g id="leaves-left">
<ellipse cx="108" cy="560" rx="11" ry="25" transform="rotate(-35 108 560)"/>
<ellipse cx="96" cy="500" rx="11" ry="25" transform="rotate(-18 96 500)"/>
<ellipse cx="94" cy="440" rx="11" ry="25" transform="rotate(-6 94 440)"/>
<ellipse cx="100" cy="380" rx="11" ry="25" transform="rotate(8 100 380)"/>
<ellipse cx="116" cy="322" rx="11" ry="25" transform="rotate(22 116 322)"/>
<ellipse cx="144" cy="270" rx="11" ry="25" transform="rotate(34 144 270)"/>
<ellipse cx="184" cy="226" rx="11" ry="25" transform="rotate(46 184 226)"/>
<ellipse cx="170" cy="580" rx="11" ry="25" transform="rotate(-60 170 580)"/>
</g>
<use href="#leaves-left" transform="translate(527 0) scale(-1 1)"/>
</g>
<!-- fasces -->
<g transform="translate(213 130) scale(1.7 1.7)" fill="none" stroke="#3a2d1d" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round">
<path d="M13 50 C11 26 22 10 38 9 C47 9 52 15 47 21 C44 25 48 31 47 50 Z" fill="#8e2b26"/>
<rect x="11" y="46" width="38" height="8" rx="2" fill="#d9c9a3"/>
<rect x="27" y="54" width="6" height="14" fill="#b9a67c"/>
<g fill="#d9c9a3">
<rect x="9" y="68" width="7" height="268" rx="3.5"/>
<rect x="17" y="68" width="7" height="268" rx="3.5"/>
<rect x="25" y="68" width="7" height="268" rx="3.5"/>
<rect x="33" y="68" width="7" height="268" rx="3.5"/>
<rect x="41" y="68" width="7" height="268" rx="3.5"/>
</g>
<g fill="#c9b684">
<path d="M7 118 L51 98 L51 110 L7 130 Z"/>
<path d="M7 168 L51 148 L51 160 L7 180 Z"/>
<path d="M7 218 L51 198 L51 210 L7 230 Z"/>
<path d="M7 268 L51 248 L51 260 L7 280 Z"/>
</g>
<g fill="#3a2d1d">
<rect x="6" y="74" width="46" height="12" rx="2"/>
<rect x="6" y="318" width="46" height="12" rx="2"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+29
View File
@@ -0,0 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 360" fill="none" stroke="#3a2d1d" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round">
<!-- Phrygian cap on its pike -->
<path d="M13 50 C11 26 22 10 38 9 C47 9 52 15 47 21 C44 25 48 31 47 50 Z" fill="#8e2b26"/>
<path d="M38 9 C41 6 45 7 46 10" stroke-width="1.2"/>
<rect x="11" y="46" width="38" height="8" rx="2" fill="#d9c9a3"/>
<rect x="27" y="54" width="6" height="14" fill="#b9a67c"/>
<!-- rods -->
<g fill="#d9c9a3">
<rect x="9" y="68" width="7" height="268" rx="3.5"/>
<rect x="17" y="68" width="7" height="268" rx="3.5"/>
<rect x="25" y="68" width="7" height="268" rx="3.5"/>
<rect x="33" y="68" width="7" height="268" rx="3.5"/>
<rect x="41" y="68" width="7" height="268" rx="3.5"/>
</g>
<path d="M21 72V332M29 72V332M37 72V332M45 72V332" stroke-width="0.8" opacity=".55"/>
<!-- spiral ribbon -->
<g fill="#c9b684">
<path d="M7 118 L51 98 L51 110 L7 130 Z"/>
<path d="M7 168 L51 148 L51 160 L7 180 Z"/>
<path d="M7 218 L51 198 L51 210 L7 230 Z"/>
<path d="M7 268 L51 248 L51 260 L7 280 Z"/>
</g>
<!-- binding bands -->
<g fill="#3a2d1d">
<rect x="6" y="74" width="46" height="12" rx="2"/>
<rect x="6" y="318" width="46" height="12" rx="2"/>
</g>
<rect x="6" y="342" width="46" height="8" rx="2" fill="#b9a67c"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<circle cx="32" cy="32" r="29" fill="#8e2b26" stroke="#3a2d1d" stroke-width="3"/>
<circle cx="32" cy="32" r="19" fill="#f3ead3"/>
<circle cx="32" cy="32" r="9" fill="#2c4268"/>
</svg>

After

Width:  |  Height:  |  Size: 251 B

+93
View File
@@ -0,0 +1,93 @@
Google Inc.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,93 @@
Copyright 2011 The Old Standard Project Authors (amkryukov@gmail.com) OldStandard-Italic.ttf: Copyright 2011 The Old Standard Project Authors (amkryukov@gmail.com) OldStandard-Bold.ttf: Copyright 2011 The Old Standard Project Authors (amkryukov@gmail.com)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

-9
View File
@@ -1,9 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Brumaire">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<circle cx="62" cy="46" r="12" fill="#e9dcb8" opacity="0.8"/>
<g fill="none" stroke="#8a9a94" stroke-width="3" stroke-linecap="round">
<path d="M28 56 Q38 50 48 56 T68 56 T92 56"/>
<path d="M24 68 Q34 62 44 68 T64 68 T96 68"/>
<path d="M30 80 Q40 74 50 80 T70 80 T90 80"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 483 B

-9
View File
@@ -1,9 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Jours complémentaires">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<g fill="none" stroke="#7a9a5a" stroke-width="3" stroke-linecap="round">
<path d="M60 90 C40 84 32 66 38 46 C46 54 48 66 60 90 Z"/>
<path d="M60 90 C80 84 88 66 82 46 C74 54 72 66 60 90 Z"/>
</g>
<path d="M60 26 L64 38 L76 38 L66 46 L70 58 L60 50 L50 58 L54 46 L44 38 L56 38 Z"
fill="#e8b84b" stroke="#a97e1f" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

-14
View File
@@ -1,14 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Floréal">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<path d="M60 68 L60 92" stroke="#5c7a4a" stroke-width="3" stroke-linecap="round"/>
<path d="M60 82 C52 80 48 74 50 70 C58 70 62 76 60 82 Z" fill="#7a9a5a" stroke="#4f6b3d" stroke-width="1.2"/>
<g fill="#c76b8a" stroke="#8a3b58" stroke-width="1.2">
<ellipse cx="60" cy="44" rx="8" ry="12"/>
<ellipse cx="60" cy="44" rx="8" ry="12" transform="rotate(60 60 44)"/>
<ellipse cx="60" cy="44" rx="8" ry="12" transform="rotate(120 60 44)"/>
<ellipse cx="60" cy="44" rx="8" ry="12" transform="rotate(180 60 44)"/>
<ellipse cx="60" cy="44" rx="8" ry="12" transform="rotate(240 60 44)"/>
<ellipse cx="60" cy="44" rx="8" ry="12" transform="rotate(300 60 44)"/>
</g>
<circle cx="60" cy="44" r="6" fill="#e8b84b" stroke="#a97e1f" stroke-width="1"/>
</svg>

Before

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

-13
View File
@@ -1,13 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Frimaire">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<g stroke="#6f8794" stroke-width="2.5" stroke-linecap="round">
<line x1="60" y1="32" x2="60" y2="88"/>
<line x1="32" y1="60" x2="88" y2="60"/>
<line x1="40" y1="40" x2="80" y2="80"/>
<line x1="40" y1="80" x2="80" y2="40"/>
<path d="M60 32 L54 40 M60 32 L66 40"/>
<path d="M60 88 L54 80 M60 88 L66 80"/>
<path d="M32 60 L40 54 M32 60 L40 66"/>
<path d="M88 60 L80 54 M88 60 L80 66"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

-8
View File
@@ -1,8 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Fructidor">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<circle cx="50" cy="66" r="14" fill="#8a3b2b" stroke="#5c2318" stroke-width="1.5"/>
<path d="M50 52 C48 46 52 44 54 46" fill="none" stroke="#5c7a4a" stroke-width="2" stroke-linecap="round"/>
<path d="M52 46 C58 42 62 46 58 50 C54 50 52 48 52 46 Z" fill="#7a9a5a" stroke="#4f6b3d" stroke-width="1"/>
<path d="M74 74 C66 74 62 66 66 58 C74 58 78 66 74 74 Z" fill="#c9973b" stroke="#8a6b28" stroke-width="1.5"/>
<path d="M70 58 C68 52 72 50 74 52" fill="none" stroke="#5c7a4a" stroke-width="2" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

-7
View File
@@ -1,7 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Germinal">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<ellipse cx="60" cy="86" rx="7" ry="5" fill="#8a6b45" stroke="#5c4326" stroke-width="1.5"/>
<path d="M60 82 C58 68 60 56 60 44" fill="none" stroke="#5c7a4a" stroke-width="3" stroke-linecap="round"/>
<path d="M60 60 C50 56 46 48 48 40 C58 42 62 50 60 60 Z" fill="#7a9a5a" stroke="#4f6b3d" stroke-width="1.5"/>
<path d="M60 68 C70 64 74 56 72 48 C62 50 58 58 60 68 Z" fill="#7a9a5a" stroke="#4f6b3d" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

-16
View File
@@ -1,16 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Messidor">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<g stroke="#c9973b" stroke-width="2.5" stroke-linecap="round" fill="none">
<path d="M60 88 L48 42"/>
<path d="M60 88 L54 40"/>
<path d="M60 88 L60 38"/>
<path d="M60 88 L66 40"/>
<path d="M60 88 L72 42"/>
<path d="M48 42 q-2 -6 4 -8"/>
<path d="M72 42 q2 -6 -4 -8"/>
<path d="M54 40 q-2 -6 4 -8"/>
<path d="M66 40 q2 -6 -4 -8"/>
<path d="M60 38 q0 -6 0 -8"/>
</g>
<path d="M46 82 Q60 74 74 82 L74 90 Q60 84 46 90 Z" fill="#8a6b45" stroke="#5c4326" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

-14
View File
@@ -1,14 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Nivôse">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<g stroke="#5b6b7a" stroke-width="2.5" stroke-linecap="round">
<line x1="60" y1="28" x2="60" y2="92"/>
<line x1="28" y1="60" x2="92" y2="60"/>
<line x1="38" y1="38" x2="82" y2="82"/>
<line x1="38" y1="82" x2="82" y2="38"/>
<path d="M60 38 L54 44 M60 38 L66 44"/>
<path d="M60 82 L54 76 M60 82 L66 76"/>
<path d="M38 60 L44 54 M38 60 L44 66"/>
<path d="M82 60 L76 54 M82 60 L76 66"/>
</g>
<circle cx="60" cy="60" r="4" fill="#5b6b7a"/>
</svg>

Before

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

-10
View File
@@ -1,10 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Pluviôse">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<path d="M40 56 a12 12 0 0 1 4 -23.6 a15 15 0 0 1 29 -4 a13 13 0 0 1 8 26.6 Z"
fill="#dfe6ea" stroke="#5b6b7a" stroke-width="2"/>
<g fill="#5b7a94">
<path d="M46 76 q3 8 0 12 q-3 -4 0 -12 Z"/>
<path d="M60 80 q3 8 0 12 q-3 -4 0 -12 Z"/>
<path d="M74 76 q3 8 0 12 q-3 -4 0 -12 Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

-15
View File
@@ -1,15 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Prairial">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<g fill="none" stroke="#5c7a4a" stroke-width="3" stroke-linecap="round">
<path d="M36 90 C34 74 40 64 38 50"/>
<path d="M48 90 C48 72 54 64 50 48"/>
<path d="M60 90 C60 70 56 60 60 44"/>
<path d="M72 90 C72 72 66 64 70 48"/>
<path d="M84 90 C86 74 80 64 82 50"/>
</g>
<g fill="#c76b8a" stroke="#8a3b58" stroke-width="1">
<circle cx="60" cy="42" r="5"/>
<circle cx="70" cy="46" r="4"/>
<circle cx="50" cy="46" r="4"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

-14
View File
@@ -1,14 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Thermidor">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<circle cx="60" cy="60" r="16" fill="#e8b84b" stroke="#b5652b" stroke-width="2"/>
<g stroke="#b5652b" stroke-width="3" stroke-linecap="round">
<line x1="60" y1="28" x2="60" y2="38"/>
<line x1="60" y1="82" x2="60" y2="92"/>
<line x1="28" y1="60" x2="38" y2="60"/>
<line x1="82" y1="60" x2="92" y2="60"/>
<line x1="39" y1="39" x2="46" y2="46"/>
<line x1="74" y1="74" x2="81" y2="81"/>
<line x1="81" y1="39" x2="74" y2="46"/>
<line x1="46" y1="74" x2="39" y2="81"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

-16
View File
@@ -1,16 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Vendémiaire">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<path d="M60 30 C64 36 64 42 60 46" fill="none" stroke="#5c7a4a" stroke-width="2.5" stroke-linecap="round"/>
<path d="M60 34 C68 30 74 34 72 40 C66 42 61 39 60 34 Z" fill="#7a9a5a" stroke="#4f6b3d" stroke-width="1.5"/>
<g fill="#7a4a6b" stroke="#452a3a" stroke-width="1.2">
<circle cx="52" cy="56" r="6.5"/>
<circle cx="68" cy="56" r="6.5"/>
<circle cx="60" cy="52" r="6.5"/>
<circle cx="46" cy="66" r="6.5"/>
<circle cx="60" cy="64" r="6.5"/>
<circle cx="74" cy="66" r="6.5"/>
<circle cx="53" cy="76" r="6.5"/>
<circle cx="67" cy="76" r="6.5"/>
<circle cx="60" cy="86" r="6.5"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

-8
View File
@@ -1,8 +0,0 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Ventôse">
<circle cx="60" cy="60" r="54" fill="#f6efe0" stroke="#8a6b45" stroke-width="2"/>
<g fill="none" stroke="#7d8a76" stroke-width="3" stroke-linecap="round">
<path d="M26 46 C50 36 62 46 78 40"/>
<path d="M22 62 C52 50 66 64 94 54"/>
<path d="M30 78 C54 70 64 80 84 72"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 395 B

+724 -165
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
{
"vendemiaire": ["Raisin", "Safran", "Châtaigne", "Colchique", "Cheval", "Balsamine", "Carotte", "Amaranthe", "Panais", "Cuve", "Pomme de terre", "Immortelle", "Potiron", "Réséda", "Âne", "Belle de nuit", "Citrouille", "Sarrasin", "Tournesol", "Pressoir", "Chanvre", "Pêche", "Navet", "Amaryllis", "Bœuf", "Aubergine", "Piment", "Tomate", "Orge", "Tonneau"],
"brumaire": ["Pomme", "Céleri", "Poire", "Betterave", "Oie", "Héliotrope", "Figue", "Scorsonère", "Alisier", "Charrue", "Salsifis", "Mâcre", "Topinambour", "Endive", "Dindon", "Chervis", "Cresson", "Dentelaire", "Grenade", "Herse", "Bacchante", "Azerole", "Garance", "Orange", "Faisan", "Pistache", "Macjonc", "Coing", "Cormier", "Rouleau"],
"frimaire": ["Raiponce", "Turneps", "Chicorée", "Nèfle", "Cochon", "Mâche", "Chou-fleur", "Miel", "Genièvre", "Pioche", "Cire", "Raifort", "Cèdre", "Sapin", "Chevreuil", "Ajonc", "Cyprès", "Lierre", "Sabine", "Hoyau", "Érable à sucre", "Bruyère", "Roseau", "Oseille", "Grillon", "Pignon", "Liège", "Truffe", "Olive", "Pelle"],
"nivose": ["Tourbe", "Houille", "Bitume", "Soufre", "Chien", "Lave", "Terre végétale", "Fumier", "Salpêtre", "Fléau", "Granit", "Argile", "Ardoise", "Grès", "Lapin", "Silex", "Marne", "Pierre à chaux", "Marbre", "Van", "Pierre à plâtre", "Sel", "Fer", "Cuivre", "Chat", "Étain", "Plomb", "Zinc", "Mercure", "Crible"],
"pluviose": ["Lauréole", "Mousse", "Fragon", "Perce-neige", "Taureau", "Laurier-thym", "Amadouvier", "Mézéréon", "Peuplier", "Coignée", "Ellébore", "Brocoli", "Laurier", "Avelinier", "Vache", "Buis", "Lichen", "If", "Pulmonaire", "Serpette", "Thlaspi", "Thimelé", "Chiendent", "Trainasse", "Lièvre", "Guède", "Noisetier", "Cyclamen", "Chélidoine", "Traîneau"],
"ventose": ["Tussilage", "Cornouiller", "Violier", "Troène", "Bouc", "Asaret", "Alaterne", "Violette", "Marceau", "Bêche", "Narcisse", "Orme", "Fumeterre", "Vélar", "Chèvre", "Épinard", "Doronic", "Mouron", "Cerfeuil", "Cordeau", "Mandragore", "Persil", "Cochléaria", "Pâquerette", "Thon", "Pissenlit", "Sylve", "Capillaire", "Frêne", "Plantoir"],
"germinal": ["Primevère", "Platane", "Asperge", "Tulipe", "Poule", "Bette", "Bouleau", "Jonquille", "Aulne", "Couvoir", "Pervenche", "Charme", "Morille", "Hêtre", "Abeille", "Laitue", "Mélèze", "Ciguë", "Radis", "Ruche", "Gainier", "Romaine", "Marronnier", "Roquette", "Pigeon", "Lilas", "Anémone", "Pensée", "Myrtille", "Greffoir"],
"floreal": ["Rose", "Chêne", "Fougère", "Aubépine", "Rossignol", "Ancolie", "Muguet", "Champignon", "Hyacinthe", "Râteau", "Rhubarbe", "Sainfoin", "Bâton-d'or", "Chamérisier", "Ver à soie", "Consoude", "Pimprenelle", "Corbeille d'or", "Arroche", "Sarcloir", "Statice", "Fritillaire", "Bourrache", "Valériane", "Carpe", "Fusain", "Civette", "Buglosse", "Sénevé", "Houlette"],
"prairial": ["Luzerne", "Hémérocalle", "Trèfle", "Angélique", "Canard", "Mélisse", "Fromental", "Martagon", "Serpolet", "Faux", "Fraise", "Bétoine", "Pois", "Acacia", "Caille", "Œillet", "Sureau", "Pavot", "Tilleul", "Fourche", "Barbeau", "Camomille", "Chèvrefeuille", "Caille-lait", "Tanche", "Jasmin", "Verveine", "Thym", "Pivoine", "Chariot"],
"messidor": ["Seigle", "Avoine", "Oignon", "Véronique", "Mulet", "Romarin", "Concombre", "Échalote", "Absinthe", "Faucille", "Coriandre", "Artichaut", "Girofle", "Lavande", "Chamois", "Tabac", "Groseille", "Gesse", "Cerise", "Parc", "Menthe", "Cumin", "Haricot", "Orcanète", "Pintade", "Sauge", "Ail", "Vesce", "Blé", "Chalémie"],
"thermidor": ["Épeautre", "Bouillon-blanc", "Melon", "Ivraie", "Bélier", "Prêle", "Armoise", "Carthame", "Mûre", "Arrosoir", "Panic", "Salicorne", "Abricot", "Basilic", "Brebis", "Guimauve", "Lin", "Amande", "Gentiane", "Écluse", "Carline", "Câprier", "Lentille", "Aunée", "Loutre", "Myrte", "Colza", "Lupin", "Coton", "Moulin"],
"fructidor": ["Prune", "Millet", "Lycoperdon", "Escourgeon", "Saumon", "Tubéreuse", "Sucrion", "Apocyn", "Réglisse", "Échelle", "Pastèque", "Fenouil", "Épine-vinette", "Noix", "Truite", "Citron", "Cardère", "Nerprun", "Tagète", "Hotte", "Églantier", "Noisette", "Houblon", "Sorgho", "Écrevisse", "Bigarade", "Verge d'or", "Maïs", "Marron", "Panier"]
}
+71 -28
View File
@@ -4,16 +4,15 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title data-i18n="site.title">Calendrier républicain</title>
<link rel="icon" href="data:,">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;1,9..144,500&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
<link rel="preload" href="assets/fonts/old-standard-tt-400.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="assets/fonts/im-fell-english-sc.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header class="site-header">
<a class="brand" href="#/">
<span class="brand-mark">an</span>
<span class="cockade" aria-hidden="true"></span>
<span class="brand-title" data-i18n="site.title">Calendrier républicain</span>
</a>
<div class="header-actions">
@@ -29,6 +28,7 @@
<nav id="main-nav" class="main-nav" aria-hidden="true">
<a href="#/" class="nav-link" data-view="home" data-i18n="nav.home">Accueil</a>
<a href="#/calendar" class="nav-link" data-view="calendar" data-i18n="nav.calendar">Le calendrier complet</a>
<p class="nav-section-label" data-i18n="nav.about">À propos</p>
<a href="#/about/history" class="nav-link" data-view="history" data-i18n="nav.aboutHistory">Histoire du calendrier</a>
<a href="#/about/leap-years" class="nav-link" data-view="leap-years" data-i18n="nav.aboutLeapYears">Les années sextiles</a>
@@ -38,32 +38,45 @@
<main id="app">
<section id="view-home" class="view">
<h1 class="today-heading">
<span data-i18n="home.todayHeading">Nous sommes aujourd'hui le</span>
</h1>
<div class="tablet">
<header class="tablet-head">
<h1 class="today-heading" data-i18n="home.todayHeading">Nous sommes aujourd'hui le</h1>
<div class="ornament" aria-hidden="true"></div>
</header>
<div class="calendar-card">
<div class="month-illustration">
<img id="month-image" src="assets/months/vendemiaire.svg" alt="" width="140" height="140">
<p id="month-period" class="month-period"></p>
</div>
<div class="tablet-body">
<figure class="plate">
<div class="plate-frame">
<img id="month-image" src="assets/months/vendemiaire.jpg" alt="" width="527" height="713">
</div>
<figcaption>
<h2 id="month-name" class="month-name">—</h2>
<p id="month-period" class="month-period"></p>
</figcaption>
</figure>
<div class="date-display">
<p id="decade-day" class="decade-day">—</p>
<p id="republican-date" class="republican-date">—</p>
<p id="republican-year" class="republican-year">—</p>
<span id="sextile-badge" class="sextile-badge" hidden data-i18n="home.sextileBadge">Année sextile</span>
<p class="gregorian-equivalent">
<span data-i18n="home.gregorianEquivalent">Équivalent grégorien</span> :
<span id="gregorian-equivalent-value">—</span>
</p>
</div>
<img class="fasces" src="assets/fasces.svg" alt="" aria-hidden="true">
<div class="description-panel">
<h2 id="month-name" class="month-name">—</h2>
<p id="month-season-suffix" class="month-season-suffix"></p>
<p id="month-etymology" class="month-etymology"></p>
<p id="month-description" class="month-description"></p>
<div class="decree">
<div class="date-display">
<p id="decade-day" class="decade-day">—</p>
<p id="republican-date" class="republican-date">—</p>
<p id="republican-year" class="republican-year">—</p>
<span id="sextile-badge" class="sextile-badge" hidden data-i18n="home.sextileBadge">Année sextile</span>
<p class="gregorian-equivalent">
<span data-i18n="home.gregorianEquivalent">Équivalent grégorien</span> :
<span id="gregorian-equivalent-value">—</span>
</p>
</div>
<div class="ornament" aria-hidden="true"></div>
<div class="description-panel">
<p id="month-season-suffix" class="month-season-suffix"></p>
<p id="month-etymology" class="month-etymology"></p>
<p id="month-description" class="month-description"></p>
</div>
</div>
</div>
</div>
@@ -80,13 +93,43 @@
</form>
</section>
<section id="view-calendar" class="view" hidden>
<div class="poster">
<header class="poster-head">
<h1 data-i18n="calendar.title">Calendrier de la République française</h1>
<p class="poster-subtitle" data-i18n="calendar.subtitle">Les douze mois, les trois décades et les jours de l'année</p>
<div class="ornament" aria-hidden="true"></div>
</header>
<div id="poster-grid" class="poster-grid"></div>
<div id="poster-feasts" class="poster-feasts"></div>
</div>
</section>
<section id="view-about" class="view about-view" hidden>
<h1 id="about-title">—</h1>
<div id="about-content" class="about-content"></div>
</section>
</main>
<dialog id="month-dialog" class="month-dialog">
<form method="dialog">
<button class="dialog-close" aria-label="Fermer" data-i18n-aria-label="calendar.close">×</button>
</form>
<figure class="plate">
<div class="plate-frame"><img id="dialog-image" src="" alt="" width="527" height="713"></div>
<figcaption>
<h2 id="dialog-name" class="month-name"></h2>
<p id="dialog-period" class="month-period"></p>
</figcaption>
</figure>
<div class="dialog-text">
<p id="dialog-etymology" class="month-etymology"></p>
<p id="dialog-description" class="month-description"></p>
</div>
</dialog>
<footer class="site-footer">
<div class="ornament" aria-hidden="true"></div>
<p data-i18n="footer.text">Dates calculées selon la règle arithmétique perpétuelle proposée par Gilbert Romme (19 floréal an III / 8 mai 1795).</p>
</footer>
+223 -9
View File
@@ -19,10 +19,20 @@
const formError = document.getElementById('form-error');
const viewHome = document.getElementById('view-home');
const viewCalendar = document.getElementById('view-calendar');
const viewAbout = document.getElementById('view-about');
const aboutTitle = document.getElementById('about-title');
const aboutContent = document.getElementById('about-content');
const posterGrid = document.getElementById('poster-grid');
const posterFeasts = document.getElementById('poster-feasts');
const monthDialog = document.getElementById('month-dialog');
const dialogImage = document.getElementById('dialog-image');
const dialogName = document.getElementById('dialog-name');
const dialogPeriod = document.getElementById('dialog-period');
const dialogEtymology = document.getElementById('dialog-etymology');
const dialogDescription = document.getElementById('dialog-description');
const menuToggle = document.getElementById('menu-toggle');
const mainNav = document.getElementById('main-nav');
const navBackdrop = document.getElementById('nav-backdrop');
@@ -34,7 +44,24 @@
'about/complementary-days': 'complementaryDays',
};
// Season columns of the full calendar; `image` is the month plate shown in the arch,
// `focus` its vertical crop position so that the face stays in the arch.
const SEASONS = [
{ key: 'autumn', image: 'vendemiaire', focus: '23%', months: ['vendemiaire', 'brumaire', 'frimaire'] },
{ key: 'winter', image: 'nivose', focus: '18%', months: ['nivose', 'pluviose', 'ventose'] },
{ key: 'spring', image: 'floreal', focus: '18%', months: ['germinal', 'floreal', 'prairial'] },
{ key: 'summer', image: 'messidor', focus: '18%', months: ['messidor', 'thermidor', 'fructidor'] },
];
const DECADE_DAY_KEYS = [
'primidi', 'duodi', 'tridi', 'quartidi', 'quintidi',
'sextidi', 'septidi', 'octidi', 'nonidi', 'decadi',
];
const COMPLEMENTARY_DAY_KEYS = ['virtue', 'genius', 'labour', 'opinion', 'rewards', 'revolution'];
let lastRepublicanResult = null;
let dayNames = {};
function pad(value) {
return String(value).padStart(2, '0');
@@ -49,6 +76,13 @@
return `${weekdayLabel} ${pad(day)}/${pad(month)}/${year}`.trim();
}
function createElement(tag, className, text) {
const element = document.createElement(tag);
if (className) element.className = className;
if (text !== undefined) element.textContent = text;
return element;
}
function closeMenu() {
mainNav.classList.remove('open');
mainNav.setAttribute('aria-hidden', 'true');
@@ -77,7 +111,7 @@
const day = complementary.days[result.complementaryDayKey] || {};
decadeDay.textContent = complementary.title || '';
republicanDate.textContent = day.name || '';
monthImage.src = 'assets/months/complementary.svg';
monthImage.src = 'assets/complementary.svg';
monthImage.alt = day.name || '';
monthPeriod.textContent = '';
monthName.textContent = day.name || '';
@@ -89,7 +123,7 @@
const seasonSuffix = (I18N.months.seasonSuffix || {})[month.season] || '';
decadeDay.textContent = decadeDays[result.decadeDayKey] || '';
republicanDate.textContent = `${result.dayOfMonth} ${month.name || ''}`.trim();
monthImage.src = `assets/months/${result.monthKey}.svg`;
monthImage.src = `assets/months/${result.monthKey}.jpg`;
monthImage.alt = month.name || '';
monthPeriod.textContent = month.period || '';
monthName.textContent = month.name || '';
@@ -97,6 +131,167 @@
monthEtymology.textContent = month.etymology || '';
monthDescription.textContent = month.description || '';
}
markToday();
}
/** Highlights the displayed republican date inside the full calendar. */
function markToday() {
posterGrid.querySelectorAll('.today, .today-row, .is-current').forEach((element) => {
element.classList.remove('today', 'today-row', 'is-current');
});
posterFeasts.querySelectorAll('.today').forEach((element) => element.classList.remove('today'));
const result = lastRepublicanResult;
if (!result) return;
if (result.isComplementaryDay) {
const feast = posterFeasts.querySelector(`[data-feast="${result.complementaryDayKey}"]`);
if (feast) feast.classList.add('today');
return;
}
const cell = posterGrid.querySelector(
`td[data-month="${result.monthKey}"][data-day="${result.dayOfMonth}"]`
);
if (cell) {
cell.classList.add('today');
cell.parentElement.firstElementChild.classList.add('today-row');
}
const monthButton = posterGrid.querySelector(`.month-button[data-month="${result.monthKey}"]`);
if (monthButton) monthButton.classList.add('is-current');
}
function buildSeasonTable(season) {
const months = I18N.months.months || {};
const decadeDays = I18N.months.decadeDays || {};
const decades = I18N.t('calendar.decades', []);
const table = createElement('table', 'season-table');
const colgroup = table.appendChild(document.createElement('colgroup'));
colgroup.appendChild(createElement('col', 'col-day'));
season.months.forEach(() => colgroup.appendChild(document.createElement('col')));
const headRow = table.createTHead().insertRow();
headRow.appendChild(createElement('th', 'day-head', I18N.t('calendar.dayColumn')));
season.months.forEach((monthKey) => {
const th = createElement('th');
th.scope = 'col';
const button = createElement('button', 'month-button', (months[monthKey] || {}).name || monthKey);
button.type = 'button';
button.dataset.month = monthKey;
th.appendChild(button);
headRow.appendChild(th);
});
const body = table.createTBody();
for (let decade = 0; decade < 3; decade += 1) {
const decadeRow = body.insertRow();
decadeRow.className = 'decade-row';
const decadeHead = createElement('th', '', decades[decade] || '');
decadeHead.colSpan = season.months.length + 1;
decadeRow.appendChild(decadeHead);
for (let index = 0; index < 10; index += 1) {
const day = decade * 10 + index + 1;
const row = body.insertRow();
if (index === 9) row.className = 'decade-end';
const dayHead = createElement('th');
dayHead.scope = 'row';
dayHead.appendChild(createElement('span', 'day-number', String(day)));
const decadeDayName = decadeDays[DECADE_DAY_KEYS[index]] || '';
const abbreviation = createElement('abbr', '', `${decadeDayName.slice(0, 3)}.`);
abbreviation.title = decadeDayName;
dayHead.appendChild(abbreviation);
row.appendChild(dayHead);
season.months.forEach((monthKey) => {
const cell = createElement('td', '', (dayNames[monthKey] || [])[day - 1] || '');
cell.dataset.month = monthKey;
cell.dataset.day = String(day);
row.appendChild(cell);
});
}
}
return table;
}
function buildSeason(season) {
const section = createElement('section', 'season');
const arch = createElement('div', 'arch');
const image = createElement('img');
image.src = `assets/months/${season.image}.jpg`;
image.alt = '';
image.loading = 'lazy';
image.style.objectPosition = `50% ${season.focus}`;
arch.appendChild(image);
section.appendChild(arch);
section.appendChild(createElement('h2', 'season-title', I18N.t(`calendar.seasons.${season.key}`)));
section.appendChild(buildSeasonTable(season));
return section;
}
function buildPosterCenter() {
const center = createElement('aside', 'poster-center');
const medallion = createElement('div', 'medallion');
const inner = createElement('div', 'medallion-inner');
const fasces = createElement('img');
fasces.src = 'assets/fasces.svg';
fasces.alt = '';
inner.appendChild(fasces);
medallion.appendChild(inner);
center.appendChild(medallion);
const explanation = createElement('div', 'explanation');
explanation.appendChild(createElement('h2', '', I18N.t('calendar.explanationTitle')));
I18N.t('calendar.explanation', []).forEach((paragraph) => {
explanation.appendChild(createElement('p', '', paragraph));
});
center.appendChild(explanation);
return center;
}
function buildFeasts() {
const complementary = I18N.months.complementaryDays || { days: {} };
const decadeDays = I18N.months.decadeDays || {};
posterFeasts.replaceChildren();
COMPLEMENTARY_DAY_KEYS.forEach((key, index) => {
const feast = createElement('p', 'feast');
feast.dataset.feast = key;
feast.appendChild(createElement('span', 'feast-day', decadeDays[DECADE_DAY_KEYS[index]] || ''));
feast.appendChild(createElement('span', 'feast-name', (complementary.days[key] || {}).name || ''));
if (key === 'revolution') {
feast.appendChild(createElement('span', 'feast-note', I18N.t('calendar.sextileOnly')));
}
posterFeasts.appendChild(feast);
});
}
function renderCalendarView() {
posterGrid.replaceChildren(...SEASONS.map(buildSeason), buildPosterCenter());
buildFeasts();
markToday();
}
function openMonthDialog(monthKey) {
const month = (I18N.months.months || {})[monthKey];
if (!month) return;
dialogImage.src = `assets/months/${monthKey}.jpg`;
dialogImage.alt = month.name;
dialogName.textContent = month.name;
dialogPeriod.textContent = month.period || '';
dialogEtymology.textContent = month.etymology || '';
dialogDescription.textContent = month.description || '';
monthDialog.showModal();
}
async function loadDayNames() {
try {
const response = await fetch('data/day-names.json');
dayNames = await response.json();
} catch (error) {
dayNames = {};
}
}
function showFormError(messageKey) {
@@ -170,18 +365,23 @@
document.querySelectorAll('.nav-link').forEach((link) => link.classList.remove('active'));
if (hash in ABOUT_ROUTES) {
viewHome.hidden = true;
viewHome.hidden = true;
viewCalendar.hidden = true;
viewAbout.hidden = true;
let activeHref = '#/';
if (hash === 'calendar') {
viewCalendar.hidden = false;
activeHref = '#/calendar';
} else if (hash in ABOUT_ROUTES) {
viewAbout.hidden = false;
renderAboutView(ABOUT_ROUTES[hash]);
const activeLink = document.querySelector(`.nav-link[href="#/${hash}"]`);
if (activeLink) activeLink.classList.add('active');
activeHref = `#/${hash}`;
} else {
viewHome.hidden = false;
viewAbout.hidden = true;
const activeLink = document.querySelector('.nav-link[href="#/"]');
if (activeLink) activeLink.classList.add('active');
}
const activeLink = document.querySelector(`.nav-link[href="${activeHref}"]`);
if (activeLink) activeLink.classList.add('active');
}
function bindEvents() {
@@ -195,6 +395,18 @@
navBackdrop.addEventListener('click', closeMenu);
window.addEventListener('hashchange', route);
posterGrid.addEventListener('click', (event) => {
const button = event.target.closest('.month-button');
if (button) openMonthDialog(button.dataset.month);
});
// A click on the ::backdrop targets the dialog itself, outside its box.
monthDialog.addEventListener('click', (event) => {
const box = monthDialog.getBoundingClientRect();
const inside = event.clientX >= box.left && event.clientX <= box.right
&& event.clientY >= box.top && event.clientY <= box.bottom;
if (!inside) monthDialog.close();
});
convertForm.addEventListener('submit', handleConvertSubmit);
todayButton.addEventListener('click', () => {
loadToday();
@@ -207,6 +419,7 @@
I18N.onChange((lang) => {
langSwitch.textContent = lang.toUpperCase();
renderCalendarView();
if (lastRepublicanResult) {
renderRepublicanResult(lastRepublicanResult);
}
@@ -220,6 +433,7 @@
async function init() {
bindEvents();
const initialLang = I18N.detectInitialLang();
await loadDayNames();
await I18N.load(initialLang);
langSwitch.textContent = initialLang.toUpperCase();
route();
+6
View File
@@ -39,6 +39,12 @@ const I18N = (() => {
element.textContent = value;
}
});
document.querySelectorAll('[data-i18n-aria-label]').forEach((element) => {
const value = getByPath(common, element.getAttribute('data-i18n-aria-label'));
if (value !== undefined) {
element.setAttribute('aria-label', value);
}
});
}
async function fetchJson(url) {