aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/fonts/Inter/Inter.woff2bin0 -> 324864 bytes
-rw-r--r--src/fonts/Inter/LICENSE.txt94
-rw-r--r--src/fonts/Kanit/Kanit-Bold.woffbin0 -> 73508 bytes
-rw-r--r--src/fonts/Kanit/Kanit-Bold.woff2bin0 -> 50480 bytes
-rw-r--r--src/fonts/Kanit/Kanit-BoldItalic.woffbin0 -> 79120 bytes
-rw-r--r--src/fonts/Kanit/Kanit-BoldItalic.woff2bin0 -> 53888 bytes
-rw-r--r--src/fonts/Kanit/Kanit-Italic.woffbin0 -> 76092 bytes
-rw-r--r--src/fonts/Kanit/Kanit-Italic.woff2bin0 -> 51768 bytes
-rw-r--r--src/fonts/Kanit/Kanit-Light.woffbin0 -> 71864 bytes
-rw-r--r--src/fonts/Kanit/Kanit-Light.woff2bin0 -> 49256 bytes
-rw-r--r--src/fonts/Kanit/Kanit-LightItalic.woffbin0 -> 75752 bytes
-rw-r--r--src/fonts/Kanit/Kanit-LightItalic.woff2bin0 -> 51788 bytes
-rw-r--r--src/fonts/Kanit/Kanit-Medium.woffbin0 -> 73260 bytes
-rw-r--r--src/fonts/Kanit/Kanit-Medium.woff2bin0 -> 50200 bytes
-rw-r--r--src/fonts/Kanit/Kanit-MediumItalic.woffbin0 -> 75980 bytes
-rw-r--r--src/fonts/Kanit/Kanit-MediumItalic.woff2bin0 -> 51752 bytes
-rw-r--r--src/fonts/Kanit/Kanit-Regular.woffbin0 -> 72792 bytes
-rw-r--r--src/fonts/Kanit/Kanit-Regular.woff2bin0 -> 49980 bytes
-rw-r--r--src/fonts/Kanit/Kanit-SemiBold.woffbin0 -> 73472 bytes
-rw-r--r--src/fonts/Kanit/Kanit-SemiBold.woff2bin0 -> 50748 bytes
-rw-r--r--src/fonts/Kanit/Kanit-SemiBoldItalic.woffbin0 -> 76072 bytes
-rw-r--r--src/fonts/Kanit/Kanit-SemiBoldItalic.woff2bin0 -> 51896 bytes
-rw-r--r--src/fonts/Kanit/OFL.txt93
-rw-r--r--src/images/armand-philippot-logo.svg170
-rw-r--r--src/images/armand-philippot.jpgbin0 -> 28584 bytes
-rw-r--r--src/images/cc-by-sa.svg79
-rw-r--r--src/images/favicon/android-chrome-192x192.pngbin0 -> 5576 bytes
-rw-r--r--src/images/favicon/android-chrome-512x512.pngbin0 -> 14971 bytes
-rw-r--r--src/images/favicon/apple-touch-icon.pngbin0 -> 5270 bytes
-rw-r--r--src/images/favicon/browserconfig.xml9
-rw-r--r--src/images/favicon/favicon-16x16.pngbin0 -> 774 bytes
-rw-r--r--src/images/favicon/favicon-32x32.pngbin0 -> 1093 bytes
-rw-r--r--src/images/favicon/favicon.icobin0 -> 7406 bytes
-rw-r--r--src/images/favicon/mstile-144x144.pngbin0 -> 4236 bytes
-rw-r--r--src/images/favicon/mstile-150x150.pngbin0 -> 4258 bytes
-rw-r--r--src/images/favicon/mstile-310x150.pngbin0 -> 4708 bytes
-rw-r--r--src/images/favicon/mstile-310x310.pngbin0 -> 8861 bytes
-rw-r--r--src/images/favicon/mstile-70x70.pngbin0 -> 3034 bytes
-rw-r--r--src/images/favicon/safari-pinned-tab.svg25
-rw-r--r--src/images/favicon/site.webmanifest19
-rw-r--r--src/images/github.svg69
-rw-r--r--src/images/gitlab.svg93
-rw-r--r--src/js/app.js382
-rw-r--r--src/js/config/projects.js224
-rw-r--r--src/js/i18n/i18n.js42
-rw-r--r--src/js/i18n/locales/en.js36
-rw-r--r--src/js/i18n/locales/fr.js36
-rw-r--r--src/js/utilities/animations.js45
-rw-r--r--src/js/utilities/helpers.js19
-rw-r--r--src/scss/abstracts/_functions.scss4
-rw-r--r--src/scss/abstracts/_mixins.scss2
-rw-r--r--src/scss/abstracts/_placeholders.scss32
-rw-r--r--src/scss/abstracts/_variables.scss103
-rw-r--r--src/scss/abstracts/functions/_convert.scss16
-rw-r--r--src/scss/abstracts/functions/_css-vars.scss8
-rw-r--r--src/scss/abstracts/functions/_encode.scss14
-rw-r--r--src/scss/abstracts/functions/_str-replace.scss20
-rw-r--r--src/scss/abstracts/mixins/_css-vars.scss20
-rw-r--r--src/scss/abstracts/mixins/_media-queries.scss81
-rw-r--r--src/scss/base/_animations.scss89
-rw-r--r--src/scss/base/_base.scss3
-rw-r--r--src/scss/base/_colors.scss21
-rw-r--r--src/scss/base/_fonts.scss115
-rw-r--r--src/scss/base/_helpers.scss44
-rw-r--r--src/scss/base/_spacings.scss24
-rw-r--r--src/scss/base/_typography.scss48
-rw-r--r--src/scss/components/_buttons.scss21
-rw-r--r--src/scss/layout/_footer.scss40
-rw-r--r--src/scss/layout/_grid.scss43
-rw-r--r--src/scss/layout/_header.scss143
-rw-r--r--src/scss/layout/_main.scss138
-rw-r--r--src/scss/layout/_nav.scss70
-rw-r--r--src/scss/layout/_toolbar.scss34
-rw-r--r--src/scss/style.scss40
74 files changed, 2608 insertions, 0 deletions
diff --git a/src/fonts/Inter/Inter.woff2 b/src/fonts/Inter/Inter.woff2
new file mode 100644
index 0000000..365eedc
--- /dev/null
+++ b/src/fonts/Inter/Inter.woff2
Binary files differ
diff --git a/src/fonts/Inter/LICENSE.txt b/src/fonts/Inter/LICENSE.txt
new file mode 100644
index 0000000..ff80f8c
--- /dev/null
+++ b/src/fonts/Inter/LICENSE.txt
@@ -0,0 +1,94 @@
+Copyright (c) 2016-2020 The Inter Project Authors.
+"Inter" is trademark of Rasmus Andersson.
+https://github.com/rsms/inter
+
+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 AND 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.
diff --git a/src/fonts/Kanit/Kanit-Bold.woff b/src/fonts/Kanit/Kanit-Bold.woff
new file mode 100644
index 0000000..7602cee
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-Bold.woff
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-Bold.woff2 b/src/fonts/Kanit/Kanit-Bold.woff2
new file mode 100644
index 0000000..8494234
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-Bold.woff2
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-BoldItalic.woff b/src/fonts/Kanit/Kanit-BoldItalic.woff
new file mode 100644
index 0000000..af36319
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-BoldItalic.woff
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-BoldItalic.woff2 b/src/fonts/Kanit/Kanit-BoldItalic.woff2
new file mode 100644
index 0000000..61f04ef
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-BoldItalic.woff2
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-Italic.woff b/src/fonts/Kanit/Kanit-Italic.woff
new file mode 100644
index 0000000..462f295
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-Italic.woff
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-Italic.woff2 b/src/fonts/Kanit/Kanit-Italic.woff2
new file mode 100644
index 0000000..1723709
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-Italic.woff2
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-Light.woff b/src/fonts/Kanit/Kanit-Light.woff
new file mode 100644
index 0000000..872ce1b
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-Light.woff
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-Light.woff2 b/src/fonts/Kanit/Kanit-Light.woff2
new file mode 100644
index 0000000..6b35eda
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-Light.woff2
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-LightItalic.woff b/src/fonts/Kanit/Kanit-LightItalic.woff
new file mode 100644
index 0000000..b81b7b3
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-LightItalic.woff
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-LightItalic.woff2 b/src/fonts/Kanit/Kanit-LightItalic.woff2
new file mode 100644
index 0000000..f933a2c
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-LightItalic.woff2
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-Medium.woff b/src/fonts/Kanit/Kanit-Medium.woff
new file mode 100644
index 0000000..d5b3510
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-Medium.woff
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-Medium.woff2 b/src/fonts/Kanit/Kanit-Medium.woff2
new file mode 100644
index 0000000..a012e98
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-Medium.woff2
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-MediumItalic.woff b/src/fonts/Kanit/Kanit-MediumItalic.woff
new file mode 100644
index 0000000..6859f58
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-MediumItalic.woff
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-MediumItalic.woff2 b/src/fonts/Kanit/Kanit-MediumItalic.woff2
new file mode 100644
index 0000000..8af9298
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-MediumItalic.woff2
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-Regular.woff b/src/fonts/Kanit/Kanit-Regular.woff
new file mode 100644
index 0000000..eec0b42
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-Regular.woff
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-Regular.woff2 b/src/fonts/Kanit/Kanit-Regular.woff2
new file mode 100644
index 0000000..9b925bf
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-Regular.woff2
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-SemiBold.woff b/src/fonts/Kanit/Kanit-SemiBold.woff
new file mode 100644
index 0000000..dacdb5b
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-SemiBold.woff
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-SemiBold.woff2 b/src/fonts/Kanit/Kanit-SemiBold.woff2
new file mode 100644
index 0000000..52d4527
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-SemiBold.woff2
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-SemiBoldItalic.woff b/src/fonts/Kanit/Kanit-SemiBoldItalic.woff
new file mode 100644
index 0000000..ebf0137
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-SemiBoldItalic.woff
Binary files differ
diff --git a/src/fonts/Kanit/Kanit-SemiBoldItalic.woff2 b/src/fonts/Kanit/Kanit-SemiBoldItalic.woff2
new file mode 100644
index 0000000..300d77a
--- /dev/null
+++ b/src/fonts/Kanit/Kanit-SemiBoldItalic.woff2
Binary files differ
diff --git a/src/fonts/Kanit/OFL.txt b/src/fonts/Kanit/OFL.txt
new file mode 100644
index 0000000..0f48ea4
--- /dev/null
+++ b/src/fonts/Kanit/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The Kanit Project Authors (https://github.com/cadsondemak/kanit)
+
+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.
diff --git a/src/images/armand-philippot-logo.svg b/src/images/armand-philippot-logo.svg
new file mode 100644
index 0000000..17a245e
--- /dev/null
+++ b/src/images/armand-philippot-logo.svg
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="92.604164mm"
+ height="92.604164mm"
+ viewBox="0 0 92.604164 92.604164"
+ version="1.1"
+ id="svg8"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
+ sodipodi:docname="armand-philippot-logo.svg">
+ <defs
+ id="defs2">
+ <rect
+ x="50.243404"
+ y="24.854818"
+ width="80.992653"
+ height="65.698357"
+ id="rect1471-2" />
+ <rect
+ x="50.243404"
+ y="24.854818"
+ width="80.992653"
+ height="65.698357"
+ id="rect1666" />
+ <rect
+ x="50.243404"
+ y="24.854818"
+ width="80.992653"
+ height="65.698357"
+ id="rect1471-2-3" />
+ <rect
+ x="50.243404"
+ y="24.854818"
+ width="80.992653"
+ height="65.698357"
+ id="rect869" />
+ <rect
+ x="50.243404"
+ y="24.854818"
+ width="80.992653"
+ height="65.698357"
+ id="rect1471-2-3-6" />
+ <rect
+ x="50.243404"
+ y="24.854818"
+ width="80.992653"
+ height="65.698357"
+ id="rect928" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.979899"
+ inkscape:cx="62.374309"
+ inkscape:cy="220.65882"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer6"
+ inkscape:document-rotation="0"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ inkscape:window-x="0"
+ inkscape:window-y="33"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="BG"
+ style="display:inline"
+ transform="translate(-38.78392,-94.321098)">
+ <rect
+ style="fill:#386394;fill-opacity:1;stroke-width:0.767807"
+ id="rect1078"
+ width="92.604164"
+ height="92.604164"
+ x="38.78392"
+ y="94.321098" />
+ </g>
+ <g
+ inkscape:label="PrimaryColor"
+ inkscape:groupmode="layer"
+ id="layer1"
+ style="display:inline"
+ transform="translate(-38.78392,-94.321098)">
+ <path
+ id="rect1424-4-2"
+ style="fill:#194776;fill-opacity:1;stroke:none;stroke-width:2.28419;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 131.38808,95.225357 39.688622,186.92526 h 91.699458 z"
+ sodipodi:nodetypes="cccc" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="PrimaryColor-Dark"
+ style="display:inline"
+ transform="translate(-38.78392,-94.321098)">
+ <path
+ id="rect1424-0"
+ style="display:inline;fill:#113d69;fill-opacity:1;stroke:none;stroke-width:2.2842;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 38.78392,94.321098 h 92.60403 L 38.78392,186.92514 Z"
+ sodipodi:nodetypes="cccc" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer7"
+ inkscape:label="Shadow"
+ transform="translate(-38.78392,-94.321098)">
+ <g
+ aria-label="AP"
+ transform="matrix(1.0764391,0,0,1.0764391,-5.5904334,83.891018)"
+ id="text1469-5-2"
+ style="font-style:normal;font-weight:normal;font-size:50.8px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect1471-2-3);display:inline;fill:#1a2f47;fill-opacity:1;stroke:none">
+ <path
+ d="m 51.514141,69.801562 12.547599,-32.7152 h 10.1092 l 12.5476,32.7152 h -9.9568 l -2.7432,-6.9596 h -9.8552 l -2.6924,6.9596 z m 13.919199,-13.8176 h 7.3152 l -3.6576,-9.6012 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:50.8px;font-family:Kanit;-inkscape-font-specification:'Kanit Bold';fill:#1a2f47;fill-opacity:1"
+ id="path957-9" />
+ <path
+ d="m 90.274445,69.801562 v -32.7152 h 16.865595 q 4.0132,0 6.604,1.6256 2.6416,1.5748 3.9116,4.318 1.27,2.7432 1.27,6.096 0,3.4544 -1.4732,6.1468 -1.4732,2.6924 -4.1656,4.2164 -2.6924,1.524 -6.35,1.524 h -7.111995 v 8.7884 z m 9.5504,-16.1036 h 4.927605 q 2.23519,0 3.30199,-1.27 1.1176,-1.27 1.1176,-3.302 0,-2.1844 -1.016,-3.4544 -1.016,-1.27 -3.14959,-1.27 h -5.181605 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:50.8px;font-family:Kanit;-inkscape-font-specification:'Kanit Bold';fill:#1a2f47;fill-opacity:1"
+ id="path959-1" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="AP"
+ style="display:inline"
+ transform="translate(-38.78392,-94.321098)">
+ <g
+ aria-label="AP"
+ transform="matrix(1.0764391,0,0,1.0764391,-7.7056069,82.297001)"
+ id="text1469-5"
+ style="font-style:normal;font-weight:normal;font-size:50.8px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect1471-2-3);fill:#f3f7fc;fill-opacity:1;stroke:none">
+ <path
+ d="m 51.514141,69.801562 12.547599,-32.7152 h 10.1092 l 12.5476,32.7152 h -9.9568 l -2.7432,-6.9596 h -9.8552 l -2.6924,6.9596 z m 13.919199,-13.8176 h 7.3152 l -3.6576,-9.6012 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:50.8px;font-family:Kanit;-inkscape-font-specification:'Kanit Bold';fill:#f3f7fc;fill-opacity:1"
+ id="path957" />
+ <path
+ d="m 90.274445,69.801562 v -32.7152 h 16.865595 q 4.0132,0 6.604,1.6256 2.6416,1.5748 3.9116,4.318 1.27,2.7432 1.27,6.096 0,3.4544 -1.4732,6.1468 -1.4732,2.6924 -4.1656,4.2164 -2.6924,1.524 -6.35,1.524 h -7.111995 v 8.7884 z m 9.5504,-16.1036 h 4.927605 q 2.23519,0 3.30199,-1.27 1.1176,-1.27 1.1176,-3.302 0,-2.1844 -1.016,-3.4544 -1.016,-1.27 -3.14959,-1.27 h -5.181605 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:50.8px;font-family:Kanit;-inkscape-font-specification:'Kanit Bold';fill:#f3f7fc;fill-opacity:1"
+ id="path959" />
+ </g>
+ </g>
+</svg>
diff --git a/src/images/armand-philippot.jpg b/src/images/armand-philippot.jpg
new file mode 100644
index 0000000..2c8ef50
--- /dev/null
+++ b/src/images/armand-philippot.jpg
Binary files differ
diff --git a/src/images/cc-by-sa.svg b/src/images/cc-by-sa.svg
new file mode 100644
index 0000000..b1fc892
--- /dev/null
+++ b/src/images/cc-by-sa.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="211.99811"
+ height="63.999996"
+ viewBox="0 0 211.99811 63.999996"
+ version="1.1"
+ id="svg8"
+ inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
+ sodipodi:docname="cc-by-sa.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.4"
+ inkscape:cx="62.142857"
+ inkscape:cy="78.571429"
+ inkscape:document-units="px"
+ inkscape:current-layer="svg8"
+ inkscape:document-rotation="0"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ inkscape:window-x="0"
+ inkscape:window-y="33"
+ inkscape:window-maximized="1"
+ inkscape:pagecheckerboard="0"
+ units="px" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <path
+ d="m 175.53911,15.829498 c 0,-3.008 1.485,-4.514 4.458,-4.514 2.973,0 4.457,1.504 4.457,4.514 0,2.971 -1.486,4.457 -4.457,4.457 -2.971,0 -4.458,-1.486 -4.458,-4.457 z"
+ id="path839" />
+ <path
+ d="m 188.62611,24.057498 v 13.085 h -3.656 v 15.542 h -9.944 v -15.541 h -3.656 v -13.086 c 0,-0.572 0.2,-1.057 0.599,-1.457 0.401,-0.399 0.887,-0.6 1.457,-0.6 h 13.144 c 0.533,0 1.01,0.2 1.428,0.6 0.417,0.4 0.628,0.886 0.628,1.457 z"
+ id="path837" />
+ <path
+ id="path4-3"
+ d="m 179.94147,-1.9073486e-6 c -8.839,0 -16.34167,3.0848125073486 -22.51367,9.2578125073486 -6.285,6.4000004 -9.42969,13.9811874 -9.42969,22.7421874 0,8.762 3.14469,16.284312 9.42969,22.570312 6.361,6.286 13.86467,9.429688 22.51367,9.429688 8.799,0 16.43611,-3.181922 22.91211,-9.544922 6.096,-5.98 9.14453,-13.464078 9.14453,-22.455078 0,-8.952 -3.10646,-16.532188 -9.31446,-22.7421874 -6.172,-6.172 -13.75418,-9.2578125073486 -22.74218,-9.2578125073486 z M 180.05475,5.7714825 c 7.238,0 13.40967,2.55225 18.51367,7.6562495 5.103,5.106 7.65625,11.294313 7.65625,18.570313 0,7.391 -2.51397,13.50575 -7.54297,18.34375 -5.295,5.221 -11.50591,7.828125 -18.6289,7.828125 -7.162,0 -13.33268,-2.589484 -18.51368,-7.771484 -5.18,-5.178001 -7.76953,-11.310485 -7.76953,-18.396485 0,-7.047 2.60813,-13.238266 7.82813,-18.572265 5.029,-5.1040004 11.18103,-7.6582035 18.45703,-7.6582035 z" />
+ <path
+ d="m 91.998554,27.114498 c 0.609,-3.924 2.189,-6.962 4.742,-9.114 2.552,-2.152 5.655996,-3.228 9.313996,-3.228 5.027,0 9.029,1.62 12,4.856 2.971,3.238 4.457,7.391 4.457,12.457 0,4.915 -1.543,9 -4.627,12.256 -3.088,3.256 -7.086,4.886 -12.002,4.886 -3.619,0 -6.742996,-1.085 -9.370996,-3.257 -2.629,-2.172 -4.209,-5.257 -4.743,-9.257 h 8.059 c 0.189996,3.886 2.532996,5.829 7.028996,5.829 2.246,0 4.057,-0.972 5.428,-2.914 1.373,-1.942 2.059,-4.534 2.059,-7.771 0,-3.391 -0.629,-5.971 -1.885,-7.743 -1.258,-1.771 -3.066,-2.657 -5.43,-2.657 -4.268,0 -6.667,1.885 -7.199996,5.656 h 2.342996 l -6.341996,6.343 -6.343,-6.343 z"
+ id="path837-7" />
+ <path
+ id="path4-5"
+ d="m 105.94241,-1.8610229e-6 c -8.799996,0 -16.304676,3.1054062610229 -22.513666,9.3164061610229 -6.285,6.3999997 -9.42969,13.9625467 -9.42969,22.6855467 0,8.763 3.14469,16.28336 9.42969,22.568359 6.361,6.286001 13.86467,9.429688 22.513666,9.429688 8.836,0 16.47511,-3.162328 22.91211,-9.486328 6.096,-6.057 9.14453,-13.559672 9.14453,-22.513672 0,-8.952 -3.10646,-16.513547 -9.31446,-22.6855468 -6.211,-6.21 -13.79118,-9.3144530610229 -22.74218,-9.3144530610229 z M 106.05569,5.7714825 c 7.275,0 13.44667,2.5698437 18.51367,7.7148435 5.103,5.028 7.65625,11.200672 7.65625,18.513672 0,7.353 -2.51397,13.46775 -7.54297,18.34375 -5.295,5.219 -11.50591,7.828125 -18.6289,7.828125 -7.161996,0 -13.332676,-2.589484 -18.513676,-7.771484 -5.18,-5.143 -7.76953,-11.275391 -7.76953,-18.400391 0,-7.046 2.60813,-13.217672 7.82813,-18.513672 5.029,-5.1429998 11.18103,-7.7148435 18.457026,-7.7148435 z" />
+ <path
+ id="path4"
+ d="M 31.942383,5.9265138e-7 C 23.066111,5.9265138e-7 15.579851,3.1065496 9.484666,9.3147376 6.399571,12.400832 4.046856,15.896269 2.427808,19.801386 0.80876,23.706506 0,27.771846 0,32.000976 c 0,4.26713 0.800415,8.32413 2.400463,12.17225 1.600051,3.84811 3.933123,7.30532 7.000216,10.37141 3.067093,3.06609 6.534587,5.40951 10.400708,7.02756 3.867116,1.62105 7.914819,2.4278 12.142946,2.4278 4.22813,0 8.32441,-0.8171 12.28553,-2.45515 3.96313,-1.63805 7.50614,-4.00301 10.62923,-7.0881 3.0081,-2.93309 5.28529,-6.31477 6.82834,-10.14289 1.54104,-3.82712 2.31257,-7.93174 2.31257,-12.31288 0,-4.34313 -0.78277,-8.44771 -2.34382,-12.31483 C 60.094133,15.82003 57.808593,12.380471 54.800503,9.3713796 48.515313,3.1241896 40.893653,5.9265136e-7 31.942383,5.9265138e-7 Z M 32.057623,5.7716626 c 7.23822,0 13.42863,2.571923 18.57478,7.7150794 2.47408,2.478074 4.35948,5.297144 5.65252,8.459244 1.29504,3.16209 1.94342,6.51384 1.94342,10.05694 0,7.35423 -2.49445,13.46816 -7.4846,18.34432 -2.59208,2.51407 -5.49406,4.43661 -8.71316,5.77166 -3.2231,1.33404 -6.54486,1.9981 -9.97296,1.9981 -3.467107,0 -6.782568,-0.65672 -9.943661,-1.97076 -3.164098,-1.31604 -5.999858,-3.21894 -8.513933,-5.71502 -2.515077,-2.49507 -4.447918,-5.33279 -5.800959,-8.51588 -1.354042,-3.1791 -2.029358,-6.48331 -2.029358,-9.91242 0,-3.4671 0.675316,-6.79186 2.029358,-9.97295 1.352043,-3.1811 3.285882,-6.046798 5.800959,-8.599875 4.991151,-5.1041594 11.14337,-7.6584384 18.457594,-7.6584384 z" />
+ <path
+ d="m 50.114533,26.687816 -4.22913,2.22907 c -0.45702,-0.95103 -1.02003,-1.61905 -1.68605,-2.00006 -0.66802,-0.38001 -1.30704,-0.57102 -1.91406,-0.57102 -2.85709,0 -4.28713,1.88506 -4.28713,5.65717 0,1.71406 0.363,3.0841 1.08603,4.11313 0.72302,1.02903 1.78906,1.54405 3.2011,1.54405 1.86506,0 3.1801,-0.91503 3.94112,-2.74309 l 4.00012,2.00007 c -0.87502,1.56304 -2.05706,2.79108 -3.54111,3.68611 -1.48604,0.89602 -3.10509,1.34304 -4.85715,1.34304 -2.89608,0 -5.20915,-0.87503 -6.94121,-2.62908 -1.73605,-1.75205 -2.60207,-4.19013 -2.60207,-7.31323 0,-3.04809 0.88502,-5.46616 2.65808,-7.25722 1.77005,-1.79005 4.00812,-2.68608 6.7132,-2.68608 3.96212,-0.002 6.78321,1.54105 8.45826,4.62714 z"
+ id="path846" />
+ <path
+ d="m 31.656963,26.687816 -4.287128,2.22907 c -0.458013,-0.95103 -1.019029,-1.61905 -1.685048,-2.00006 -0.667024,-0.38001 -1.286042,-0.57102 -1.858057,-0.57102 -2.856087,0 -4.28613,1.88506 -4.28613,5.65717 0,1.71406 0.362014,3.0841 1.085029,4.11313 0.724025,1.02903 1.791056,1.54405 3.201101,1.54405 1.867057,0 3.181095,-0.91503 3.944118,-2.74309 l 3.942125,2.00007 c -0.83803,1.56304 -2.000065,2.79108 -3.486111,3.68611 -1.484043,0.89602 -3.123093,1.34304 -4.914149,1.34304 -2.857088,0 -5.163158,-0.87503 -6.915212,-2.62908 -1.752053,-1.75205 -2.62808,-4.19013 -2.62808,-7.31323 0,-3.04809 0.886028,-5.46616 2.657081,-7.25722 1.771054,-1.79005 4.009125,-2.68608 6.715205,-2.68608 3.963122,-0.002 6.800209,1.54105 8.515256,4.62714 z"
+ id="path844" />
+</svg>
diff --git a/src/images/favicon/android-chrome-192x192.png b/src/images/favicon/android-chrome-192x192.png
new file mode 100644
index 0000000..2def6f5
--- /dev/null
+++ b/src/images/favicon/android-chrome-192x192.png
Binary files differ
diff --git a/src/images/favicon/android-chrome-512x512.png b/src/images/favicon/android-chrome-512x512.png
new file mode 100644
index 0000000..514e060
--- /dev/null
+++ b/src/images/favicon/android-chrome-512x512.png
Binary files differ
diff --git a/src/images/favicon/apple-touch-icon.png b/src/images/favicon/apple-touch-icon.png
new file mode 100644
index 0000000..e4b7ae0
--- /dev/null
+++ b/src/images/favicon/apple-touch-icon.png
Binary files differ
diff --git a/src/images/favicon/browserconfig.xml b/src/images/favicon/browserconfig.xml
new file mode 100644
index 0000000..f9c2e67
--- /dev/null
+++ b/src/images/favicon/browserconfig.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+ <msapplication>
+ <tile>
+ <square150x150logo src="/mstile-150x150.png"/>
+ <TileColor>#2b5797</TileColor>
+ </tile>
+ </msapplication>
+</browserconfig>
diff --git a/src/images/favicon/favicon-16x16.png b/src/images/favicon/favicon-16x16.png
new file mode 100644
index 0000000..525a1b3
--- /dev/null
+++ b/src/images/favicon/favicon-16x16.png
Binary files differ
diff --git a/src/images/favicon/favicon-32x32.png b/src/images/favicon/favicon-32x32.png
new file mode 100644
index 0000000..c4b80ad
--- /dev/null
+++ b/src/images/favicon/favicon-32x32.png
Binary files differ
diff --git a/src/images/favicon/favicon.ico b/src/images/favicon/favicon.ico
new file mode 100644
index 0000000..d1d41a8
--- /dev/null
+++ b/src/images/favicon/favicon.ico
Binary files differ
diff --git a/src/images/favicon/mstile-144x144.png b/src/images/favicon/mstile-144x144.png
new file mode 100644
index 0000000..f68a989
--- /dev/null
+++ b/src/images/favicon/mstile-144x144.png
Binary files differ
diff --git a/src/images/favicon/mstile-150x150.png b/src/images/favicon/mstile-150x150.png
new file mode 100644
index 0000000..b36424a
--- /dev/null
+++ b/src/images/favicon/mstile-150x150.png
Binary files differ
diff --git a/src/images/favicon/mstile-310x150.png b/src/images/favicon/mstile-310x150.png
new file mode 100644
index 0000000..f395692
--- /dev/null
+++ b/src/images/favicon/mstile-310x150.png
Binary files differ
diff --git a/src/images/favicon/mstile-310x310.png b/src/images/favicon/mstile-310x310.png
new file mode 100644
index 0000000..3c7fd66
--- /dev/null
+++ b/src/images/favicon/mstile-310x310.png
Binary files differ
diff --git a/src/images/favicon/mstile-70x70.png b/src/images/favicon/mstile-70x70.png
new file mode 100644
index 0000000..12d226b
--- /dev/null
+++ b/src/images/favicon/mstile-70x70.png
Binary files differ
diff --git a/src/images/favicon/safari-pinned-tab.svg b/src/images/favicon/safari-pinned-tab.svg
new file mode 100644
index 0000000..2354f78
--- /dev/null
+++ b/src/images/favicon/safari-pinned-tab.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+</metadata>
+<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M0 3500 l0 -3500 3500 0 3500 0 0 3500 0 3500 -3500 0 -3500 0 0
+-3500z m3032 60 c279 -729 508 -1326 508 -1327 0 -2 -182 -3 -404 -3 l-405 0
+-112 285 -113 285 -400 -2 -399 -3 -72 -185 c-40 -102 -89 -229 -109 -282
+l-38 -98 -405 0 c-320 0 -404 3 -400 13 24 64 993 2590 1003 2615 l13 32 412
+-2 413 -3 508 -1325z m2468 1298 c123 -34 193 -65 285 -129 178 -123 285 -288
+347 -536 19 -75 22 -115 22 -278 0 -174 -2 -198 -26 -287 -78 -287 -279 -514
+-543 -614 -153 -58 -219 -65 -612 -71 l-363 -6 0 -353 0 -354 -390 0 -390 0 0
+1330 0 1331 788 -3 787 -4 95 -26z"/>
+<path d="M1960 3748 c-80 -209 -146 -384 -148 -389 -2 -5 124 -9 293 -9 285 0
+297 1 292 19 -7 29 -280 745 -286 753 -3 4 -71 -164 -151 -374z"/>
+<path d="M4610 3919 l0 -381 253 4 c296 5 321 11 403 93 74 73 97 140 98 275
+1 226 -85 351 -264 379 -36 6 -161 11 -277 11 l-213 0 0 -381z"/>
+</g>
+</svg>
diff --git a/src/images/favicon/site.webmanifest b/src/images/favicon/site.webmanifest
new file mode 100644
index 0000000..31df508
--- /dev/null
+++ b/src/images/favicon/site.webmanifest
@@ -0,0 +1,19 @@
+{
+ "name": "Armand Philippot",
+ "short_name": "AP",
+ "icons": [
+ {
+ "src": "/wp-content/themes/apcom/assets/images/favicon/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/wp-content/themes/apcom/assets/images/favicon/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#194476",
+ "background_color": "#194476",
+ "display": "standalone"
+}
diff --git a/src/images/github.svg b/src/images/github.svg
new file mode 100644
index 0000000..a39f6d2
--- /dev/null
+++ b/src/images/github.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="100"
+ height="100"
+ viewBox="0 0 100 100"
+ version="1.1"
+ id="svg8"
+ inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+ sodipodi:docname="github.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="4.6360027"
+ inkscape:cx="58.239828"
+ inkscape:cy="63.632405"
+ inkscape:document-units="px"
+ inkscape:current-layer="svg8"
+ inkscape:document-rotation="0"
+ showgrid="false"
+ units="px"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ inkscape:window-x="0"
+ inkscape:window-y="33"
+ inkscape:window-maximized="1"
+ inkscape:pagecheckerboard="0" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <rect
+ style="fill: #1b1817;
+ fill-opacity: 1;
+ opacity: 1;
+ stroke-width: 6.55748;"
+ id="rect832"
+ width="100"
+ height="100"
+ x="0"
+ y="0" />
+ <path
+ d="M 50.003222,15.863891 C 69.330089,15.863891 85,31.533801 85,50.864965 85,66.328607 74.972373,79.445952 61.064334,84.076237 c -1.751127,0.32014 -2.389268,-0.76061 -2.389268,-1.68882 0,-0.829368 0.03008,-3.031707 0.04727,-5.951686 9.735412,2.114245 11.789496,-4.692594 11.789496,-4.692594 1.59213,-4.043709 3.88686,-5.120169 3.88686,-5.120169 3.177814,-2.17011 -0.240646,-2.127137 -0.240646,-2.127137 -3.512998,0.247092 -5.360815,3.607538 -5.360815,3.607538 -3.12195,5.347924 -8.192701,3.803064 -10.186623,2.907088 -0.317995,-2.260352 -1.220417,-3.803063 -2.221677,-4.677553 7.771571,-0.883084 15.942786,-3.88686 15.942786,-17.298566 0,-3.820253 -1.364376,-6.944351 -3.603241,-9.391633 0.360969,-0.885233 1.562049,-4.443353 -0.341632,-9.262715 0,0 -2.939317,-0.941097 -9.625832,3.588201 -2.791062,-0.777802 -5.786243,-1.164554 -8.762086,-1.177446 -2.971546,0.01289 -5.966727,0.399644 -8.762085,1.177446 -6.68222,-4.529298 -9.615089,-3.588201 -9.615089,-3.588201 -1.910127,4.819362 -0.709046,8.377482 -0.348078,9.262715 -2.243163,2.447282 -3.598944,5.57138 -3.598944,9.391633 0,13.446084 8.184106,16.404739 15.98146,17.270634 -1.256945,1.080757 -2.376378,3.216489 -2.376378,6.482396 0,4.677553 0.04297,8.452684 0.04297,9.600049 0,0.9368 -0.629547,2.02615 -2.406458,1.68452 C 25.01903,79.43306 15,66.324309 15,50.864965 15,31.533801 30.672057,15.863891 50.003221,15.863891"
+ style="fill: #fff;
+ fill-opacity: 1;
+ fill-rule: evenodd;
+ stroke: none;
+ stroke-width: 0.999999;"
+ id="path198" />
+</svg>
diff --git a/src/images/gitlab.svg b/src/images/gitlab.svg
new file mode 100644
index 0000000..5a0ce62
--- /dev/null
+++ b/src/images/gitlab.svg
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="100"
+ height="100"
+ viewBox="0 0 100 100"
+ version="1.1"
+ id="svg8"
+ inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+ sodipodi:docname="gitlab.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.8308202"
+ inkscape:cx="-61.174768"
+ inkscape:cy="22.940538"
+ inkscape:document-units="px"
+ inkscape:current-layer="svg8"
+ inkscape:document-rotation="0"
+ showgrid="false"
+ units="px"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ inkscape:window-x="0"
+ inkscape:window-y="33"
+ inkscape:window-maximized="1"
+ inkscape:pagecheckerboard="0" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <rect
+ style="opacity:1;fill:#cccccc;fill-opacity:1;stroke-width:7.26231"
+ id="rect838"
+ width="100"
+ height="100"
+ x="0"
+ y="0" />
+ <path
+ id="path50_2_"
+ class="st3"
+ d="m 49.997513,82.236501 v 0 L 62.893305,42.569483 H 37.120587 Z"
+ style="fill:#e24329;stroke-width:1" />
+ <path
+ id="path66_6_"
+ class="st4"
+ d="m 19.057894,42.56969 v 0 l -3.92706,12.051333 c -0.355366,1.094294 0.0284,2.302206 0.966371,2.984325 L 49.99772,82.236501 Z"
+ style="fill:#fca326;stroke-width:1" />
+ <path
+ id="path74_2_"
+ class="st3"
+ d="M 19.057894,42.56969 H 37.120587 L 29.346912,18.680161 c -0.397869,-1.222217 -2.13178,-1.222217 -2.543954,0 z"
+ style="fill:#e24329;stroke-width:1" />
+ <path
+ id="path82_6_"
+ class="st4"
+ d="m 80.956206,42.56969 v 0 l 3.912962,12.051333 c 0.355365,1.094294 -0.02841,2.302206 -0.966371,2.984325 L 49.997513,82.236501 Z"
+ style="fill:#fca326;stroke-width:1" />
+ <path
+ id="path86_2_"
+ class="st3"
+ d="M 80.956206,42.56969 H 62.893513 l 7.75937,-23.889529 c 0.397868,-1.222217 2.131779,-1.222217 2.543953,0 z"
+ style="fill:#e24329;stroke-width:1" />
+ <path
+ id="polygon11-6"
+ style="fill:#fc6d26;stroke-width:4.82321"
+ class="st5"
+ d="M 50.016587,82.236502 80.956206,42.56969 H 62.893513 Z" />
+ <path
+ id="polygon11-6-7"
+ style="fill:#fc6d26;stroke-width:4.82321"
+ class="st5"
+ d="M 49.997513,82.236502 19.057894,42.56969 h 18.062693 z" />
+</svg>
diff --git a/src/js/app.js b/src/js/app.js
new file mode 100644
index 0000000..38aee0e
--- /dev/null
+++ b/src/js/app.js
@@ -0,0 +1,382 @@
+import projects from './config/projects';
+import {
+ translate,
+ setLocale,
+ currentLocale,
+ supportedLanguages,
+} from './i18n/i18n';
+import {
+ hideToBottom,
+ hideToLeft,
+ showFromBottom,
+ showFromLeft,
+} from './utilities/animations';
+import { isSmallVw, isStyleJsExists } from './utilities/helpers';
+
+/**
+ * Show/hide header and footer with slide animation (left).
+ */
+function toggleHeaderFooter() {
+ const header = document.querySelector('header');
+ const footer = document.querySelector('footer');
+ const elements = [header, footer];
+
+ elements.forEach((el) => {
+ if (el.classList.contains('hide')) {
+ showFromLeft(el);
+ } else {
+ hideToLeft(el);
+ }
+ });
+}
+
+/**
+ * Show/hide project details with slide animation (bottom).
+ */
+function toggleProjectDetails() {
+ const details = document.querySelector('.project-details');
+
+ if (details.classList.contains('hide')) {
+ showFromBottom(details);
+ } else {
+ hideToBottom(details);
+ }
+}
+
+/**
+ * Add an event listener to show or hide header and footer.
+ */
+function listenMenuBtn() {
+ const menuBtn = document.querySelector('.btn--menu');
+ menuBtn.addEventListener('click', toggleHeaderFooter);
+}
+
+/**
+ * Show or hide the project details button depending on current location.
+ */
+function toggleProjectDetailsBtn() {
+ const button = document.querySelector('.btn--details');
+ const currentPath = window.location.hash;
+
+ if (currentPath) {
+ button.style.display = '';
+ } else {
+ button.style.display = 'none';
+ }
+}
+
+/**
+ * Update the visibility of some DOM elements depending on viewport.
+ */
+function updateView() {
+ const header = document.querySelector('header');
+ const footer = document.querySelector('footer');
+ const toolbar = document.querySelector('.toolbar');
+ const details = document.querySelector('.project-details');
+
+ if (isSmallVw()) {
+ header.classList.add('hide');
+ footer.classList.add('hide');
+ toolbar.classList.remove('hide');
+ details?.classList.add('hide');
+ details?.classList.remove('fade-in');
+ } else {
+ showFromLeft(header);
+ showFromLeft(footer);
+ toolbar.classList.add('hide');
+ details?.classList.remove('hide');
+ details?.classList.add('fade-in');
+ }
+
+ toggleProjectDetailsBtn();
+}
+
+/**
+ * Update view when the window size changes.
+ */
+function listenWindowSize() {
+ window.addEventListener('resize', updateView);
+}
+
+/**
+ * Retrieve a project by id.
+ * @param {Integer} id - The project id.
+ * @returns {Object} The current project.
+ */
+function getCurrentProject(id) {
+ return projects.find((project) => project.id === id);
+}
+
+/**
+ * Get a list item for the given repo.
+ * @param {String} name - The repository name.
+ * @param {String} url - The repository URL.
+ * @returns {HTMLElement} A list item.
+ */
+function getRepoItem(name, url) {
+ const item = document.createElement('li');
+ const link = document.createElement('a');
+ const span = document.createElement('span');
+ span.classList.add('screen-reader-text');
+ span.textContent = name;
+ link.classList.add('list__link', `list__link--${name.toLocaleLowerCase()}`);
+ link.href = url;
+ link.appendChild(span);
+ item.classList.add('list__item');
+ item.appendChild(link);
+ return item;
+}
+
+/**
+ * Get the repos list wrapped inside ul element and the title.
+ * @param {Object[]} repos - An array of repo with name and URL.
+ * @returns {[title, list]} An array of HTMLElements for title and list.
+ */
+function getRepos(repos) {
+ if (repos.length === 0) return [];
+
+ const wrapper = document.createElement('div');
+ const title = document.createElement('h3');
+ const list = document.createElement('ul');
+ const items = repos.map((repo) => getRepoItem(repo.name, repo.url));
+ title.classList.add('project-details__title');
+ title.textContent = translate('main.project.details.repo', {
+ count: repos.length,
+ });
+ list.classList.add('list', 'list--repos');
+ list.append(...items);
+ wrapper.append(title, list);
+ return [title, list];
+}
+
+/**
+ * Get the technologies list wrapped inside ul element and the title.
+ * @param {String[]} technologies - An array of technology names.
+ * @returns {[title, list]} An array of HTMLElements for title and list.
+ */
+function getTechs(technologies) {
+ if (technologies.length === 0) return [];
+
+ const title = document.createElement('h3');
+ title.classList.add('project-details__title');
+ title.textContent = translate('main.project.details.tech', {
+ count: technologies.length,
+ });
+ const list = document.createElement('ul');
+ const items = technologies.map((technology) => {
+ const item = document.createElement('li');
+ item.textContent = technology;
+ return item;
+ });
+ list.classList.add('list', 'list--tech');
+ list.append(...items);
+ return [title, list];
+}
+
+/**
+ * Retrieve the project details.
+ * @param {Object} project - The project.
+ * @returns {HTMLElement} The project details wrapped in a div.
+ */
+function getProjectDetails(project) {
+ const details = document.createElement('div');
+ const title = document.createElement('h2');
+ const techList = project?.technologies ? getTechs(project.technologies) : [];
+ const reposList = getRepos(project.repo);
+ const locale = currentLocale();
+ let description;
+
+ if (project.description) {
+ description = document.createElement('div');
+ description.classList.add('project-details__description');
+ description.textContent = project.description[locale] || '';
+ } else {
+ description = '';
+ }
+
+ title.classList.add('project-details__title');
+ title.textContent = translate('main.project.details.about', {
+ name: project.name,
+ });
+ details.classList.add('project-details');
+ if (!isSmallVw()) details.classList.add('fade-in');
+ details.replaceChildren(title, description, ...techList, ...reposList);
+
+ return details;
+}
+
+/**
+ * Get an iframe for the given path/url.
+ * @param {String} src - The path/url to use as source.
+ * @returns {HTMLElement} The iframe.
+ */
+function getIframe(src) {
+ const iframe = document.createElement('iframe');
+ iframe.src = src;
+ return iframe;
+}
+
+/**
+ * Retrieve the project preview.
+ * @param {String} projectPath - The project path.
+ * @returns {HTMLElement} The project preview wrapped in a div.
+ */
+function getProjectPreview(projectPath) {
+ const preview = document.createElement('div');
+ const iframe = getIframe(projectPath);
+ preview.classList.add('project-preview', 'fade-in');
+ preview.replaceChildren(iframe);
+ return preview;
+}
+
+/**
+ * Display the targeted project.
+ * @param {String} id - The project id.
+ */
+function showProject(id) {
+ const currentProject = getCurrentProject(id);
+ const main = document.querySelector('.main');
+ const details = getProjectDetails(currentProject);
+ const preview = getProjectPreview(currentProject.path);
+ const detailsBtn = document.querySelector('.btn--details');
+
+ if (isSmallVw()) details.classList.add('hide');
+
+ detailsBtn.textContent = translate('main.project.details.about', {
+ name: currentProject.name,
+ });
+ detailsBtn.addEventListener('click', toggleProjectDetails);
+ window.history.pushState({}, currentProject.name, `/#${id}`);
+ document.title = `${currentProject.name} | Demo | Armand Philippot`;
+ main.replaceChildren(preview, details);
+}
+
+/**
+ * Add a CSS class to the current project in projects nav.
+ * @param {String} id - The project id.
+ */
+function setSelectedProject(id) {
+ const links = document.querySelectorAll('.nav__link');
+ links.forEach((link) => {
+ if (link.id === id) {
+ link.classList.add('nav__link--selected');
+ } else {
+ link.classList.remove('nav__link--selected');
+ }
+ });
+}
+
+/**
+ * Create a list item for a project.
+ * @param {String} id - The project id.
+ * @param {String} name - The project name.
+ * @returns {HTMLElement} The list item.
+ */
+function getProjectsNavItem(id, name) {
+ const item = document.createElement('li');
+ const link = document.createElement('a');
+ link.classList.add('nav__link');
+ link.href = `/#${id}`;
+ link.id = id;
+ link.textContent = name;
+ link.addEventListener('click', (e) => {
+ e.preventDefault();
+ showProject(id);
+ setSelectedProject(id);
+ toggleProjectDetailsBtn();
+ if (isSmallVw()) toggleHeaderFooter();
+ });
+ item.classList.add('nav__item');
+ item.appendChild(link);
+ return item;
+}
+
+/**
+ * Print the list of available projects.
+ */
+function printProjectsNav() {
+ const ul = document.querySelector('.nav .nav__list');
+
+ projects.forEach((project) => {
+ const item = getProjectsNavItem(project.id, project.name);
+ ul.appendChild(item);
+ });
+}
+
+/**
+ * Add style.js script for development purposes.
+ */
+function loadWebpackStyles() {
+ if (isStyleJsExists()) {
+ const head = document.querySelector('head');
+ const script = document.createElement('script');
+ script.src = 'assets/js/style.js';
+ head.appendChild(script);
+ }
+}
+
+/**
+ * Load corresponding project if the requested page contains a hash.
+ */
+function printRequestedPage() {
+ const currentPath = window.location.hash;
+
+ if (currentPath) {
+ const id = currentPath.replace('#', '');
+ showProject(id);
+ setSelectedProject(id);
+ }
+}
+
+/**
+ * Replace the legal notice link and text.
+ */
+function replaceLegalNoticeLink() {
+ const link = document.querySelector('.nav__link--legal');
+ link.href = translate('footer.legalNotice.link');
+ link.textContent = translate('footer.legalNotice.txt');
+}
+
+/**
+ * Translate all text available in HTML templates.
+ */
+function translateHTMLContent() {
+ const brandingDesc = document.querySelector('.branding__description');
+ const navLabel = document.querySelector('.nav__label');
+ const license = document.querySelector('.copyright__license');
+ const instructions = document.querySelector('.instructions');
+ brandingDesc.textContent = translate('branding.description');
+ navLabel.textContent = translate('nav.title');
+ license.title = translate('footer.license');
+ if (instructions) instructions.textContent = translate('main.instructions');
+}
+
+/**
+ * Translate the website according to the user preferred language.
+ */
+function setAppLocale() {
+ const preferredLanguage = navigator.language;
+ const supportedLanguage = supportedLanguages.find(
+ (lang) => preferredLanguage.startsWith(lang.code)
+ // eslint-disable-next-line function-paren-newline -- Conflict with Prettier
+ );
+ const locale = supportedLanguage?.code || 'en';
+ setLocale(locale);
+}
+
+/**
+ * Initialize the website with the projects list.
+ */
+function init() {
+ setAppLocale();
+ translateHTMLContent();
+ replaceLegalNoticeLink();
+ loadWebpackStyles();
+ printProjectsNav();
+ updateView();
+ listenWindowSize();
+ listenMenuBtn();
+ printRequestedPage();
+}
+
+init();
diff --git a/src/js/config/projects.js b/src/js/config/projects.js
new file mode 100644
index 0000000..53f1af8
--- /dev/null
+++ b/src/js/config/projects.js
@@ -0,0 +1,224 @@
+const projects = [
+ {
+ id: 'bin2dec',
+ name: 'Bin2Dec',
+ description: {
+ en: 'Convert a binary string to a decimal number.',
+ fr: 'Convertit un nombre binaire en un nombre décimal.',
+ },
+ path: './projects/js-small-apps/bin2dec/index.html',
+ repo: [
+ {
+ name: 'Github',
+ url: 'https://github.com/ArmandPhilippot/js-small-apps/tree/main/bin2dec',
+ },
+ {
+ name: 'Gitlab',
+ url: 'https://gitlab.com/ArmandPhilippot/js-small-apps/-/tree/main/bin2dec',
+ },
+ ],
+ technologies: ['Vanilla Javascript'],
+ },
+ {
+ id: 'budget-app',
+ name: 'Budget App',
+ description: {
+ en: 'By selecting a language in the initialization form, only the currency is converted (the app is not translated). Also, no data is saved on page reload.',
+ fr: "En sélectionnant une langue dans le formulaire d'initialisation, seul le format des nombres change (l'application n'est pas traduite). Aucune donnée n'est conservée après rechargement de la page.",
+ },
+ path: './projects/js-small-apps/budget-app/index.html',
+ repo: [
+ {
+ name: 'Github',
+ url: 'https://github.com/ArmandPhilippot/js-small-apps/tree/main/budget-app',
+ },
+ {
+ name: 'Gitlab',
+ url: 'https://gitlab.com/ArmandPhilippot/js-small-apps/-/tree/main/budget-app',
+ },
+ ],
+ technologies: ['Vanilla Javascript'],
+ },
+ {
+ id: 'calculator',
+ name: 'Calculator',
+ description: {
+ en: 'A basic calculator. Decimal part is limited to 3 digits. The first part is limited to 8 digits. If the result does not respect these limits, you will see an error.',
+ fr: 'Une simple calculette. La partie décimale est limitée à 3 chiffres. La première partie est limitée à 8 chiffres. Si le résultat ne respecte pas ces limites, vous verrez une erreur.',
+ },
+ path: './projects/js-small-apps/calculator/index.html',
+ repo: [
+ {
+ name: 'Github',
+ url: 'https://github.com/ArmandPhilippot/js-small-apps/tree/main/calculator',
+ },
+ {
+ name: 'Gitlab',
+ url: 'https://gitlab.com/ArmandPhilippot/js-small-apps/-/tree/main/calculator',
+ },
+ ],
+ technologies: ['Vanilla Javascript'],
+ },
+ {
+ id: 'clock',
+ name: 'Clock',
+ description: {
+ en: 'What time is it? You can have the current hour in three formats: an analogic clock, a numeric display or a text.',
+ fr: "Quelle heure est-il ? Vous pouvez voir l'heure actuelle dans trois formats différents : une horloge analogique, un affichage numérique et sous forme de texte.",
+ },
+ path: './projects/js-small-apps/clock/index.html',
+ repo: [
+ {
+ name: 'Github',
+ url: 'https://github.com/ArmandPhilippot/js-small-apps/tree/main/clock',
+ },
+ {
+ name: 'Gitlab',
+ url: 'https://gitlab.com/ArmandPhilippot/js-small-apps/-/tree/main/clock',
+ },
+ ],
+ technologies: ['Vanilla Javascript', 'SVG'],
+ },
+ {
+ id: 'color-cycle',
+ name: 'Color cycle',
+ description: {
+ en: 'Play with hexadecimal colors. Set a color, then choose one or more increment values and start the preview.',
+ fr: "Jouez avec les couleurs hexadécimales. Définissez une couleur, puis choisissez une ou plusieurs valeurs d'incrémentation et démarrez l'aperçu.",
+ },
+ path: './projects/js-small-apps/color-cycle/index.html',
+ repo: [
+ {
+ name: 'Github',
+ url: 'https://github.com/ArmandPhilippot/js-small-apps/tree/main/color-cycle',
+ },
+ {
+ name: 'Gitlab',
+ url: 'https://gitlab.com/ArmandPhilippot/js-small-apps/-/tree/main/color-cycle',
+ },
+ ],
+ technologies: ['Vanilla Javascript'],
+ },
+ {
+ id: 'css-border-previewer',
+ name: 'CSS Border Previewer',
+ description: {
+ en: 'Play with CSS borders (style, width, radius). Then, you can copy the generated code if the preview suits you.',
+ fr: "Jouez avec les bordures CSS (style, largeur, radius). Ensuite, vous pouvez copier le code généré si l'aperçu vous satisfait.",
+ },
+ path: './projects/js-small-apps/css-border-previewer/index.html',
+ repo: [
+ {
+ name: 'Github',
+ url: 'https://github.com/ArmandPhilippot/js-small-apps/tree/main/css-border-previewer',
+ },
+ {
+ name: 'Gitlab',
+ url: 'https://gitlab.com/ArmandPhilippot/js-small-apps/-/tree/main/css-border-previewer',
+ },
+ ],
+ technologies: ['Vanilla Javascript'],
+ },
+ {
+ id: 'meme-generator',
+ name: 'Meme Generator',
+ description: {
+ en: 'Choose a random image, set one or more texts then position them. Your meme is ready!',
+ fr: 'Choisissez une image aléatoire, définissez un ou plusieurs textes et positionnez-les. Votre meme est prêt !',
+ },
+ path: './projects/react-small-apps/meme-generator/build/index.html',
+ repo: [
+ {
+ name: 'Github',
+ url: 'https://github.com/ArmandPhilippot/react-small-apps/tree/main/meme-generator',
+ },
+ {
+ name: 'Gitlab',
+ url: 'https://gitlab.com/ArmandPhilippot/react-small-apps/-/tree/main/meme-generator',
+ },
+ ],
+ technologies: ['React', 'Fetch'],
+ },
+ {
+ id: 'notebook',
+ name: 'Notebook',
+ description: {
+ en: 'Create as many pages as you want and fill them. You can define a title and a body. Then you can easily navigate between your pages with the nav.',
+ fr: 'Créez autant de pages que vous le souhaitez et remplissez-les. Vous pouvez définir un titre et un corps de texte. Ensuite, vous pouvez facilement naviguer entre vos pages grâce à la navigation.',
+ },
+ path: './projects/react-small-apps/notebook/build/',
+ repo: [
+ {
+ name: 'Github',
+ url: 'https://github.com/ArmandPhilippot/react-small-apps/tree/main/notebook',
+ },
+ {
+ name: 'Gitlab',
+ url: 'https://gitlab.com/ArmandPhilippot/react-small-apps/-/tree/main/notebook',
+ },
+ ],
+ technologies: ['React', 'React router'],
+ },
+ {
+ id: 'rps-game',
+ name: 'Rock Paper Scissors',
+ description: {
+ en: 'A basic implementation of the game. Try to beat your friend or the computer.',
+ fr: "Une implémentation du jeu. Essayez de battre votre ami ou l'ordinateur.",
+ },
+ path: './projects/js-small-apps/rock-paper-scissors/index.html',
+ repo: [
+ {
+ name: 'Github',
+ url: 'https://github.com/ArmandPhilippot/js-small-apps/tree/main/rock-paper-scissors',
+ },
+ {
+ name: 'Gitlab',
+ url: 'https://gitlab.com/ArmandPhilippot/js-small-apps/-/tree/main/rock-paper-scissors',
+ },
+ ],
+ technologies: ['Vanilla Javascript'],
+ },
+ {
+ id: 'todos',
+ name: 'Todos',
+ description: {
+ en: 'You can add, remove or mark as done your todos. For each todos, you can add some details in addition to the title.\n\nLogin: demo@email.com\nPassword: demo',
+ fr: 'Vous pouvez ajouter, supprimer ou marquer comme fait vos "todo". Pour chaque "todo", vous pouvez ajouter des détails en plus du titre.\n\nLogin : demo@email.com\nMot de passe : demo',
+ },
+ path: './projects/react-small-apps/todos/build/',
+ repo: [
+ {
+ name: 'Github',
+ url: 'https://github.com/ArmandPhilippot/react-small-apps/tree/main/todos',
+ },
+ {
+ name: 'Gitlab',
+ url: 'https://gitlab.com/ArmandPhilippot/react-small-apps/-/tree/main/todos',
+ },
+ ],
+ technologies: ['React', 'React router', 'Redux'],
+ },
+ {
+ id: 'users-list',
+ name: 'Users list',
+ description: {
+ en: 'You can see a list of username. By clicking on it, the next column display information about the selected user.',
+ fr: "Vous pouvez voir une liste de noms d'utilisateur. En cliquant sur l'un d'eux, la colonne suivante affiche les informations à propos de cet utilisateur.",
+ },
+ path: './projects/js-small-apps/users-list/index.html',
+ repo: [
+ {
+ name: 'Github',
+ url: 'https://github.com/ArmandPhilippot/js-small-apps/tree/main/users-list',
+ },
+ {
+ name: 'Gitlab',
+ url: 'https://gitlab.com/ArmandPhilippot/js-small-apps/-/tree/main/users-list',
+ },
+ ],
+ technologies: ['Vanilla Javascript', 'Fetch'],
+ },
+];
+
+export default projects;
diff --git a/src/js/i18n/i18n.js b/src/js/i18n/i18n.js
new file mode 100644
index 0000000..6bdc7cd
--- /dev/null
+++ b/src/js/i18n/i18n.js
@@ -0,0 +1,42 @@
+import I18n from 'i18n-js';
+import en from './locales/en';
+import fr from './locales/fr';
+
+const supportedLanguages = [
+ {
+ code: 'en',
+ label: 'English',
+ translations: en,
+ },
+ {
+ code: 'fr',
+ label: 'Français',
+ translations: fr,
+ },
+];
+
+supportedLanguages.forEach((locale) => {
+ I18n.translations[locale.code] = locale.translations;
+});
+
+function setLocale(locale) {
+ I18n.locale = locale;
+}
+
+function currentLocale() {
+ return I18n.currentLocale();
+}
+
+function translate(name, params = {}) {
+ return I18n.t(name, params);
+}
+
+const { defaultLocale } = I18n;
+
+export {
+ supportedLanguages,
+ setLocale,
+ translate,
+ defaultLocale,
+ currentLocale,
+};
diff --git a/src/js/i18n/locales/en.js b/src/js/i18n/locales/en.js
new file mode 100644
index 0000000..9717528
--- /dev/null
+++ b/src/js/i18n/locales/en.js
@@ -0,0 +1,36 @@
+const en = {
+ branding: {
+ description: 'Front-end developer',
+ },
+ nav: {
+ title: 'Apps list:',
+ },
+ main: {
+ instructions:
+ 'Select an app inside menu to see a live preview and app details (description, technologies, repositories).',
+ project: {
+ details: {
+ about: 'About {{name}}',
+ repo: {
+ one: 'Repository:',
+ other: 'Repositories:',
+ zero: 'Repositories:',
+ },
+ tech: {
+ one: 'Technology:',
+ other: 'Technologies:',
+ zero: 'Technologies:',
+ },
+ },
+ },
+ },
+ footer: {
+ legalNotice: {
+ txt: 'Legal notice',
+ link: 'legal-notice.html',
+ },
+ license: 'License MIT',
+ },
+};
+
+export default en;
diff --git a/src/js/i18n/locales/fr.js b/src/js/i18n/locales/fr.js
new file mode 100644
index 0000000..9c93012
--- /dev/null
+++ b/src/js/i18n/locales/fr.js
@@ -0,0 +1,36 @@
+const fr = {
+ branding: {
+ description: 'Intégrateur web',
+ },
+ nav: {
+ title: 'Liste des applications :',
+ },
+ main: {
+ instructions:
+ "Sélectionnez une application dans le menu pour afficher un aperçu en direct et les informations sur l'application (description, technologies, dépôts).",
+ project: {
+ details: {
+ about: 'À propos de {{name}}',
+ repo: {
+ one: 'Dépôt :',
+ other: 'Dépôts :',
+ zero: 'Dépôt :',
+ },
+ tech: {
+ one: 'Technologie :',
+ other: 'Technologies :',
+ zero: 'Technologie :',
+ },
+ },
+ },
+ },
+ footer: {
+ legalNotice: {
+ txt: 'Mentions légales',
+ link: 'mentions-legales.html',
+ },
+ license: 'Licence MIT',
+ },
+};
+
+export default fr;
diff --git a/src/js/utilities/animations.js b/src/js/utilities/animations.js
new file mode 100644
index 0000000..9a30685
--- /dev/null
+++ b/src/js/utilities/animations.js
@@ -0,0 +1,45 @@
+/**
+ * Change the element classes to hide it with a slide out left animation.
+ * @param {HTMLElement} el - The HTMLElement to hide.
+ */
+function hideToLeft(el) {
+ el?.classList.remove('slide-in--left');
+ el?.classList.add('slide-out--left');
+ setTimeout(() => {
+ el?.classList.add('hide');
+ }, 800);
+}
+
+/**
+ * Change the element classes to show it with a slide in left animation.
+ * @param {HTMLElement} el - The HTMLElement to show.
+ */
+function showFromLeft(el) {
+ el?.classList.remove('slide-out--left');
+ el?.classList.remove('hide');
+ el?.classList.add('slide-in--left');
+}
+
+/**
+ * Change the element classes to hide it with a slide out bottom animation.
+ * @param {HTMLElement} el - The HTMLElement to hide.
+ */
+function hideToBottom(el) {
+ el?.classList.remove('slide-in--up');
+ el?.classList.add('slide-out--bottom');
+ setTimeout(() => {
+ el?.classList.add('hide');
+ }, 800);
+}
+
+/**
+ * Change the element classes to show it with a slide in up animation.
+ * @param {HTMLElement} el - The HTMLElement to show.
+ */
+function showFromBottom(el) {
+ el?.classList.remove('slide-out--bottom');
+ el?.classList.remove('hide');
+ el?.classList.add('slide-in--up');
+}
+
+export { hideToLeft, showFromLeft, hideToBottom, showFromBottom };
diff --git a/src/js/utilities/helpers.js b/src/js/utilities/helpers.js
new file mode 100644
index 0000000..470c49c
--- /dev/null
+++ b/src/js/utilities/helpers.js
@@ -0,0 +1,19 @@
+/**
+ * Check the size of the current viewport.
+ * @returns {Boolean} True if viewport lower than 1200px; false otherwise.
+ */
+function isSmallVw() {
+ return window.innerWidth < 1200;
+}
+
+/**
+ * Check if /assets/styles.js exists (Webpack dev mode).
+ * @returns {Boolean} True if style.js exists ; false otherwise.
+ */
+async function isStyleJsExists() {
+ const filePath = 'assets/js/style.js';
+ const response = await fetch(filePath);
+ return response.status === 200;
+}
+
+export { isSmallVw, isStyleJsExists };
diff --git a/src/scss/abstracts/_functions.scss b/src/scss/abstracts/_functions.scss
new file mode 100644
index 0000000..672e5e7
--- /dev/null
+++ b/src/scss/abstracts/_functions.scss
@@ -0,0 +1,4 @@
+@forward "./functions/convert";
+@forward "./functions/css-vars";
+@forward "./functions/str-replace";
+@forward "./functions/encode";
diff --git a/src/scss/abstracts/_mixins.scss b/src/scss/abstracts/_mixins.scss
new file mode 100644
index 0000000..fd28631
--- /dev/null
+++ b/src/scss/abstracts/_mixins.scss
@@ -0,0 +1,2 @@
+@forward "./mixins/css-vars";
+@forward "./mixins/media-queries";
diff --git a/src/scss/abstracts/_placeholders.scss b/src/scss/abstracts/_placeholders.scss
new file mode 100644
index 0000000..079cae7
--- /dev/null
+++ b/src/scss/abstracts/_placeholders.scss
@@ -0,0 +1,32 @@
+/// List Reset
+%reset-list {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+
+ li {
+ margin-bottom: 0;
+ }
+}
+
+/// Ordered List Reset
+%reset-ordered-list {
+ @extend %reset-list;
+
+ li {
+ counter-increment: none;
+ display: list-item;
+
+ &::before {
+ display: none;
+ }
+ }
+}
+
+/// Display an inline list with flexbox
+%flex-list {
+ @extend %reset-list;
+
+ display: flex;
+ flex-flow: row wrap;
+}
diff --git a/src/scss/abstracts/_variables.scss b/src/scss/abstracts/_variables.scss
new file mode 100644
index 0000000..a8a22a8
--- /dev/null
+++ b/src/scss/abstracts/_variables.scss
@@ -0,0 +1,103 @@
+@use "sass:math";
+@use "../abstracts/functions" as fun;
+
+//===========================================================================
+// Ratios
+//===========================================================================
+
+/// Ratios map
+/// @prop {String} keys - Keys are identifiers mapped to a given ratio
+/// @prop {Map} value - Value is actual ratio
+$ratios: (
+ "minor-second": 1.067,
+ "major-second": 1.125,
+ "minor-third": 1.2,
+ "major-third": 1.25,
+ "perfect-fourth": 1.333,
+ "augmented-fourth": 1.414,
+ "perfect-fifth": 1.5,
+ "golden-number": 1.618,
+);
+
+// Cannot declare the following function in partials due to module loop.
+// Also, it will only be used in this file so it is not a problem.
+
+/// Get ratio
+/// @param {String} $name - Ratio name.
+/// @return {Integer} The ratio value.
+@function get-ratio($name) {
+ @return map-get($ratios, $name);
+}
+
+//===========================================================================
+// Layout
+//===========================================================================
+
+/// Breakpoints map
+/// @prop {String} keys - Keys are identifiers mapped to a given length
+/// @prop {Map} values - Values are actual breakpoints expressed in pixels
+$breakpoints: (
+ "xs": fun.convert-px(568, "em"),
+ "sm": fun.convert-px(768, "em"),
+ "md": fun.convert-px(1024, "em"),
+ "lg": fun.convert-px(1200, "em"),
+ "xl": fun.convert-px(1600, "em"),
+ "2xl": fun.convert-px(1920, "em"),
+);
+
+//===========================================================================
+// Fonts
+//===========================================================================
+
+/* stylelint-disable -- Fonts name are not keywords, lowercase is not needed. */
+/// Regular font family
+/// @type List
+$font-family_primary: ("Inter", "Liberation Sans", Arial, sans-serif);
+
+/// Alternative regular font family
+/// @type List
+$font-family_secondary: ("Kanit", "Liberation Sans", Arial, sans-serif);
+
+$line-height: get-ratio("golden-number");
+
+$font-size_base: 16px;
+$font-size_base-rem: fun.convert-px(16); // font-size_base without unit
+$font-size_ratio: get-ratio("minor-third");
+$font-size_sm: $font-size_base-rem * math.pow($font-size_ratio, -1);
+$font-size_md: $font-size_base-rem * math.pow($font-size_ratio, 0);
+$font-size_lg: $font-size_base-rem * math.pow($font-size_ratio, 1);
+$font-size_xl: $font-size_base-rem * math.pow($font-size_ratio, 2);
+$font-size_2xl: $font-size_base-rem * math.pow($font-size_ratio, 3);
+$font-size_3xl: $font-size_base-rem * math.pow($font-size_ratio, 4);
+
+//============================================================================
+// Spacings
+//============================================================================
+
+$spacing_ratio: get-ratio("golden-number");
+$spacing_base: $spacing_ratio * 1rem;
+
+$spacing_3xs: math.div($spacing_base, 4);
+$spacing_2xs: math.div($spacing_base, 3);
+$spacing_xs: math.div($spacing_base, 2);
+$spacing_sm: math.div($spacing_base, 1.5);
+$spacing_md: $spacing_base;
+$spacing_lg: $spacing_base * 1.5;
+
+//============================================================================
+// Colors
+//============================================================================
+
+$color_black-squeeze: hsl(212, 55%, 97%);
+$color_catskill-white: hsl(212, 53%, 92%);
+$color_link-water: hsl(212, 51%, 87%);
+$color_geyser: hsl(212, 27%, 83%);
+$color_gull-gray: hsl(212, 15%, 66%);
+$color_pale-sky: hsl(212, 13%, 46%);
+$color_nile-blue: hsl(212, 47%, 19%);
+$color_firefly: hsl(212, 45%, 11%);
+$color_chambray: hsl(212, 45%, 40%);
+$color_chathams-blue: hsl(212, 65%, 28%);
+$color_chathams-blue-light: hsl(212, 90%, 30%);
+$color_chathams-blue-light-opacity-25: hsla(212, 90%, 30%, 0.25);
+$color_chathams-blue-dark: hsl(212, 70%, 25%);
diff --git a/src/scss/abstracts/functions/_convert.scss b/src/scss/abstracts/functions/_convert.scss
new file mode 100644
index 0000000..9f51dc7
--- /dev/null
+++ b/src/scss/abstracts/functions/_convert.scss
@@ -0,0 +1,16 @@
+@use "sass:math";
+
+/// Convert px to rem or em.
+/// @param {Number} $px Value in px
+/// @param {String} $to Unit. Either "rem" or "em"
+/// @param {Number} $standard 1rem (or 1em) = 16px
+/// @return {Number} Value in rem or em
+@function convert-px($px, $to: "rem", $standard: 16) {
+ @if $to == "rem" {
+ @return math.div($px, $standard) + 0rem; // stylelint-disable-line
+ } @else if $to == "em" {
+ @return math.div($px, $standard) + 0em; // stylelint-disable-line
+ } @else {
+ @error "`$to` must be either `rem` or `em`.";
+ }
+}
diff --git a/src/scss/abstracts/functions/_css-vars.scss b/src/scss/abstracts/functions/_css-vars.scss
new file mode 100644
index 0000000..89e1a15
--- /dev/null
+++ b/src/scss/abstracts/functions/_css-vars.scss
@@ -0,0 +1,8 @@
+/// Retrieve a CSS variable value with prefix
+/// @see https://dev.to/felipperegazio/css-custom-properties-vars-with-sass-scss-a-practical-architecture-strategy-1m88
+/// @param {String} $name Variable name
+/// @param {String} $prefix Variable prefix
+/// @return {String} Variable in CSS format
+@function get-var($name, $prefix: dap) {
+ @return var(--#{$prefix}-#{$name});
+}
diff --git a/src/scss/abstracts/functions/_encode.scss b/src/scss/abstracts/functions/_encode.scss
new file mode 100644
index 0000000..4350185
--- /dev/null
+++ b/src/scss/abstracts/functions/_encode.scss
@@ -0,0 +1,14 @@
+@use "str-replace" as fun;
+
+/// Encode a SVG.
+/// @param {String} $svg A complete svg (`<svg>...</svg>`).
+/// @return The encoded svg, ready to use for background-image.
+@function encode-svg($svg) {
+ $svg-encoding: (("<", "%3C"), (">", "%3E"), ("#", "%23"));
+
+ @each $char, $encoded in $svg-encoding {
+ $svg: fun.str-replace($svg, $char, $encoded);
+ }
+
+ @return "data:image/svg+xml;utf8," + $svg;
+}
diff --git a/src/scss/abstracts/functions/_str-replace.scss b/src/scss/abstracts/functions/_str-replace.scss
new file mode 100644
index 0000000..624bf33
--- /dev/null
+++ b/src/scss/abstracts/functions/_str-replace.scss
@@ -0,0 +1,20 @@
+/// Replace `$search` with `$replace` in `$string`
+/// @author Hugo Giraudel
+/// @param {String} $string - Initial string
+/// @param {String} $search - Substring to replace
+/// @param {String} $replace ('') - New value
+/// @return {String} - Updated string
+@function str-replace($string, $search, $replace: "") {
+ $index: str-index($string, $search);
+
+ @if $index {
+ @return str-slice($string, 1, $index - 1) + $replace +
+ str-replace(
+ str-slice($string, $index + str-length($search)),
+ $search,
+ $replace
+ );
+ }
+
+ @return $string;
+}
diff --git a/src/scss/abstracts/mixins/_css-vars.scss b/src/scss/abstracts/mixins/_css-vars.scss
new file mode 100644
index 0000000..8e31c96
--- /dev/null
+++ b/src/scss/abstracts/mixins/_css-vars.scss
@@ -0,0 +1,20 @@
+/// Declare a set of CSS variables properly prefixed.
+///
+/// @see https://dev.to/felipperegazio/css-custom-properties-vars-with-sass-scss-a-practical-architecture-strategy-1m88
+///
+/// @param {List} $variables - A list of variable name and value.
+/// @param {Bool} $root - Set vars at root.
+/// @param {String} $prefix - The variables prefix.
+@mixin set-vars($variables, $root: true, $prefix: "dap") {
+ @if $root {
+ :root {
+ @each $name, $value in $variables {
+ --#{$prefix}-#{$name}: #{$value};
+ }
+ }
+ } @else {
+ @each $name, $value in $variables {
+ --#{$prefix}-#{$name}: #{$value};
+ }
+ }
+}
diff --git a/src/scss/abstracts/mixins/_media-queries.scss b/src/scss/abstracts/mixins/_media-queries.scss
new file mode 100644
index 0000000..fcaea4b
--- /dev/null
+++ b/src/scss/abstracts/mixins/_media-queries.scss
@@ -0,0 +1,81 @@
+@use "../variables" as var;
+
+/// Media query: media type
+/// @param {String} $type - Media type: all, screen, print, retina.
+/// @example scss - `@media only screen` equivalent is:
+/// @include media("screen");
+@mixin media($type) {
+ @if $type == "retina" {
+ $type: "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)";
+ } @else if $type == "screen" or $type == "print" {
+ $type: "only #{$type}";
+ }
+
+ @media #{$type} {
+ @content;
+ }
+}
+
+/// Media query: min-width / max-width
+/// @param {String} $from - min-width breakpoint.
+/// @param {String} $until - max-width breakpoint.
+/// @example scss - `@media (min-width: "md")` equivalent is:
+/// @include dimensions("md");
+@mixin dimensions($from: null, $until: null) {
+ $query: "";
+
+ @if $from {
+ @if type-of($from) == "string" {
+ $size: map-get(var.$breakpoints, $from);
+ $query: "(min-width: #{$size})";
+ } @else {
+ @error "`$from` must be a string.";
+ }
+ }
+
+ @if $from and $until {
+ $query: $query + " and ";
+ }
+
+ @if $until {
+ @if type-of($until) == "string" {
+ $size: map-get(var.$breakpoints, $until);
+ $size: calc(#{$size} - 1px);
+ $query: $query + "(max-width: #{$size})";
+ } @else {
+ @error "`$until` must be a string.";
+ }
+ }
+
+ @media #{$query} {
+ @content;
+ }
+}
+
+/// Media query: prefers-reduced-motion
+/// @param {String} $value - Media query value: `no-preference` or `reduce`.
+/// @example scss - @media (prefers-reduced-motion: "reduce") equivalent is:
+/// @include motion("reduce");
+@mixin motion($value) {
+ @if $value == "no-preference" or $value == "reduce" {
+ @media (prefers-reduced-motion: #{$value}) {
+ @content;
+ }
+ } @else {
+ @error "Allowed values are `no-preference` and `reduce`.";
+ }
+}
+
+/// Media query: any-pointer
+/// @param {String} $value - Media query value: `fine`, `coarse` or `none`.
+/// @example scss - @media (any-pointer: "fine") equivalent is:
+/// @include pointer("fine");
+@mixin pointer($value) {
+ @if $value == "fine" or $value == "coarse" or $value == "none" {
+ @media (any-pointer: #{$value}) {
+ @content;
+ }
+ } @else {
+ @error "Allowed values are `fine`, `coarse` and `none`.";
+ }
+}
diff --git a/src/scss/base/_animations.scss b/src/scss/base/_animations.scss
new file mode 100644
index 0000000..a2df8ab
--- /dev/null
+++ b/src/scss/base/_animations.scss
@@ -0,0 +1,89 @@
+@keyframes fadeIn {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ visibility: visible;
+ }
+}
+
+@keyframes fadeOut {
+ 0% {
+ opacity: 1;
+ }
+
+ 100% {
+ opacity: 0;
+ visibility: hidden;
+ }
+}
+
+@keyframes slideInLeft {
+ 0% {
+ margin-left: -100%;
+ }
+
+ 100% {
+ margin-left: 0;
+ visibility: visible;
+ }
+}
+
+@keyframes slideOutLeft {
+ 0% {
+ margin-left: 0;
+ }
+
+ 100% {
+ margin-left: -100%;
+ visibility: hidden;
+ }
+}
+
+@keyframes slideInUp {
+ 0% {
+ margin-bottom: -100vh;
+ }
+
+ 100% {
+ margin-bottom: 0;
+ visibility: visible;
+ }
+}
+
+@keyframes slideOutBottom {
+ 0% {
+ margin-bottom: 0;
+ }
+
+ 100% {
+ margin-bottom: -100vh;
+ visibility: hidden;
+ }
+}
+
+.fade-in {
+ animation: fadeIn 1s;
+}
+
+.fade-out {
+ animation: fadeOut 1s;
+}
+
+.slide-in--left {
+ animation: slideInLeft 0.8s;
+}
+
+.slide-out--left {
+ animation: slideOutLeft 0.8s;
+}
+
+.slide-in--up {
+ animation: slideInUp 1s;
+}
+
+.slide-out--bottom {
+ animation: slideOutBottom 1s;
+}
diff --git a/src/scss/base/_base.scss b/src/scss/base/_base.scss
new file mode 100644
index 0000000..a157a50
--- /dev/null
+++ b/src/scss/base/_base.scss
@@ -0,0 +1,3 @@
+html {
+ overflow: hidden;
+}
diff --git a/src/scss/base/_colors.scss b/src/scss/base/_colors.scss
new file mode 100644
index 0000000..6251b2e
--- /dev/null
+++ b/src/scss/base/_colors.scss
@@ -0,0 +1,21 @@
+@use "../abstracts/mixins" as mix;
+@use "../abstracts/variables" as var;
+@include mix.set-vars(
+ (
+ color-bg: #{var.$color_black-squeeze},
+ color-bg-secondary: #{var.$color_catskill-white},
+ color-bg-tertiary: #{var.$color_link-water},
+ color-fg: #{var.$color_firefly},
+ color-fg-inverted: #{var.$color_black-squeeze},
+ color-border: #{var.$color_chathams-blue},
+ color-border-light: #{var.$color_geyser},
+ color-shadow-darker: #{var.$color_nile-blue},
+ color-shadow-dark: #{var.$color_pale-sky},
+ color-shadow: #{var.$color_chambray},
+ color-shadow-light: #{var.$color_gull-gray},
+ color-primary: #{var.$color_chathams-blue},
+ color-primary-light: #{var.$color_chathams-blue-light},
+ color-primary-light-opacity: #{var.$color_chathams-blue-light-opacity-25},
+ color-primary-dark: #{var.$color_chathams-blue-dark},
+ )
+);
diff --git a/src/scss/base/_fonts.scss b/src/scss/base/_fonts.scss
new file mode 100644
index 0000000..192f6b8
--- /dev/null
+++ b/src/scss/base/_fonts.scss
@@ -0,0 +1,115 @@
+@use "../abstracts/functions" as fun;
+@use "../abstracts/mixins" as mix;
+@use "../abstracts/variables" as var;
+
+@font-face {
+ font-display: swap;
+ font-family: Kanit;
+ font-style: normal;
+ font-weight: 700;
+ src: url("../fonts/Kanit/Kanit-Bold.woff2") format("woff2"),
+ url("../fonts/Kanit/Kanit-Bold.woff") format("woff");
+}
+
+@font-face {
+ font-display: swap;
+ font-family: Kanit;
+ font-style: italic;
+ font-weight: 700;
+ src: url("../fonts/Kanit/Kanit-BoldItalic.woff2") format("woff2"),
+ url("../fonts/Kanit/Kanit-BoldItalic.woff") format("woff");
+}
+
+@font-face {
+ font-display: swap;
+ font-family: Kanit;
+ font-style: normal;
+ font-weight: 600;
+ src: url("../fonts/Kanit/Kanit-SemiBold.woff2") format("woff2"),
+ url("../fonts/Kanit/Kanit-SemiBold.woff") format("woff");
+}
+
+@font-face {
+ font-display: swap;
+ font-family: Kanit;
+ font-style: italic;
+ font-weight: 600;
+ src: url("../fonts/Kanit/Kanit-SemiBoldItalic.woff2") format("woff2"),
+ url("../fonts/Kanit/Kanit-SemiBoldItalic.woff") format("woff");
+}
+
+@font-face {
+ font-display: swap;
+ font-family: Kanit;
+ font-style: normal;
+ font-weight: 500;
+ src: url("../fonts/Kanit/Kanit-Medium.woff2") format("woff2"),
+ url("../fonts/Kanit/Kanit-Medium.woff") format("woff");
+}
+
+@font-face {
+ font-display: swap;
+ font-family: Kanit;
+ font-style: italic;
+ font-weight: 500;
+ src: url("../fonts/Kanit/Kanit-MediumItalic.woff2") format("woff2"),
+ url("../fonts/Kanit/Kanit-MediumItalic.woff") format("woff");
+}
+
+@font-face {
+ font-display: swap;
+ font-family: Kanit;
+ font-style: normal;
+ font-weight: 400;
+ src: url("../fonts/Kanit/Kanit-Regular.woff2") format("woff2"),
+ url("../fonts/Kanit/Kanit-Regular.woff") format("woff");
+}
+
+@font-face {
+ font-display: swap;
+ font-family: Kanit;
+ font-style: italic;
+ font-weight: 400;
+ src: url("../fonts/Kanit/Kanit-Italic.woff2") format("woff2"),
+ url("../fonts/Kanit/Kanit-Italic.woff") format("woff");
+}
+
+@font-face {
+ font-display: swap;
+ font-family: Kanit;
+ font-style: normal;
+ font-weight: 300;
+ src: url("../fonts/Kanit/Kanit-Light.woff2") format("woff2"),
+ url("../fonts/Kanit/Kanit-Light.woff") format("woff");
+}
+
+@font-face {
+ font-display: swap;
+ font-family: Kanit;
+ font-style: italic;
+ font-weight: 300;
+ src: url("../fonts/Kanit/Kanit-LightItalic.woff2") format("woff2"),
+ url("../fonts/Kanit/Kanit-LightItalic.woff") format("woff");
+}
+
+@font-face {
+ font-display: swap;
+ font-family: Inter;
+ font-style: oblique 0deg 10deg;
+ font-weight: 100 900;
+ src: url("../fonts/Inter/Inter.woff2?v=3.18") format("woff2");
+}
+
+@include mix.set-vars(
+ (
+ font-family-primary: #{var.$font-family_primary},
+ font-family-secondary: #{var.$font-family_secondary},
+ font-size-sm: #{var.$font-size_sm},
+ font-size-md: #{var.$font-size_md},
+ font-size-lg: #{var.$font-size_lg},
+ font-size-xl: #{var.$font-size_xl},
+ font-size-2xl: #{var.$font-size_2xl},
+ font-size-3xl: #{var.$font-size_3xl},
+ line-height: #{var.$line-height},
+ )
+);
diff --git a/src/scss/base/_helpers.scss b/src/scss/base/_helpers.scss
new file mode 100644
index 0000000..d6a9233
--- /dev/null
+++ b/src/scss/base/_helpers.scss
@@ -0,0 +1,44 @@
+@use "../abstracts/functions" as fun;
+@use "../abstracts/mixins" as mix;
+
+.hide {
+ display: none !important;
+}
+
+/* Text meant only for screen readers. */
+.screen-reader-text {
+ border: 0;
+ clip: rect(1px, 1px, 1px, 1px);
+ height: fun.convert-px(1);
+ overflow: hidden;
+ padding: 0;
+ position: absolute !important;
+ width: fun.convert-px(1);
+ word-break: normal;
+ word-wrap: normal !important; /* Many screen reader and browser combinations announce broken words as they would appear visually. */
+
+ &:focus {
+ background: fun.get-var(color-bg);
+ border: fun.convert-px(3) solid fun.get-var(color-border);
+ box-shadow: 0 0 fun.convert-px(2) fun.convert-px(2)
+ fun.get-var(color-shadow-light);
+ clip: auto !important;
+ color: fun.get-var(color-primary);
+ display: block;
+ font-size: fun.get-var(font-size-md);
+ font-weight: 600;
+ height: auto;
+ left: 0;
+ padding: fun.get-var(spacing-sm) fun.get-var(spacing-md);
+ top: 0;
+ width: auto;
+ z-index: 100000;
+ }
+}
+
+@include mix.motion("reduce") {
+ * {
+ animation: none !important;
+ transition: none !important;
+ }
+}
diff --git a/src/scss/base/_spacings.scss b/src/scss/base/_spacings.scss
new file mode 100644
index 0000000..e908caf
--- /dev/null
+++ b/src/scss/base/_spacings.scss
@@ -0,0 +1,24 @@
+@use "../abstracts/functions" as fun;
+@use "../abstracts/mixins" as mix;
+@use "../abstracts/variables" as var;
+@include mix.set-vars(
+ (
+ spacing-3xs: var.$spacing_3xs,
+ spacing-2xs: var.$spacing_2xs,
+ spacing-xs: var.$spacing_xs,
+ spacing-sm: var.$spacing_sm,
+ spacing-md: var.$spacing_md,
+ spacing-lg: var.$spacing_lg,
+ toolbar-height: fun.convert-px(60),
+ )
+);
+
+@include mix.media("screen") {
+ @include mix.dimensions("lg") {
+ @include mix.set-vars(
+ (
+ toolbar-height: fun.convert-px(0),
+ )
+ );
+ }
+}
diff --git a/src/scss/base/_typography.scss b/src/scss/base/_typography.scss
new file mode 100644
index 0000000..b0504b7
--- /dev/null
+++ b/src/scss/base/_typography.scss
@@ -0,0 +1,48 @@
+@use "../abstracts/functions" as fun;
+
+*::selection {
+ background: fun.get-var(color-primary-light-opacity);
+}
+
+body {
+ background: fun.get-var(color-bg);
+ color: fun.get-var(color-fg);
+ font-size: fun.get-var(font-size-md);
+ line-height: fun.get-var(line-height);
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+ul {
+ margin: 0 0 fun.get-var(spacing-sm);
+}
+
+a {
+ color: fun.get-var(color-primary);
+ text-decoration-thickness: fun.convert-px(2);
+ text-underline-offset: fun.convert-px(3);
+ transition: all 0.3s ease-in-out 0s;
+
+ &:hover,
+ &:focus {
+ color: fun.get-var(color-primary-light);
+ text-decoration-color: fun.get-var(color-primary-light);
+ text-decoration-thickness: fun.convert-px(4);
+ }
+
+ &:focus {
+ outline: fun.get-var(color-primary) dotted fun.convert-px(1);
+ }
+
+ &:active {
+ color: fun.get-var(color-primary-dark);
+ outline: none;
+ text-decoration-color: fun.get-var(color-primary-dark);
+ text-decoration-thickness: fun.convert-px(2);
+ }
+}
diff --git a/src/scss/components/_buttons.scss b/src/scss/components/_buttons.scss
new file mode 100644
index 0000000..bc7959e
--- /dev/null
+++ b/src/scss/components/_buttons.scss
@@ -0,0 +1,21 @@
+@use "../abstracts/functions" as fun;
+
+.btn {
+ background: fun.get-var(color-bg);
+ border: 0;
+ color: fun.get-var(color-primary);
+ cursor: pointer;
+ display: block;
+ font-weight: 600;
+
+ &:hover,
+ &:focus {
+ background: fun.get-var(color-primary);
+ color: fun.get-var(color-fg-inverted);
+ }
+
+ &:active {
+ background: fun.get-var(color-bg);
+ color: fun.get-var(color-primary);
+ }
+}
diff --git a/src/scss/layout/_footer.scss b/src/scss/layout/_footer.scss
new file mode 100644
index 0000000..7dce0dc
--- /dev/null
+++ b/src/scss/layout/_footer.scss
@@ -0,0 +1,40 @@
+@use "../abstracts/functions" as fun;
+@use "../abstracts/mixins" as mix;
+
+.footer {
+ align-items: center;
+ background: fun.get-var(color-bg-secondary);
+ border-top: fun.convert-px(1) solid fun.get-var(color-border-light);
+ display: flex;
+ flex-flow: row wrap;
+ font-family: fun.get-var(font-family-secondary);
+ font-size: fun.get-var(font-size-md);
+ gap: fun.get-var(spacing-3xs);
+ justify-content: center;
+ padding: fun.get-var(spacing-sm) fun.get-var(spacing-md)
+ calc(#{fun.get-var(toolbar-height)} + #{fun.get-var(spacing-sm)});
+
+ @include mix.media("screen") {
+ @include mix.dimensions("lg") {
+ box-shadow: 0 -1px 2px 0 fun.get-var(color-shadow);
+ padding: fun.get-var(spacing-sm) fun.get-var(spacing-md);
+ }
+ }
+
+ .nav {
+ display: inline-flex;
+ gap: fun.get-var(spacing-3xs);
+
+ &::after {
+ content: "/";
+ }
+ }
+}
+
+.copyright {
+ align-items: center;
+ display: flex;
+ flex-flow: row wrap;
+ gap: fun.get-var(spacing-3xs);
+ justify-content: center;
+}
diff --git a/src/scss/layout/_grid.scss b/src/scss/layout/_grid.scss
new file mode 100644
index 0000000..3749678
--- /dev/null
+++ b/src/scss/layout/_grid.scss
@@ -0,0 +1,43 @@
+@use "../abstracts/functions" as fun;
+@use "../abstracts/mixins" as mix;
+
+.body {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ grid-template-rows: minmax(0, 1fr) max-content;
+ height: 100vh;
+ position: relative;
+
+ @include mix.media("screen") {
+ @include mix.dimensions("lg") {
+ grid-template-columns: 1.5fr 4fr;
+ }
+
+ @include mix.dimensions("xl") {
+ grid-template-columns: 1fr 4fr;
+ }
+ }
+}
+
+.header {
+ grid-column: 1;
+ grid-row: 1;
+ width: 100%;
+}
+
+.main {
+ grid-column: 1;
+ grid-row: 1 / -1;
+
+ @include mix.media("screen") {
+ @include mix.dimensions("lg") {
+ grid-column: 2;
+ }
+ }
+}
+
+.footer {
+ grid-column: 1;
+ grid-row: 2;
+ width: 100%;
+}
diff --git a/src/scss/layout/_header.scss b/src/scss/layout/_header.scss
new file mode 100644
index 0000000..cbc1693
--- /dev/null
+++ b/src/scss/layout/_header.scss
@@ -0,0 +1,143 @@
+@use "../abstracts/functions" as fun;
+@use "../abstracts/mixins" as mix;
+
+.header {
+ background: fun.get-var(color-bg-secondary);
+ overflow-y: auto;
+ padding: fun.get-var(spacing-md)
+ clamp(#{fun.get-var(spacing-md)}, 3vw, #{fun.get-var(spacing-lg)});
+ scrollbar-color: fun.get-var(color-primary-light-opacity)
+ fun.get-var(color-bg-tertiary);
+ z-index: 5;
+
+ @include mix.media("screen") {
+ @include mix.dimensions("lg") {
+ box-shadow: 0 -1px 2px 0 fun.get-var(color-shadow);
+ }
+ }
+}
+
+.branding {
+ margin-bottom: clamp(
+ #{fun.get-var(spacing-sm)},
+ 3vw,
+ #{fun.get-var(spacing-md)}
+ );
+ text-align: center;
+
+ &__title {
+ font-family: fun.get-var(font-family-secondary);
+ font-size: clamp(
+ #{fun.get-var(font-size-2xl)},
+ 5vw,
+ #{fun.get-var(font-size-3xl)}
+ );
+ font-weight: 500;
+ margin: fun.get-var(spacing-xs) 0 fun.get-var(spacing-3xs);
+ }
+
+ &__link {
+ background: linear-gradient(
+ to top,
+ fun.get-var(color-primary-light) fun.convert-px(5),
+ transparent fun.convert-px(5)
+ )
+ center / 0 100% no-repeat;
+ text-decoration: none;
+ transition: all 0.5s ease-in-out 0s;
+
+ &:hover,
+ &:focus {
+ background-size: 100% 100%;
+ }
+
+ &:active {
+ background-size: 0 100%;
+ }
+ }
+
+ &__description {
+ font-family: fun.get-var(font-family-secondary);
+ font-size: clamp(
+ #{fun.get-var(font-size-md)},
+ 3vw,
+ #{fun.get-var(font-size-lg)}
+ );
+ font-weight: 400;
+ letter-spacing: fun.convert-px(1);
+ margin: 0;
+ text-transform: uppercase;
+ }
+}
+
+.logo {
+ margin: auto;
+ position: relative;
+ width: max-content;
+
+ &__image {
+ backface-visibility: hidden;
+ border: fun.convert-px(3) solid fun.get-var(color-border-light);
+ border-radius: 50%;
+ box-shadow: 0 0 fun.convert-px(6) fun.convert-px(1)
+ fun.get-var(color-shadow-darker);
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+
+ &--back {
+ transform: rotateY(180deg);
+ }
+ }
+
+ &__link {
+ display: block;
+ height: clamp(#{fun.convert-px(75)}, 15vmin, #{fun.convert-px(90)});
+ transform-style: preserve-3d;
+ transition: all 0.6s linear 0s;
+ width: clamp(#{fun.convert-px(75)}, 15vmin, #{fun.convert-px(90)});
+
+ &:hover,
+ &:focus {
+ outline: none;
+ transform: rotateY(180deg);
+ }
+
+ &:hover &,
+ &:focus & {
+ &__image {
+ &--front {
+ transform: none;
+ }
+
+ &--back {
+ transform: rotateY(180deg);
+ }
+ }
+ }
+
+ &:focus & {
+ &__image {
+ box-shadow: 0 0 fun.convert-px(6) fun.convert-px(1)
+ fun.get-var(color-shadow-dark),
+ 0 0 0 fun.convert-px(5) fun.get-var(color-primary-light-opacity);
+ outline: none;
+ }
+ }
+
+ &:active & {
+ &__image {
+ box-shadow: 0 0 fun.convert-px(6) fun.convert-px(1)
+ fun.get-var(color-shadow-dark),
+ 0 0 0 fun.convert-px(7) fun.get-var(color-primary-light-opacity);
+ }
+ }
+ }
+
+ &:hover & {
+ &__link {
+ transform: rotateY(180deg);
+ }
+ }
+}
diff --git a/src/scss/layout/_main.scss b/src/scss/layout/_main.scss
new file mode 100644
index 0000000..8ab842b
--- /dev/null
+++ b/src/scss/layout/_main.scss
@@ -0,0 +1,138 @@
+@use "../abstracts/functions" as fun;
+@use "../abstracts/mixins" as mix;
+@use "../abstracts/placeholders";
+
+.main {
+ display: flex;
+ flex-flow: column nowrap;
+ height: calc(100% - #{fun.get-var(toolbar-height)});
+
+ @include mix.media("screen") {
+ @include mix.dimensions("lg") {
+ display: grid;
+ grid-template-columns: 5fr 2fr;
+ }
+
+ @include mix.dimensions("xl") {
+ grid-template-columns: 4fr 1fr;
+ }
+ }
+}
+
+// NoScript extension seems to replace noscript tag with a span.
+.main > span,
+.instructions,
+noscript {
+ background: fun.get-var(color-bg);
+ padding: fun.get-var(spacing-md);
+ text-align: center;
+
+ @include mix.media("screen") {
+ @include mix.dimensions("lg") {
+ grid-column: 1 / -1;
+ }
+ }
+}
+
+.instructions {
+ align-items: center;
+ display: flex;
+ justify-content: center;
+}
+
+.legal-notice {
+ height: 100%;
+ overflow-y: auto;
+ padding: clamp(#{fun.get-var(spacing-md)}, 3vw, #{fun.get-var(spacing-lg)});
+ scrollbar-color: fun.get-var(color-primary-light-opacity)
+ fun.get-var(color-bg-tertiary);
+ width: 100%;
+
+ @include mix.media("screen") {
+ @include mix.dimensions("lg") {
+ grid-column: 1 / -1;
+ }
+ }
+}
+
+.project-preview {
+ background: fun.get-var(color-bg);
+ flex: 0 1 100%;
+ min-height: 0;
+ width: 100%;
+
+ @include mix.media("screen") {
+ @include mix.dimensions("lg") {
+ height: 100%;
+ }
+ }
+
+ iframe {
+ border: 0;
+ height: 100%;
+ width: 100%;
+ }
+}
+
+.project-details {
+ background: fun.get-var(color-bg-secondary);
+ box-shadow: 0 -1px 2px 0 fun.get-var(color-shadow);
+ flex: 1 0 100%;
+ overflow-y: auto;
+ padding: fun.get-var(spacing-md);
+ scrollbar-color: fun.get-var(color-primary-light-opacity)
+ fun.get-var(color-bg-tertiary);
+
+ @include mix.media("screen") {
+ @include mix.dimensions("lg") {
+ font-size: fun.get-var(font-size-md);
+ }
+ }
+
+ &__description {
+ margin-bottom: fun.get-var(spacing-md);
+ white-space: pre-wrap;
+ }
+
+ .list {
+ &--tech {
+ padding-left: fun.get-var(spacing-sm);
+ }
+
+ &--repos {
+ @extend %flex-list;
+
+ gap: fun.get-var(spacing-xs);
+ }
+
+ &__link {
+ background-repeat: no-repeat;
+ background-size: contain;
+ box-shadow: 0 0 0 0 fun.get-var(color-shadow);
+ display: block;
+ height: fun.convert-px(50);
+ transition: transform 0.3s ease-in-out 0s,
+ box-shadow 0.15s ease-in-out 0.15s;
+ width: fun.convert-px(50);
+
+ &--github {
+ background: url(#{fun.encode-svg('<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path style="fill:#1b1817;fill-opacity:1;opacity:1;stroke-width:6.55748" d="M0 0h100v100H0z"/><path d="M50.003 15.864C69.33 15.864 85 31.534 85 50.865c0 15.464-10.028 28.581-23.936 33.211-1.75.32-2.389-.76-2.389-1.689 0-.829.03-3.031.047-5.951 9.736 2.114 11.79-4.693 11.79-4.693 1.592-4.044 3.887-5.12 3.887-5.12 3.178-2.17-.241-2.127-.241-2.127-3.513.247-5.36 3.607-5.36 3.607-3.123 5.348-8.193 3.803-10.187 2.907-.318-2.26-1.22-3.803-2.222-4.677 7.772-.883 15.943-3.887 15.943-17.299 0-3.82-1.365-6.944-3.604-9.391.361-.886 1.563-4.444-.341-9.263 0 0-2.94-.941-9.626 3.588-2.791-.778-5.786-1.164-8.762-1.177-2.972.013-5.967.4-8.762 1.177-6.682-4.53-9.615-3.588-9.615-3.588-1.91 4.82-.71 8.377-.348 9.263-2.243 2.447-3.6 5.571-3.6 9.391 0 13.446 8.185 16.405 15.982 17.271-1.257 1.08-2.376 3.216-2.376 6.482 0 4.678.043 8.453.043 9.6 0 .937-.63 2.027-2.407 1.685C25.02 79.433 15 66.324 15 50.865c0-19.331 15.672-35.001 35.003-35.001" style="fill:#fff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:.999999"/></svg>')});
+ }
+
+ &--gitlab {
+ background: url(#{fun.encode-svg('<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path style="opacity:1;fill:#ccc;fill-opacity:1;stroke-width:7.26231" d="M0 0h100v100H0z"/><path class="st3" d="m49.998 82.237 12.895-39.668H37.121Z" style="fill:#e24329;stroke-width:1"/><path class="st4" d="M19.058 42.57 15.13 54.62a2.67 2.67 0 0 0 .966 2.984l33.9 24.632Z" style="fill:#fca326;stroke-width:1"/><path class="st3" d="M19.058 42.57H37.12l-7.774-23.89c-.398-1.222-2.132-1.222-2.544 0z" style="fill:#e24329;stroke-width:1"/><path class="st4" d="m80.956 42.57 3.914 12.05a2.67 2.67 0 0 1-.966 2.984L49.998 82.237Z" style="fill:#fca326;stroke-width:1"/><path class="st3" d="M80.956 42.57H62.894l7.759-23.89c.398-1.222 2.132-1.222 2.544 0z" style="fill:#e24329;stroke-width:1"/><path style="fill:#fc6d26;stroke-width:4.82321" class="st5" d="m50.017 82.237 30.94-39.667H62.893Zm-.019 0L19.058 42.57H37.12z"/></svg>')});
+ }
+
+ &:hover,
+ &:focus {
+ box-shadow: fun.convert-px(-1) fun.convert-px(1) fun.convert-px(4)
+ fun.convert-px(2) fun.get-var(color-shadow-light);
+ transform: scale(1.15);
+ }
+
+ &:active {
+ opacity: 1;
+ }
+ }
+ }
+}
diff --git a/src/scss/layout/_nav.scss b/src/scss/layout/_nav.scss
new file mode 100644
index 0000000..98e4cb5
--- /dev/null
+++ b/src/scss/layout/_nav.scss
@@ -0,0 +1,70 @@
+@use "../abstracts/functions" as fun;
+@use "../abstracts/placeholders";
+
+.nav {
+ text-align: center;
+
+ &__label {
+ font-weight: 600;
+ }
+
+ &__list {
+ @extend %reset-list;
+
+ .btn {
+ width: 100%;
+ }
+ }
+
+ &:not(&--footer) &__item {
+ margin: fun.get-var(spacing-2xs) 0;
+ }
+
+ &:not(&--footer) &__link {
+ background-image: linear-gradient(
+ to left,
+ #{fun.get-var(color-bg)} 0,
+ #{fun.get-var(color-bg)} 50%,
+ #{fun.get-var(color-primary)} 50%
+ );
+ background-position: 100% 0;
+ background-size: 200% 100%;
+ border: fun.convert-px(3) solid fun.get-var(color-border);
+ border-radius: fun.convert-px(50);
+ display: block;
+ font-weight: 600;
+ margin: auto;
+ padding: fun.get-var(spacing-3xs);
+ position: relative;
+ text-decoration: none;
+ transition: all 0.4s ease-in-out 0s;
+ width: 75%;
+
+ &:hover,
+ &:focus {
+ background-position: 0 0;
+ color: fun.get-var(color-fg-inverted);
+ }
+
+ &:active {
+ background-position: 100% 0;
+ color: fun.get-var(color-primary-dark);
+ text-decoration: fun.convert-px(1) solid underline;
+ }
+
+ &--selected {
+ background: fun.get-var(color-primary-dark);
+ box-shadow: inset 0 0 0 4px fun.get-var(color-bg);
+ color: fun.get-var(color-fg-inverted);
+
+ &:hover,
+ &:focus {
+ background: fun.get-var(color-primary-light);
+ }
+ }
+ }
+
+ .btn {
+ margin: auto;
+ }
+}
diff --git a/src/scss/layout/_toolbar.scss b/src/scss/layout/_toolbar.scss
new file mode 100644
index 0000000..00e2bd7
--- /dev/null
+++ b/src/scss/layout/_toolbar.scss
@@ -0,0 +1,34 @@
+@use "../abstracts/functions" as fun;
+
+.toolbar {
+ align-items: center;
+ background: fun.get-var(color-primary);
+ bottom: 0;
+ box-shadow: 0 -1px 2px 0 fun.get-var(color-shadow-dark);
+ color: fun.get-var(color-fg-inverted);
+ display: flex;
+ flex-flow: row nowrap;
+ gap: fun.get-var(spacing-xs);
+ height: fun.get-var(toolbar-height);
+ justify-content: center;
+ left: 0;
+ padding: 0 fun.get-var(spacing-sm);
+ position: absolute;
+ right: 0;
+ z-index: 2;
+
+ & > &__options {
+ background: fun.get-var(color-primary);
+ color: fun.get-var(color-fg-inverted);
+ font-size: fun.get-var(font-size-sm);
+ height: 85%;
+ line-height: inherit;
+ padding: 0 fun.get-var(spacing-xs);
+
+ &:hover,
+ &:focus {
+ background: fun.get-var(color-bg);
+ color: fun.get-var(color-primary);
+ }
+ }
+}
diff --git a/src/scss/style.scss b/src/scss/style.scss
new file mode 100644
index 0000000..96c8fe5
--- /dev/null
+++ b/src/scss/style.scss
@@ -0,0 +1,40 @@
+@charset 'utf-8';
+
+/**
+ * 1.0 Vendors
+ *
+ * Import each files separately to define vendors styles order.
+ */
+@use "modern-normalize";
+
+/**
+ * 2.0 Base
+ *
+ * Define some standard styles and CSS variables (colors, fonts...).
+ */
+@use "base/base";
+@use "base/animations";
+@use "base/colors";
+@use "base/fonts";
+@use "base/helpers";
+@use "base/spacings";
+@use "base/typography";
+
+/**
+ * 3.0 Layout
+ *
+ * Define website layout.
+ */
+@use "layout/grid";
+@use "layout/header";
+@use "layout/main";
+@use "layout/footer";
+@use "layout/toolbar";
+@use "layout/nav";
+
+/**
+* 4.0 Components
+*
+* Define styles for all kind of specific modules like buttons, widgets...
+*/
+@use "components/buttons";