diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index af4bff7..cec7670 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -26,6 +26,100 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +## Inter 4.1 + +Copyright (c) 2016 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: +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. + ## Framework7 documentation placeholder media The development-only Sky UI Kitchen Sink includes placeholder images mirrored diff --git a/frontend/src/assets/fonts/InterVariable-Italic.woff2 b/frontend/src/assets/fonts/InterVariable-Italic.woff2 new file mode 100644 index 0000000..b3530f3 Binary files /dev/null and b/frontend/src/assets/fonts/InterVariable-Italic.woff2 differ diff --git a/frontend/src/assets/fonts/InterVariable.woff2 b/frontend/src/assets/fonts/InterVariable.woff2 new file mode 100644 index 0000000..5a8d3e7 Binary files /dev/null and b/frontend/src/assets/fonts/InterVariable.woff2 differ diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index c490e5f..cd07100 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -211,9 +211,11 @@ } } :root { - font-family: - -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', - sans-serif; + font-family: var(--sky-font-family); + font-feature-settings: + 'liga' 1, + 'calt' 1; + font-optical-sizing: auto; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; @@ -234,7 +236,9 @@ body, user-select: none; } button, -input { +input, +textarea, +select { font: inherit; } button { @@ -1632,8 +1636,7 @@ button { overflow: hidden; background: #000; color: #f5f5f7; - font-family: - -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', sans-serif; + font-family: var(--sky-font-family); } .darkchat-page button, .darkchat-page input, @@ -7833,8 +7836,7 @@ button { min-height: var(--springboard-grid-row); } .app-icon-button { - font-family: - -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', sans-serif; + font-family: var(--sky-font-family); touch-action: none; user-select: none; -webkit-user-select: none; diff --git a/frontend/src/components/HomeFolderOverlay.vue b/frontend/src/components/HomeFolderOverlay.vue index 16496b1..e2cecb0 100644 --- a/frontend/src/components/HomeFolderOverlay.vue +++ b/frontend/src/components/HomeFolderOverlay.vue @@ -331,8 +331,7 @@ function finishPageSwipe(event: PointerEvent): void { z-index: 70; inset: 0; color: #fff; - font-family: - -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', sans-serif; + font-family: var(--sky-font-family); } .home-folder-backdrop { diff --git a/frontend/src/components/PayphoneOverlay.vue b/frontend/src/components/PayphoneOverlay.vue index eb1fbd3..e7c3b6f 100644 --- a/frontend/src/components/PayphoneOverlay.vue +++ b/frontend/src/components/PayphoneOverlay.vue @@ -399,7 +399,7 @@ onBeforeUnmount(() => { rgb(30 38 42 / 35%), rgb(0 0 0 / 84%) 72% ); - font-family: 'Segoe UI', Arial, sans-serif; + font-family: var(--sky-font-family); pointer-events: auto; user-select: none; } diff --git a/frontend/src/components/RadioHud.vue b/frontend/src/components/RadioHud.vue index b1c67f0..b416a76 100644 --- a/frontend/src/components/RadioHud.vue +++ b/frontend/src/components/RadioHud.vue @@ -210,7 +210,7 @@ onBeforeUnmount(() => { z-index: 40; display: flex; pointer-events: none; - font-family: Inter, ui-sans-serif, system-ui, sans-serif; + font-family: var(--sky-font-family); } .radio-hud[data-horizontal='left'] { diff --git a/frontend/src/components/SpringboardWidget.vue b/frontend/src/components/SpringboardWidget.vue index 5841869..48eba4f 100644 --- a/frontend/src/components/SpringboardWidget.vue +++ b/frontend/src/components/SpringboardWidget.vue @@ -677,9 +677,7 @@ onBeforeUnmount(() => { backdrop-filter: blur(26px) saturate(125%); -webkit-backdrop-filter: blur(26px) saturate(125%); cursor: pointer; - font-family: - -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', - 'Segoe UI', sans-serif; + font-family: var(--sky-font-family); user-select: none; -webkit-user-select: none; touch-action: none; diff --git a/frontend/src/ui/InterFont.contract.test.ts b/frontend/src/ui/InterFont.contract.test.ts new file mode 100644 index 0000000..398a38e --- /dev/null +++ b/frontend/src/ui/InterFont.contract.test.ts @@ -0,0 +1,59 @@ +import { existsSync, readdirSync, readFileSync } from 'node:fs' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { describe, expect, it } from 'vitest' + +const sourceDirectory = fileURLToPath(new URL('..', import.meta.url)) +const tokensSource = readFileSync(new URL('./tokens.css', import.meta.url), 'utf8') +const mainCssSource = readFileSync( + new URL('../assets/main.css', import.meta.url), + 'utf8', +) + +function styleSources(directory: string): Array<{ + file: string + source: string +}> { + return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { + const path = join(directory, entry.name) + if (entry.isDirectory()) return styleSources(path) + if (!/\.(?:css|vue)$/.test(entry.name)) return [] + return [{ file: path, source: readFileSync(path, 'utf8') }] + }) +} + +describe('Inter font contract', () => { + it('bundles normal and italic variable fonts for every supported weight', () => { + expect( + existsSync(new URL('../assets/fonts/InterVariable.woff2', import.meta.url)), + ).toBe(true) + expect( + existsSync( + new URL('../assets/fonts/InterVariable-Italic.woff2', import.meta.url), + ), + ).toBe(true) + expect(tokensSource.match(/@font-face/g)).toHaveLength(2) + expect(tokensSource.match(/font-weight:\s*100 900;/g)).toHaveLength(2) + expect(tokensSource).toContain("--sky-font-family: 'Inter', Arial, sans-serif;") + }) + + it('applies the shared font to the document and native form controls', () => { + expect(mainCssSource).toMatch( + /:root\s*\{[\s\S]*?font-family:\s*var\(--sky-font-family\);/, + ) + expect(mainCssSource).toMatch( + /button,\s*input,\s*textarea,\s*select\s*\{\s*font:\s*inherit;/, + ) + }) + + it('does not bypass the shared token with a generic system UI stack', () => { + const genericSystemStack = + /font-family\s*:\s*(?:-apple-system|BlinkMacSystemFont|system-ui|ui-sans-serif|['"]Segoe UI['"])/ + const violations = styleSources(sourceDirectory) + .filter(({ source }) => genericSystemStack.test(source)) + .map(({ file }) => file.slice(sourceDirectory.length + 1)) + + expect(violations).toEqual([]) + }) +}) diff --git a/frontend/src/ui/foundation.css b/frontend/src/ui/foundation.css index c0b3180..0aa46ee 100644 --- a/frontend/src/ui/foundation.css +++ b/frontend/src/ui/foundation.css @@ -525,8 +525,7 @@ height: var(--sky-widget-label-height); overflow: hidden; color: var(--sky-widget-label-color, #fff); - font-family: - -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', sans-serif; + font-family: var(--sky-font-family); font-size: 11.5px; font-weight: 500; letter-spacing: -0.15px; diff --git a/frontend/src/ui/tokens.css b/frontend/src/ui/tokens.css index a1e83dc..a3f60ff 100644 --- a/frontend/src/ui/tokens.css +++ b/frontend/src/ui/tokens.css @@ -1,3 +1,19 @@ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100 900; + font-display: swap; + src: url('../assets/fonts/InterVariable.woff2') format('woff2'); +} + +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 100 900; + font-display: swap; + src: url('../assets/fonts/InterVariable-Italic.woff2') format('woff2'); +} + :root { --sky-device-pixel-ratio: 1; --sky-hairline-scale: 1; @@ -26,9 +42,7 @@ --sky-font-title: 17px; --sky-font-medium-title: 24px; --sky-font-large-title: 34px; - --sky-font-family: - -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'SF UI Text', - 'Helvetica Neue', Helvetica, Arial, sans-serif; + --sky-font-family: 'Inter', Arial, sans-serif; --sky-transition-fast: 100ms; --sky-transition-normal: 200ms; --sky-ease-out: cubic-bezier(0.22, 1, 0.36, 1); diff --git a/frontend/src/views/apps/BillingApp.vue b/frontend/src/views/apps/BillingApp.vue index 26e67e0..13effc2 100644 --- a/frontend/src/views/apps/BillingApp.vue +++ b/frontend/src/views/apps/BillingApp.vue @@ -791,7 +791,7 @@ onBeforeUnmount(() => { flex-direction: column; color: #f6f7f9; background: #07090c; - font-family: Inter, system-ui, sans-serif; + font-family: var(--sky-font-family); } .billing-app--light { --billing-border: rgb(15 23 42 / 10%); diff --git a/frontend/src/views/apps/CalendarApp.vue b/frontend/src/views/apps/CalendarApp.vue index 4833148..9238ff4 100644 --- a/frontend/src/views/apps/CalendarApp.vue +++ b/frontend/src/views/apps/CalendarApp.vue @@ -1156,13 +1156,7 @@ onMounted(async () => { padding: 47px 0 24px; background: var(--bg); color: var(--label); - font-family: - Inter, - -apple-system, - BlinkMacSystemFont, - 'SF Pro Display', - system-ui, - sans-serif; + font-family: var(--sky-font-family); } .calendar--light { diff --git a/frontend/src/views/apps/CityMarktApp.vue b/frontend/src/views/apps/CityMarktApp.vue index 6357919..12ae713 100644 --- a/frontend/src/views/apps/CityMarktApp.vue +++ b/frontend/src/views/apps/CityMarktApp.vue @@ -2195,7 +2195,7 @@ onMounted(async () => { overflow: hidden; background: #151613; color: #f8f8f4; - font-family: Inter, system-ui, sans-serif; + font-family: var(--sky-font-family); } .citymarkt--light { --ink: #fff; diff --git a/frontend/src/views/apps/FeatherApp.vue b/frontend/src/views/apps/FeatherApp.vue index 2dd7ec7..f1b8991 100644 --- a/frontend/src/views/apps/FeatherApp.vue +++ b/frontend/src/views/apps/FeatherApp.vue @@ -3590,7 +3590,7 @@ onMounted(async () => { overflow: hidden; background: #12171b !important; color: #f7f8f4; - font-family: Inter, system-ui, sans-serif; + font-family: var(--sky-font-family); } .feather-app--active.feather-app--light { --feather-panel: #f0f1ec; diff --git a/frontend/src/views/apps/FlipTokApp.vue b/frontend/src/views/apps/FlipTokApp.vue index 2cf2756..657b222 100644 --- a/frontend/src/views/apps/FlipTokApp.vue +++ b/frontend/src/views/apps/FlipTokApp.vue @@ -3375,7 +3375,7 @@ onBeforeUnmount(() => { overflow: hidden; background: #000; color: #fff; - font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif; + font-family: var(--sky-font-family); } .state, .light-empty, diff --git a/frontend/src/views/apps/LocalPagesApp.vue b/frontend/src/views/apps/LocalPagesApp.vue index e851a7f..b395536 100644 --- a/frontend/src/views/apps/LocalPagesApp.vue +++ b/frontend/src/views/apps/LocalPagesApp.vue @@ -1526,7 +1526,7 @@ onMounted(async () => { overflow: hidden; background: #12171b; color: #f7f7f2; - font-family: Inter, system-ui, sans-serif; + font-family: var(--sky-font-family); } .pages--light { --yellow: #8a6500; diff --git a/frontend/src/views/apps/MemoryApp.vue b/frontend/src/views/apps/MemoryApp.vue index c2aa588..b29ef15 100644 --- a/frontend/src/views/apps/MemoryApp.vue +++ b/frontend/src/views/apps/MemoryApp.vue @@ -278,7 +278,7 @@ onBeforeUnmount(() => { background: radial-gradient(circle at 88% 8%, rgb(255 255 255 / 72%), transparent 28%), linear-gradient(155deg, #f4efff 0%, #e7ddff 55%, #d9cdf7 100%); - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: var(--sky-font-family); user-select: none; } diff --git a/frontend/src/views/apps/MinesweeperApp.vue b/frontend/src/views/apps/MinesweeperApp.vue index 63b1612..e82c7c8 100644 --- a/frontend/src/views/apps/MinesweeperApp.vue +++ b/frontend/src/views/apps/MinesweeperApp.vue @@ -419,7 +419,7 @@ onBeforeUnmount(() => { background: radial-gradient(circle at 88% 7%, rgb(108 231 218 / 32%), transparent 30%), linear-gradient(155deg, #effcf7 0%, #cfeee5 52%, #abdcd7 100%); - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: var(--sky-font-family); touch-action: manipulation; user-select: none; } diff --git a/frontend/src/views/apps/NeonDropApp.vue b/frontend/src/views/apps/NeonDropApp.vue index 1f24c40..7106751 100644 --- a/frontend/src/views/apps/NeonDropApp.vue +++ b/frontend/src/views/apps/NeonDropApp.vue @@ -376,7 +376,7 @@ onBeforeUnmount(() => { padding: 52px 14px 25px; color: #f5fbff; background: radial-gradient(circle at 50% 0, #253163, #090c22 58%, #050715); - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-family: var(--sky-font-family); user-select: none; touch-action: manipulation; } diff --git a/frontend/src/views/apps/NumberMergeApp.vue b/frontend/src/views/apps/NumberMergeApp.vue index 897c649..dfc6b24 100644 --- a/frontend/src/views/apps/NumberMergeApp.vue +++ b/frontend/src/views/apps/NumberMergeApp.vue @@ -334,7 +334,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown)) background: radial-gradient(circle at 86% 8%, rgb(255 192 94 / 28%), transparent 29%), linear-gradient(155deg, #fff3dc 0%, #f3d8b7 55%, #e8b98d 100%); - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: var(--sky-font-family); touch-action: none; user-select: none; } diff --git a/frontend/src/views/apps/SkyFlappyApp.vue b/frontend/src/views/apps/SkyFlappyApp.vue index 1adbc4c..f07c7ae 100644 --- a/frontend/src/views/apps/SkyFlappyApp.vue +++ b/frontend/src/views/apps/SkyFlappyApp.vue @@ -166,7 +166,7 @@ onBeforeUnmount(() => {