diff --git a/docs/design-system.md b/docs/design-system.md index f91e605e..5052b721 100644 --- a/docs/design-system.md +++ b/docs/design-system.md @@ -101,6 +101,8 @@ All text-on-surface combinations meet WCAG AA at minimum. Parchment on Obsidian | Inter | Body, UI, labels | Google Fonts | | JetBrains Mono | Code, terminal output, monospaced data | Google Fonts | +All three are served by the app itself, from `frontend/src/assets/fonts/`, with every subset Google Fonts publishes for them. A page never asks a font host for anything, so an instance with no internet still draws in its own type. They are under the SIL Open Font License; the licences sit beside the files. + ### Why this pairing Fraunces is a contemporary serif with personality — it has the warmth and authority of a book serif without feeling like costume. It signals "this is considered" without signaling "this is a fantasy product." Inter is the workhorse — neutral, ubiquitous, designed for screens, doesn't compete with the serif. JetBrains Mono is the natural choice for any developer-adjacent product and supports ligatures. diff --git a/frontend/src/assets/fonts/OFL-fraunces.txt b/frontend/src/assets/fonts/OFL-fraunces.txt new file mode 100644 index 00000000..22fa7ce5 --- /dev/null +++ b/frontend/src/assets/fonts/OFL-fraunces.txt @@ -0,0 +1,93 @@ +Copyright 2018 The Fraunces Project Authors (https://github.com/undercasetype/Fraunces) + +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/frontend/src/assets/fonts/OFL-inter.txt b/frontend/src/assets/fonts/OFL-inter.txt new file mode 100644 index 00000000..21f6aff9 --- /dev/null +++ b/frontend/src/assets/fonts/OFL-inter.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (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: +https://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/frontend/src/assets/fonts/OFL-jetbrainsmono.txt b/frontend/src/assets/fonts/OFL-jetbrainsmono.txt new file mode 100644 index 00000000..821a3dac --- /dev/null +++ b/frontend/src/assets/fonts/OFL-jetbrainsmono.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +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: https://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/frontend/src/assets/fonts/fonts.css b/frontend/src/assets/fonts/fonts.css new file mode 100644 index 00000000..5150e894 --- /dev/null +++ b/frontend/src/assets/fonts/fonts.css @@ -0,0 +1,401 @@ +/* The three typefaces the design system names: Fraunces for display, Inter for + body text, JetBrains Mono for code. They are served from the app itself, so a + page never asks a font host for anything and an instance with no internet + still draws in its own type. + + Every subset Google Fonts publishes for these families is here, not only + latin. Each face carries a unicode-range, so a browser downloads only the + subsets the page's text uses. + + All three are under the SIL Open Font License 1.1; the OFL-*.txt files beside + this one are their licences. */ + +/* Fraunces, italic, vietnamese */ +@font-face { + font-family: 'Fraunces'; + font-style: italic; + font-weight: 300 900; + font-display: swap; + src: url(./fraunces-italic-vietnamese.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} + +/* Fraunces, italic, latin-ext */ +@font-face { + font-family: 'Fraunces'; + font-style: italic; + font-weight: 300 900; + font-display: swap; + src: url(./fraunces-italic-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* Fraunces, italic, latin */ +@font-face { + font-family: 'Fraunces'; + font-style: italic; + font-weight: 300 900; + font-display: swap; + src: url(./fraunces-italic-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* Fraunces, normal, vietnamese */ +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 300 900; + font-display: swap; + src: url(./fraunces-normal-vietnamese.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} + +/* Fraunces, normal, latin-ext */ +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 300 900; + font-display: swap; + src: url(./fraunces-normal-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* Fraunces, normal, latin */ +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 300 900; + font-display: swap; + src: url(./fraunces-normal-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* Inter, italic, cyrillic-ext */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./inter-italic-cyrillic-ext.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} + +/* Inter, italic, cyrillic */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./inter-italic-cyrillic.woff2) format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +/* Inter, italic, greek-ext */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./inter-italic-greek-ext.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} + +/* Inter, italic, greek */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./inter-italic-greek.woff2) format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} + +/* Inter, italic, vietnamese */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./inter-italic-vietnamese.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} + +/* Inter, italic, latin-ext */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./inter-italic-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* Inter, italic, latin */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./inter-italic-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* Inter, normal, cyrillic-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./inter-normal-cyrillic-ext.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} + +/* Inter, normal, cyrillic */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./inter-normal-cyrillic.woff2) format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +/* Inter, normal, greek-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./inter-normal-greek-ext.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} + +/* Inter, normal, greek */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./inter-normal-greek.woff2) format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} + +/* Inter, normal, vietnamese */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./inter-normal-vietnamese.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} + +/* Inter, normal, latin-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./inter-normal-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* Inter, normal, latin */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./inter-normal-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* Inter, normal, cyrillic-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(./inter-normal-cyrillic-ext.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} + +/* Inter, normal, cyrillic */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(./inter-normal-cyrillic.woff2) format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +/* Inter, normal, greek-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(./inter-normal-greek-ext.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} + +/* Inter, normal, greek */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(./inter-normal-greek.woff2) format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} + +/* Inter, normal, vietnamese */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(./inter-normal-vietnamese.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} + +/* Inter, normal, latin-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(./inter-normal-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* Inter, normal, latin */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(./inter-normal-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* JetBrains Mono, italic, cyrillic-ext */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./jetbrains-mono-italic-cyrillic-ext.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} + +/* JetBrains Mono, italic, cyrillic */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./jetbrains-mono-italic-cyrillic.woff2) format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +/* JetBrains Mono, italic, greek */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./jetbrains-mono-italic-greek.woff2) format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} + +/* JetBrains Mono, italic, vietnamese */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./jetbrains-mono-italic-vietnamese.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} + +/* JetBrains Mono, italic, latin-ext */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./jetbrains-mono-italic-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* JetBrains Mono, italic, latin */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(./jetbrains-mono-italic-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* JetBrains Mono, normal, cyrillic-ext */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./jetbrains-mono-normal-cyrillic-ext.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} + +/* JetBrains Mono, normal, cyrillic */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./jetbrains-mono-normal-cyrillic.woff2) format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +/* JetBrains Mono, normal, greek */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./jetbrains-mono-normal-greek.woff2) format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} + +/* JetBrains Mono, normal, vietnamese */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./jetbrains-mono-normal-vietnamese.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} + +/* JetBrains Mono, normal, latin-ext */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./jetbrains-mono-normal-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* JetBrains Mono, normal, latin */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(./jetbrains-mono-normal-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} diff --git a/frontend/src/assets/fonts/fraunces-italic-latin-ext.woff2 b/frontend/src/assets/fonts/fraunces-italic-latin-ext.woff2 new file mode 100644 index 00000000..2e650495 Binary files /dev/null and b/frontend/src/assets/fonts/fraunces-italic-latin-ext.woff2 differ diff --git a/frontend/src/assets/fonts/fraunces-italic-latin.woff2 b/frontend/src/assets/fonts/fraunces-italic-latin.woff2 new file mode 100644 index 00000000..b2cf6488 Binary files /dev/null and b/frontend/src/assets/fonts/fraunces-italic-latin.woff2 differ diff --git a/frontend/src/assets/fonts/fraunces-italic-vietnamese.woff2 b/frontend/src/assets/fonts/fraunces-italic-vietnamese.woff2 new file mode 100644 index 00000000..ede99e3b Binary files /dev/null and b/frontend/src/assets/fonts/fraunces-italic-vietnamese.woff2 differ diff --git a/frontend/src/assets/fonts/fraunces-normal-latin-ext.woff2 b/frontend/src/assets/fonts/fraunces-normal-latin-ext.woff2 new file mode 100644 index 00000000..b6e3e3a5 Binary files /dev/null and b/frontend/src/assets/fonts/fraunces-normal-latin-ext.woff2 differ diff --git a/frontend/src/assets/fonts/fraunces-normal-latin.woff2 b/frontend/src/assets/fonts/fraunces-normal-latin.woff2 new file mode 100644 index 00000000..cb295bf0 Binary files /dev/null and b/frontend/src/assets/fonts/fraunces-normal-latin.woff2 differ diff --git a/frontend/src/assets/fonts/fraunces-normal-vietnamese.woff2 b/frontend/src/assets/fonts/fraunces-normal-vietnamese.woff2 new file mode 100644 index 00000000..43126c27 Binary files /dev/null and b/frontend/src/assets/fonts/fraunces-normal-vietnamese.woff2 differ diff --git a/frontend/src/assets/fonts/inter-italic-cyrillic-ext.woff2 b/frontend/src/assets/fonts/inter-italic-cyrillic-ext.woff2 new file mode 100644 index 00000000..e9ea5b35 Binary files /dev/null and b/frontend/src/assets/fonts/inter-italic-cyrillic-ext.woff2 differ diff --git a/frontend/src/assets/fonts/inter-italic-cyrillic.woff2 b/frontend/src/assets/fonts/inter-italic-cyrillic.woff2 new file mode 100644 index 00000000..129e73f6 Binary files /dev/null and b/frontend/src/assets/fonts/inter-italic-cyrillic.woff2 differ diff --git a/frontend/src/assets/fonts/inter-italic-greek-ext.woff2 b/frontend/src/assets/fonts/inter-italic-greek-ext.woff2 new file mode 100644 index 00000000..c9f84ac8 Binary files /dev/null and b/frontend/src/assets/fonts/inter-italic-greek-ext.woff2 differ diff --git a/frontend/src/assets/fonts/inter-italic-greek.woff2 b/frontend/src/assets/fonts/inter-italic-greek.woff2 new file mode 100644 index 00000000..bf0c2b72 Binary files /dev/null and b/frontend/src/assets/fonts/inter-italic-greek.woff2 differ diff --git a/frontend/src/assets/fonts/inter-italic-latin-ext.woff2 b/frontend/src/assets/fonts/inter-italic-latin-ext.woff2 new file mode 100644 index 00000000..9de5e3e3 Binary files /dev/null and b/frontend/src/assets/fonts/inter-italic-latin-ext.woff2 differ diff --git a/frontend/src/assets/fonts/inter-italic-latin.woff2 b/frontend/src/assets/fonts/inter-italic-latin.woff2 new file mode 100644 index 00000000..9e98286f Binary files /dev/null and b/frontend/src/assets/fonts/inter-italic-latin.woff2 differ diff --git a/frontend/src/assets/fonts/inter-italic-vietnamese.woff2 b/frontend/src/assets/fonts/inter-italic-vietnamese.woff2 new file mode 100644 index 00000000..0ef7659e Binary files /dev/null and b/frontend/src/assets/fonts/inter-italic-vietnamese.woff2 differ diff --git a/frontend/src/assets/fonts/inter-normal-cyrillic-ext.woff2 b/frontend/src/assets/fonts/inter-normal-cyrillic-ext.woff2 new file mode 100644 index 00000000..de83a9c7 Binary files /dev/null and b/frontend/src/assets/fonts/inter-normal-cyrillic-ext.woff2 differ diff --git a/frontend/src/assets/fonts/inter-normal-cyrillic.woff2 b/frontend/src/assets/fonts/inter-normal-cyrillic.woff2 new file mode 100644 index 00000000..d7509147 Binary files /dev/null and b/frontend/src/assets/fonts/inter-normal-cyrillic.woff2 differ diff --git a/frontend/src/assets/fonts/inter-normal-greek-ext.woff2 b/frontend/src/assets/fonts/inter-normal-greek-ext.woff2 new file mode 100644 index 00000000..6e7141f8 Binary files /dev/null and b/frontend/src/assets/fonts/inter-normal-greek-ext.woff2 differ diff --git a/frontend/src/assets/fonts/inter-normal-greek.woff2 b/frontend/src/assets/fonts/inter-normal-greek.woff2 new file mode 100644 index 00000000..024f0770 Binary files /dev/null and b/frontend/src/assets/fonts/inter-normal-greek.woff2 differ diff --git a/frontend/src/assets/fonts/inter-normal-latin-ext.woff2 b/frontend/src/assets/fonts/inter-normal-latin-ext.woff2 new file mode 100644 index 00000000..479d010d Binary files /dev/null and b/frontend/src/assets/fonts/inter-normal-latin-ext.woff2 differ diff --git a/frontend/src/assets/fonts/inter-normal-latin.woff2 b/frontend/src/assets/fonts/inter-normal-latin.woff2 new file mode 100644 index 00000000..d15208de Binary files /dev/null and b/frontend/src/assets/fonts/inter-normal-latin.woff2 differ diff --git a/frontend/src/assets/fonts/inter-normal-vietnamese.woff2 b/frontend/src/assets/fonts/inter-normal-vietnamese.woff2 new file mode 100644 index 00000000..a40c4699 Binary files /dev/null and b/frontend/src/assets/fonts/inter-normal-vietnamese.woff2 differ diff --git a/frontend/src/assets/fonts/jetbrains-mono-italic-cyrillic-ext.woff2 b/frontend/src/assets/fonts/jetbrains-mono-italic-cyrillic-ext.woff2 new file mode 100644 index 00000000..907599df Binary files /dev/null and b/frontend/src/assets/fonts/jetbrains-mono-italic-cyrillic-ext.woff2 differ diff --git a/frontend/src/assets/fonts/jetbrains-mono-italic-cyrillic.woff2 b/frontend/src/assets/fonts/jetbrains-mono-italic-cyrillic.woff2 new file mode 100644 index 00000000..ad54a035 Binary files /dev/null and b/frontend/src/assets/fonts/jetbrains-mono-italic-cyrillic.woff2 differ diff --git a/frontend/src/assets/fonts/jetbrains-mono-italic-greek.woff2 b/frontend/src/assets/fonts/jetbrains-mono-italic-greek.woff2 new file mode 100644 index 00000000..bd07500c Binary files /dev/null and b/frontend/src/assets/fonts/jetbrains-mono-italic-greek.woff2 differ diff --git a/frontend/src/assets/fonts/jetbrains-mono-italic-latin-ext.woff2 b/frontend/src/assets/fonts/jetbrains-mono-italic-latin-ext.woff2 new file mode 100644 index 00000000..c88b32dd Binary files /dev/null and b/frontend/src/assets/fonts/jetbrains-mono-italic-latin-ext.woff2 differ diff --git a/frontend/src/assets/fonts/jetbrains-mono-italic-latin.woff2 b/frontend/src/assets/fonts/jetbrains-mono-italic-latin.woff2 new file mode 100644 index 00000000..3d3c5d77 Binary files /dev/null and b/frontend/src/assets/fonts/jetbrains-mono-italic-latin.woff2 differ diff --git a/frontend/src/assets/fonts/jetbrains-mono-italic-vietnamese.woff2 b/frontend/src/assets/fonts/jetbrains-mono-italic-vietnamese.woff2 new file mode 100644 index 00000000..18b3a0ce Binary files /dev/null and b/frontend/src/assets/fonts/jetbrains-mono-italic-vietnamese.woff2 differ diff --git a/frontend/src/assets/fonts/jetbrains-mono-normal-cyrillic-ext.woff2 b/frontend/src/assets/fonts/jetbrains-mono-normal-cyrillic-ext.woff2 new file mode 100644 index 00000000..3d61ccaa Binary files /dev/null and b/frontend/src/assets/fonts/jetbrains-mono-normal-cyrillic-ext.woff2 differ diff --git a/frontend/src/assets/fonts/jetbrains-mono-normal-cyrillic.woff2 b/frontend/src/assets/fonts/jetbrains-mono-normal-cyrillic.woff2 new file mode 100644 index 00000000..f60f0bd1 Binary files /dev/null and b/frontend/src/assets/fonts/jetbrains-mono-normal-cyrillic.woff2 differ diff --git a/frontend/src/assets/fonts/jetbrains-mono-normal-greek.woff2 b/frontend/src/assets/fonts/jetbrains-mono-normal-greek.woff2 new file mode 100644 index 00000000..da7ab5f3 Binary files /dev/null and b/frontend/src/assets/fonts/jetbrains-mono-normal-greek.woff2 differ diff --git a/frontend/src/assets/fonts/jetbrains-mono-normal-latin-ext.woff2 b/frontend/src/assets/fonts/jetbrains-mono-normal-latin-ext.woff2 new file mode 100644 index 00000000..9d97c9b7 Binary files /dev/null and b/frontend/src/assets/fonts/jetbrains-mono-normal-latin-ext.woff2 differ diff --git a/frontend/src/assets/fonts/jetbrains-mono-normal-latin.woff2 b/frontend/src/assets/fonts/jetbrains-mono-normal-latin.woff2 new file mode 100644 index 00000000..58588733 Binary files /dev/null and b/frontend/src/assets/fonts/jetbrains-mono-normal-latin.woff2 differ diff --git a/frontend/src/assets/fonts/jetbrains-mono-normal-vietnamese.woff2 b/frontend/src/assets/fonts/jetbrains-mono-normal-vietnamese.woff2 new file mode 100644 index 00000000..2e5dab08 Binary files /dev/null and b/frontend/src/assets/fonts/jetbrains-mono-normal-vietnamese.woff2 differ diff --git a/frontend/src/assets/theme.css b/frontend/src/assets/theme.css index 13b1c799..7d5ea9cb 100644 --- a/frontend/src/assets/theme.css +++ b/frontend/src/assets/theme.css @@ -1,5 +1,3 @@ -@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,300..900&family=Inter:ital,wght@0,400;0,500;1,400&family=JetBrains+Mono:ital,wght@0,400;1,400&display=swap'); - /* ========================================================================== GENERATED FROM THE DESIGN SYSTEM — Grimoire (design system 2), which inherits the FabledSword house style (design system 1). @@ -8,13 +6,13 @@ and regenerate: /design-systems -> Master stylesheet -> Copy. DARK IS THE BASE LAYER. The kit is dark-mode-first, so :root carries the dark - palette and [data-theme="light"] overrides it. That is the inverse of how this - file used to read, and it is deliberate: the light palette was never specified - by any rule, so it is recorded as a departure rather than as the default. + palette and [data-theme="light"] overrides it. No rule specifies the light + palette, so it is written as an override of the dark one rather than as the + default. - The -fg tokens are a badge's TEXT colour, added because the ladder used its - raw hue as text on a 12% tint of the same hue — measured 1.60-2.97:1 on the - dark palette against the kit's AA floor of 4.5. Each is the hue mixed toward + The -fg tokens are a badge's TEXT colour. A raw hue as text on a 12% tint of + the same hue measures 1.60-2.97:1 on the dark palette, under the kit's AA + floor of 4.5, so each -fg token is the hue mixed toward --fs-text-primary until it clears 4.5:1 worst-case over surface-raised and surface-hover in BOTH modes. Mixing toward that token is what makes one declaration cover both: it inverts, so the text follows the mode. diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 9672c42a..0ffbd319 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -2,6 +2,7 @@ import { createApp } from "vue"; import { createPinia } from "pinia"; import App from "./App.vue"; import router from "./router"; +import "./assets/fonts/fonts.css"; import "./assets/theme.css"; // After theme.css — it consumes the tokens declared there. import "./assets/components.css"; diff --git a/src/grimoire/app.py b/src/grimoire/app.py index 8c304323..21e36252 100644 --- a/src/grimoire/app.py +++ b/src/grimoire/app.py @@ -152,10 +152,10 @@ def create_app() -> Quart: "Content-Security-Policy", "default-src 'self'; " "script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; " - "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " + "style-src 'self' 'unsafe-inline'; " "img-src 'self' data: blob:; " "connect-src 'self'; " - "font-src 'self' data: https://fonts.gstatic.com; " + "font-src 'self' data:; " "object-src 'none'; " "base-uri 'self'; worker-src 'self' blob:;" ) diff --git a/tests/test_frontend_no_remote_assets.py b/tests/test_frontend_no_remote_assets.py new file mode 100644 index 00000000..32199a64 --- /dev/null +++ b/tests/test_frontend_no_remote_assets.py @@ -0,0 +1,65 @@ +"""The app draws itself only from its own origin. + +A font, stylesheet or script linked from another host hands that host every +visitor's address on every page load, sign-in page included, and when the host +is slow or blocked the page quietly renders in fallback type with nothing in the +console. Everything the frontend needs ships in the image instead, and the +Content-Security-Policy names no other host, so a remote link that slips back in +is refused by the browser as well as by this check. +""" +from __future__ import annotations + +import pathlib +import re + +ROOT = pathlib.Path(__file__).resolve().parents[1] +FRONTEND = ROOT / "frontend" + +CSS_COMMENT = re.compile(r"/\*.*?\*/", re.S) +HTML_COMMENT = re.compile(r"", re.S) +REMOTE = r"""(?:https?:)?//""" +REMOTE_REFS = [ + re.compile(rf"""@import\s+(?:url\(\s*)?['"]?{REMOTE}""", re.I), + re.compile(rf"""url\(\s*['"]?{REMOTE}""", re.I), + re.compile(rf"""]*\bhref\s*=\s*['"]{REMOTE}""", re.I), + re.compile(rf"""]*\bsrc\s*=\s*['"]{REMOTE}""", re.I), + re.compile(r"""\brel\s*=\s*['"](?:preconnect|dns-prefetch)""", re.I), +] + + +def remote_refs(text: str) -> list[str]: + """The remote asset references in a stylesheet, page or component.""" + text = HTML_COMMENT.sub("", CSS_COMMENT.sub("", text)) + return [m.group(0) for pattern in REMOTE_REFS for m in pattern.finditer(text)] + + +def frontend_sources() -> dict[str, str]: + paths = [FRONTEND / "index.html"] + for suffix in ("*.css", "*.vue", "*.html"): + paths += (FRONTEND / "src").rglob(suffix) + return {str(p.relative_to(ROOT)): p.read_text() for p in paths} + + +def test_no_frontend_source_links_a_remote_asset(): + sources = frontend_sources() + assert len(sources) > 1, f"no frontend sources under {FRONTEND}" + found = {name: refs for name, text in sources.items() if (refs := remote_refs(text))} + assert found == {}, "vendor the asset and serve it from the app's own origin" + + +def test_the_content_security_policy_names_no_other_host(): + app = (ROOT / "src" / "grimoire" / "app.py").read_text() + policy = re.search(r'"Content-Security-Policy",\s*((?:"[^"]*"\s*)+)\)', app) + assert policy, "the Content-Security-Policy header is no longer set in app.py" + assert "//" not in policy.group(1) + + +def test_the_guard_can_fail(): + assert remote_refs("@import url('https://fonts.example.com/css2?family=X');") + assert remote_refs("@font-face { src: url(//cdn.example.com/x.woff2); }") + assert remote_refs('') + assert remote_refs('') + assert remote_refs('') + assert not remote_refs("/* no @import url('https://x.example.com') here */") + assert not remote_refs("@font-face { src: url(./inter-normal-latin.woff2); }") + assert not remote_refs('docs')