From 3a3baddad1c801d77dc398d2c6980f3c14f4a47c Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 30 Oct 2021 22:11:00 +0200 Subject: chore: move htdocs to repo root --- src/fonts/Inter/Inter.woff2 | Bin 0 -> 324864 bytes src/fonts/Inter/LICENSE.txt | 94 ++++++ src/fonts/Kanit/Kanit-Bold.woff | Bin 0 -> 73508 bytes src/fonts/Kanit/Kanit-Bold.woff2 | Bin 0 -> 50480 bytes src/fonts/Kanit/Kanit-BoldItalic.woff | Bin 0 -> 79120 bytes src/fonts/Kanit/Kanit-BoldItalic.woff2 | Bin 0 -> 53888 bytes src/fonts/Kanit/Kanit-Italic.woff | Bin 0 -> 76092 bytes src/fonts/Kanit/Kanit-Italic.woff2 | Bin 0 -> 51768 bytes src/fonts/Kanit/Kanit-Light.woff | Bin 0 -> 71864 bytes src/fonts/Kanit/Kanit-Light.woff2 | Bin 0 -> 49256 bytes src/fonts/Kanit/Kanit-LightItalic.woff | Bin 0 -> 75752 bytes src/fonts/Kanit/Kanit-LightItalic.woff2 | Bin 0 -> 51788 bytes src/fonts/Kanit/Kanit-Medium.woff | Bin 0 -> 73260 bytes src/fonts/Kanit/Kanit-Medium.woff2 | Bin 0 -> 50200 bytes src/fonts/Kanit/Kanit-MediumItalic.woff | Bin 0 -> 75980 bytes src/fonts/Kanit/Kanit-MediumItalic.woff2 | Bin 0 -> 51752 bytes src/fonts/Kanit/Kanit-Regular.woff | Bin 0 -> 72792 bytes src/fonts/Kanit/Kanit-Regular.woff2 | Bin 0 -> 49980 bytes src/fonts/Kanit/Kanit-SemiBold.woff | Bin 0 -> 73472 bytes src/fonts/Kanit/Kanit-SemiBold.woff2 | Bin 0 -> 50748 bytes src/fonts/Kanit/Kanit-SemiBoldItalic.woff | Bin 0 -> 76072 bytes src/fonts/Kanit/Kanit-SemiBoldItalic.woff2 | Bin 0 -> 51896 bytes src/fonts/Kanit/OFL.txt | 93 ++++++ src/images/armand-philippot-logo.svg | 170 +++++++++++ src/images/armand-philippot.jpg | Bin 0 -> 28584 bytes src/images/cc-by-sa.svg | 79 +++++ src/images/favicon/android-chrome-192x192.png | Bin 0 -> 5576 bytes src/images/favicon/android-chrome-512x512.png | Bin 0 -> 14971 bytes src/images/favicon/apple-touch-icon.png | Bin 0 -> 5270 bytes src/images/favicon/browserconfig.xml | 9 + src/images/favicon/favicon-16x16.png | Bin 0 -> 774 bytes src/images/favicon/favicon-32x32.png | Bin 0 -> 1093 bytes src/images/favicon/favicon.ico | Bin 0 -> 7406 bytes src/images/favicon/mstile-144x144.png | Bin 0 -> 4236 bytes src/images/favicon/mstile-150x150.png | Bin 0 -> 4258 bytes src/images/favicon/mstile-310x150.png | Bin 0 -> 4708 bytes src/images/favicon/mstile-310x310.png | Bin 0 -> 8861 bytes src/images/favicon/mstile-70x70.png | Bin 0 -> 3034 bytes src/images/favicon/safari-pinned-tab.svg | 25 ++ src/images/favicon/site.webmanifest | 19 ++ src/images/github.svg | 69 +++++ src/images/gitlab.svg | 93 ++++++ src/js/app.js | 382 +++++++++++++++++++++++++ src/js/config/projects.js | 224 +++++++++++++++ src/js/i18n/i18n.js | 42 +++ src/js/i18n/locales/en.js | 36 +++ src/js/i18n/locales/fr.js | 36 +++ src/js/utilities/animations.js | 45 +++ src/js/utilities/helpers.js | 19 ++ src/scss/abstracts/_functions.scss | 4 + src/scss/abstracts/_mixins.scss | 2 + src/scss/abstracts/_placeholders.scss | 32 +++ src/scss/abstracts/_variables.scss | 103 +++++++ src/scss/abstracts/functions/_convert.scss | 16 ++ src/scss/abstracts/functions/_css-vars.scss | 8 + src/scss/abstracts/functions/_encode.scss | 14 + src/scss/abstracts/functions/_str-replace.scss | 20 ++ src/scss/abstracts/mixins/_css-vars.scss | 20 ++ src/scss/abstracts/mixins/_media-queries.scss | 81 ++++++ src/scss/base/_animations.scss | 89 ++++++ src/scss/base/_base.scss | 3 + src/scss/base/_colors.scss | 21 ++ src/scss/base/_fonts.scss | 115 ++++++++ src/scss/base/_helpers.scss | 44 +++ src/scss/base/_spacings.scss | 24 ++ src/scss/base/_typography.scss | 48 ++++ src/scss/components/_buttons.scss | 21 ++ src/scss/layout/_footer.scss | 40 +++ src/scss/layout/_grid.scss | 43 +++ src/scss/layout/_header.scss | 143 +++++++++ src/scss/layout/_main.scss | 138 +++++++++ src/scss/layout/_nav.scss | 70 +++++ src/scss/layout/_toolbar.scss | 34 +++ src/scss/style.scss | 40 +++ 74 files changed, 2608 insertions(+) create mode 100644 src/fonts/Inter/Inter.woff2 create mode 100644 src/fonts/Inter/LICENSE.txt create mode 100644 src/fonts/Kanit/Kanit-Bold.woff create mode 100644 src/fonts/Kanit/Kanit-Bold.woff2 create mode 100644 src/fonts/Kanit/Kanit-BoldItalic.woff create mode 100644 src/fonts/Kanit/Kanit-BoldItalic.woff2 create mode 100644 src/fonts/Kanit/Kanit-Italic.woff create mode 100644 src/fonts/Kanit/Kanit-Italic.woff2 create mode 100644 src/fonts/Kanit/Kanit-Light.woff create mode 100644 src/fonts/Kanit/Kanit-Light.woff2 create mode 100644 src/fonts/Kanit/Kanit-LightItalic.woff create mode 100644 src/fonts/Kanit/Kanit-LightItalic.woff2 create mode 100644 src/fonts/Kanit/Kanit-Medium.woff create mode 100644 src/fonts/Kanit/Kanit-Medium.woff2 create mode 100644 src/fonts/Kanit/Kanit-MediumItalic.woff create mode 100644 src/fonts/Kanit/Kanit-MediumItalic.woff2 create mode 100644 src/fonts/Kanit/Kanit-Regular.woff create mode 100644 src/fonts/Kanit/Kanit-Regular.woff2 create mode 100644 src/fonts/Kanit/Kanit-SemiBold.woff create mode 100644 src/fonts/Kanit/Kanit-SemiBold.woff2 create mode 100644 src/fonts/Kanit/Kanit-SemiBoldItalic.woff create mode 100644 src/fonts/Kanit/Kanit-SemiBoldItalic.woff2 create mode 100644 src/fonts/Kanit/OFL.txt create mode 100644 src/images/armand-philippot-logo.svg create mode 100644 src/images/armand-philippot.jpg create mode 100644 src/images/cc-by-sa.svg create mode 100644 src/images/favicon/android-chrome-192x192.png create mode 100644 src/images/favicon/android-chrome-512x512.png create mode 100644 src/images/favicon/apple-touch-icon.png create mode 100644 src/images/favicon/browserconfig.xml create mode 100644 src/images/favicon/favicon-16x16.png create mode 100644 src/images/favicon/favicon-32x32.png create mode 100644 src/images/favicon/favicon.ico create mode 100644 src/images/favicon/mstile-144x144.png create mode 100644 src/images/favicon/mstile-150x150.png create mode 100644 src/images/favicon/mstile-310x150.png create mode 100644 src/images/favicon/mstile-310x310.png create mode 100644 src/images/favicon/mstile-70x70.png create mode 100644 src/images/favicon/safari-pinned-tab.svg create mode 100644 src/images/favicon/site.webmanifest create mode 100644 src/images/github.svg create mode 100644 src/images/gitlab.svg create mode 100644 src/js/app.js create mode 100644 src/js/config/projects.js create mode 100644 src/js/i18n/i18n.js create mode 100644 src/js/i18n/locales/en.js create mode 100644 src/js/i18n/locales/fr.js create mode 100644 src/js/utilities/animations.js create mode 100644 src/js/utilities/helpers.js create mode 100644 src/scss/abstracts/_functions.scss create mode 100644 src/scss/abstracts/_mixins.scss create mode 100644 src/scss/abstracts/_placeholders.scss create mode 100644 src/scss/abstracts/_variables.scss create mode 100644 src/scss/abstracts/functions/_convert.scss create mode 100644 src/scss/abstracts/functions/_css-vars.scss create mode 100644 src/scss/abstracts/functions/_encode.scss create mode 100644 src/scss/abstracts/functions/_str-replace.scss create mode 100644 src/scss/abstracts/mixins/_css-vars.scss create mode 100644 src/scss/abstracts/mixins/_media-queries.scss create mode 100644 src/scss/base/_animations.scss create mode 100644 src/scss/base/_base.scss create mode 100644 src/scss/base/_colors.scss create mode 100644 src/scss/base/_fonts.scss create mode 100644 src/scss/base/_helpers.scss create mode 100644 src/scss/base/_spacings.scss create mode 100644 src/scss/base/_typography.scss create mode 100644 src/scss/components/_buttons.scss create mode 100644 src/scss/layout/_footer.scss create mode 100644 src/scss/layout/_grid.scss create mode 100644 src/scss/layout/_header.scss create mode 100644 src/scss/layout/_main.scss create mode 100644 src/scss/layout/_nav.scss create mode 100644 src/scss/layout/_toolbar.scss create mode 100644 src/scss/style.scss (limited to 'src') diff --git a/src/fonts/Inter/Inter.woff2 b/src/fonts/Inter/Inter.woff2 new file mode 100644 index 0000000..365eedc Binary files /dev/null and b/src/fonts/Inter/Inter.woff2 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 Binary files /dev/null and b/src/fonts/Kanit/Kanit-Bold.woff differ diff --git a/src/fonts/Kanit/Kanit-Bold.woff2 b/src/fonts/Kanit/Kanit-Bold.woff2 new file mode 100644 index 0000000..8494234 Binary files /dev/null and b/src/fonts/Kanit/Kanit-Bold.woff2 differ diff --git a/src/fonts/Kanit/Kanit-BoldItalic.woff b/src/fonts/Kanit/Kanit-BoldItalic.woff new file mode 100644 index 0000000..af36319 Binary files /dev/null and b/src/fonts/Kanit/Kanit-BoldItalic.woff differ diff --git a/src/fonts/Kanit/Kanit-BoldItalic.woff2 b/src/fonts/Kanit/Kanit-BoldItalic.woff2 new file mode 100644 index 0000000..61f04ef Binary files /dev/null and b/src/fonts/Kanit/Kanit-BoldItalic.woff2 differ diff --git a/src/fonts/Kanit/Kanit-Italic.woff b/src/fonts/Kanit/Kanit-Italic.woff new file mode 100644 index 0000000..462f295 Binary files /dev/null and b/src/fonts/Kanit/Kanit-Italic.woff differ diff --git a/src/fonts/Kanit/Kanit-Italic.woff2 b/src/fonts/Kanit/Kanit-Italic.woff2 new file mode 100644 index 0000000..1723709 Binary files /dev/null and b/src/fonts/Kanit/Kanit-Italic.woff2 differ diff --git a/src/fonts/Kanit/Kanit-Light.woff b/src/fonts/Kanit/Kanit-Light.woff new file mode 100644 index 0000000..872ce1b Binary files /dev/null and b/src/fonts/Kanit/Kanit-Light.woff differ diff --git a/src/fonts/Kanit/Kanit-Light.woff2 b/src/fonts/Kanit/Kanit-Light.woff2 new file mode 100644 index 0000000..6b35eda Binary files /dev/null and b/src/fonts/Kanit/Kanit-Light.woff2 differ diff --git a/src/fonts/Kanit/Kanit-LightItalic.woff b/src/fonts/Kanit/Kanit-LightItalic.woff new file mode 100644 index 0000000..b81b7b3 Binary files /dev/null and b/src/fonts/Kanit/Kanit-LightItalic.woff differ diff --git a/src/fonts/Kanit/Kanit-LightItalic.woff2 b/src/fonts/Kanit/Kanit-LightItalic.woff2 new file mode 100644 index 0000000..f933a2c Binary files /dev/null and b/src/fonts/Kanit/Kanit-LightItalic.woff2 differ diff --git a/src/fonts/Kanit/Kanit-Medium.woff b/src/fonts/Kanit/Kanit-Medium.woff new file mode 100644 index 0000000..d5b3510 Binary files /dev/null and b/src/fonts/Kanit/Kanit-Medium.woff differ diff --git a/src/fonts/Kanit/Kanit-Medium.woff2 b/src/fonts/Kanit/Kanit-Medium.woff2 new file mode 100644 index 0000000..a012e98 Binary files /dev/null and b/src/fonts/Kanit/Kanit-Medium.woff2 differ diff --git a/src/fonts/Kanit/Kanit-MediumItalic.woff b/src/fonts/Kanit/Kanit-MediumItalic.woff new file mode 100644 index 0000000..6859f58 Binary files /dev/null and b/src/fonts/Kanit/Kanit-MediumItalic.woff differ diff --git a/src/fonts/Kanit/Kanit-MediumItalic.woff2 b/src/fonts/Kanit/Kanit-MediumItalic.woff2 new file mode 100644 index 0000000..8af9298 Binary files /dev/null and b/src/fonts/Kanit/Kanit-MediumItalic.woff2 differ diff --git a/src/fonts/Kanit/Kanit-Regular.woff b/src/fonts/Kanit/Kanit-Regular.woff new file mode 100644 index 0000000..eec0b42 Binary files /dev/null and b/src/fonts/Kanit/Kanit-Regular.woff differ diff --git a/src/fonts/Kanit/Kanit-Regular.woff2 b/src/fonts/Kanit/Kanit-Regular.woff2 new file mode 100644 index 0000000..9b925bf Binary files /dev/null and b/src/fonts/Kanit/Kanit-Regular.woff2 differ diff --git a/src/fonts/Kanit/Kanit-SemiBold.woff b/src/fonts/Kanit/Kanit-SemiBold.woff new file mode 100644 index 0000000..dacdb5b Binary files /dev/null and b/src/fonts/Kanit/Kanit-SemiBold.woff differ diff --git a/src/fonts/Kanit/Kanit-SemiBold.woff2 b/src/fonts/Kanit/Kanit-SemiBold.woff2 new file mode 100644 index 0000000..52d4527 Binary files /dev/null and b/src/fonts/Kanit/Kanit-SemiBold.woff2 differ diff --git a/src/fonts/Kanit/Kanit-SemiBoldItalic.woff b/src/fonts/Kanit/Kanit-SemiBoldItalic.woff new file mode 100644 index 0000000..ebf0137 Binary files /dev/null and b/src/fonts/Kanit/Kanit-SemiBoldItalic.woff differ diff --git a/src/fonts/Kanit/Kanit-SemiBoldItalic.woff2 b/src/fonts/Kanit/Kanit-SemiBoldItalic.woff2 new file mode 100644 index 0000000..300d77a Binary files /dev/null and b/src/fonts/Kanit/Kanit-SemiBoldItalic.woff2 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 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/armand-philippot.jpg b/src/images/armand-philippot.jpg new file mode 100644 index 0000000..2c8ef50 Binary files /dev/null and b/src/images/armand-philippot.jpg 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 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + 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 Binary files /dev/null and b/src/images/favicon/android-chrome-192x192.png 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 Binary files /dev/null and b/src/images/favicon/android-chrome-512x512.png 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 Binary files /dev/null and b/src/images/favicon/apple-touch-icon.png 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 @@ + + + + + + #2b5797 + + + diff --git a/src/images/favicon/favicon-16x16.png b/src/images/favicon/favicon-16x16.png new file mode 100644 index 0000000..525a1b3 Binary files /dev/null and b/src/images/favicon/favicon-16x16.png differ diff --git a/src/images/favicon/favicon-32x32.png b/src/images/favicon/favicon-32x32.png new file mode 100644 index 0000000..c4b80ad Binary files /dev/null and b/src/images/favicon/favicon-32x32.png differ diff --git a/src/images/favicon/favicon.ico b/src/images/favicon/favicon.ico new file mode 100644 index 0000000..d1d41a8 Binary files /dev/null and b/src/images/favicon/favicon.ico differ diff --git a/src/images/favicon/mstile-144x144.png b/src/images/favicon/mstile-144x144.png new file mode 100644 index 0000000..f68a989 Binary files /dev/null and b/src/images/favicon/mstile-144x144.png differ diff --git a/src/images/favicon/mstile-150x150.png b/src/images/favicon/mstile-150x150.png new file mode 100644 index 0000000..b36424a Binary files /dev/null and b/src/images/favicon/mstile-150x150.png differ diff --git a/src/images/favicon/mstile-310x150.png b/src/images/favicon/mstile-310x150.png new file mode 100644 index 0000000..f395692 Binary files /dev/null and b/src/images/favicon/mstile-310x150.png differ diff --git a/src/images/favicon/mstile-310x310.png b/src/images/favicon/mstile-310x310.png new file mode 100644 index 0000000..3c7fd66 Binary files /dev/null and b/src/images/favicon/mstile-310x310.png differ diff --git a/src/images/favicon/mstile-70x70.png b/src/images/favicon/mstile-70x70.png new file mode 100644 index 0000000..12d226b Binary files /dev/null and b/src/images/favicon/mstile-70x70.png 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 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + 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 @@ + + + + + + + + image/svg+xml + + + + + + + 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 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + 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 (`...`). +/// @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('')}); + } + + &--gitlab { + background: url(#{fun.encode-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"; -- cgit v1.2.3