- A fresh perspective, one letter at a time.
- Make a statement.And let the details speak.
+ The quick brown fox jumps over the lazy dog. 0123456789
+ Bold text for emphasis.Italic text for a different voice.
`
: nothing}
@@ -251,10 +261,10 @@ function specimen() {
function categoryControls() {
return html`
-
A font for every kind of text.
+
Serif & sans-serif
- Give serif and sans-serif text their own character. Leave either on
- the default to use your regular typeface.
+ Choose by the page’s original text category, or use your regular
+ typeface.
@@ -323,7 +333,7 @@ function categoryControls() {
const font = "${fontName(s.monospace.family)}";
-// Make code feel like home.
Replacement is off by default. Icons and exceptions stay protected, and
@@ -334,15 +344,13 @@ const font = "${fontName(s.monospace.family)}";
function typography() {
return html`${hero(
"Make yourself at font.",
- "Give the whole web a typeface that feels like you. Keep the structure. Change the character.",
+ "Choose the fonts you read on the web. Changes apply to open pages automatically.",
)}
-
Fontify the web
-
- Apply your fonts on every site, except your exceptions.
-
- ${["The everyday", "The emphasis", "The expression"][i]}
-
+
${["Regular", "Bold", "Italic"][i]}
- ${specimen()} ${categoryControls()}
-
+ ${specimen()}
+
@@ -418,14 +426,15 @@ function typography() {
uses the serif or sans-serif family selected above, with its selected
style weight. Bold italics use the italic profile.
-
`;
+
+ ${categoryControls()}`;
}
function popupView() {
const excluded =
activeSite && s.exceptions.sites.some((p) => matchesSite(activeSite, p));
return html`${hero(
- "Your web.\nYour type.",
- "A small change. A whole new feeling.",
+ "Your fonts, on this page.",
+ "Choose a typeface or pause Fontify below.",
)}
@@ -435,7 +444,7 @@ function popupView() {
${status?.count
? `${status.count} text elements restyled`
: status?.failures
- ? "A font could not load — see settings"
+ ? "A font could not load. Check settings."
: activeSite
? excluded
? "This site is an exception"
@@ -476,7 +485,7 @@ function popupView() {
Browser settings, stores and other protected pages cannot be restyled.
`}
@@ -987,9 +987,8 @@ async function clearCache() {
}
function preferences() {
return html`${hero(
- "Just how you like it.",
- "A quieter web, a brighter interface, or a fresh start. You’re in control.",
- "✳",
+ "Preferences",
+ "Set the theme, manage font downloads, and back up your settings.",
)}
Appearance & downloads
@@ -1039,7 +1038,7 @@ function preferences() {
-
Your settings, to go.
+
Backup & reset
Back up your settings, overrides, exceptions and custom font files.
Downloads from Google are excluded.
@@ -1078,7 +1077,7 @@ function preferences() {
-
Made for your eyes. By XDan.
+
About Fontify
Fontify 1.1.0 · MIT licensed · Built with matraic’s M3E components.
@@ -1155,7 +1154,8 @@ function pickerDialog() {
- ${catalogue.length.toLocaleString()} Google families. Yours included.
+ ${catalogue.length.toLocaleString()} Google Fonts families, plus your
+ uploaded fonts.
THIS WEBSITE
@@ -490,7 +490,8 @@ function popupView() {
() => api.runtime.openOptionsPage(),
"filled",
)}
-
`;
+
+ ${specimen()}`;
}
async function upload(event) {
const files = [...event.target.files];
From 1efd6d349205e278fac91550cdd4b6d20d99339f Mon Sep 17 00:00:00 2001
From: XDan <109234186+XDanfr@users.noreply.github.com>
Date: Mon, 12 Oct 2026 21:59:36 +0100
Subject: [PATCH 3/7] Apply fonts across Fontify and add expressive navigation
with reliable popup sizing
---
THIRD_PARTY_NOTICES.md | 2 +
anti-slop/delivery-2026-10-12.md | 27 ++-
public/symbols/LICENSE | 202 ++++++++++++++++++++++
public/symbols/block-0.svg | 1 +
public/symbols/block-1.svg | 1 +
public/symbols/collections_bookmark-0.svg | 1 +
public/symbols/collections_bookmark-1.svg | 1 +
public/symbols/settings-0.svg | 1 +
public/symbols/settings-1.svg | 1 +
public/symbols/text_fields-0.svg | 1 +
public/symbols/text_fields-1.svg | 1 +
public/symbols/tune-0.svg | 1 +
public/symbols/tune-1.svg | 1 +
public/ui.css | 165 ++++++++++++++----
src/ui.js | 72 +++++++-
tests/browser.mjs | 140 ++++++++++++++-
16 files changed, 572 insertions(+), 46 deletions(-)
create mode 100644 public/symbols/LICENSE
create mode 100644 public/symbols/block-0.svg
create mode 100644 public/symbols/block-1.svg
create mode 100644 public/symbols/collections_bookmark-0.svg
create mode 100644 public/symbols/collections_bookmark-1.svg
create mode 100644 public/symbols/settings-0.svg
create mode 100644 public/symbols/settings-1.svg
create mode 100644 public/symbols/text_fields-0.svg
create mode 100644 public/symbols/text_fields-1.svg
create mode 100644 public/symbols/tune-0.svg
create mode 100644 public/symbols/tune-1.svg
diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md
index 0b35ada..a18c264 100644
--- a/THIRD_PARTY_NOTICES.md
+++ b/THIRD_PARTY_NOTICES.md
@@ -5,3 +5,5 @@ Fontify is MIT licensed. It uses @m3e/web (MIT, copyright 2025–2026 matraic),
The Google Fonts catalogue contains family names and metadata retrieved from Google's public metadata endpoint. Individual font files retain their own licences; selected Google fonts are downloaded at runtime, not redistributed in this repository. Outfit is bundled for the interface under SIL Open Font License 1.1; see public/fonts/OFL.txt.
Custom fonts are supplied by the user and stored only on their device. Users should have permission to use their uploaded fonts.
+
+Sidebar symbols are Google's Material Symbols Rounded, weight 700, in outlined and filled variants, from https://github.com/google/material-design-icons. They are bundled under Apache License 2.0; see public/symbols/LICENSE.
diff --git a/anti-slop/delivery-2026-10-12.md b/anti-slop/delivery-2026-10-12.md
index 96c56c0..460e482 100644
--- a/anti-slop/delivery-2026-10-12.md
+++ b/anti-slop/delivery-2026-10-12.md
@@ -1,10 +1,15 @@
# Fontify Anti Slop delivery
-Applied the Anti Slop plugin in during mode. Direction comes from Fontify's existing Outfit typography, lavender M3E tonal palette, expressive selected shapes, and browser-extension workflow. ENERGY 2 / RHYTHM 2 / MOTION 1.
+Applied the Anti Slop plugin in during mode. Direction comes from Fontify's existing lavender M3E tonal palette, expressive selected shapes, and browser-extension workflow, plus the owner's request for interface-wide font application, a wider popup, a larger sidebar, thick rounded icons, and micro-interaction morphs. Outfit remains the loading/error fallback. ENERGY 2 / RHYTHM 2 / MOTION 2.
## Changes and reasons
-- Retain Outfit, the existing icon, and M3E components: these are Fontify's established identity.
+- Retain Outfit as the fallback, the existing app icon, and M3E components: these are Fontify's established identity.
+- Apply the selected sans-serif/default profile across the interface, including native inputs and M3E shadow-root labels; apply the bold and italic profiles to their text styles: the extension itself now demonstrates the saved choices. Fonts apply after loading, with Outfit as a fallback on failure.
+- Increase the desktop sidebar from 195px to 260px, with 76px navigation rows and 30px symbols: the controls are easier to read and target.
+- Bundle Google's Material Symbols Rounded at weight 700 in outlined/filled pairs: typography, library, exclusion, tuning, and settings symbols describe each section, keep their shape when the interface font changes, and meet the owner's explicit icon direction.
+- Morph navigation/icon corners and crossfade outlined/filled selection states; soften button/filter/icon press shapes and animate the picker entrance: short motion communicates actions and hierarchy. M3E expressive motion is enabled; reduced-motion preferences remove the added motion. Background colours switch immediately to avoid low-contrast transitional pairings.
+- Set native toolbar popup width explicitly to 520px, while ordinary-tab previews remain responsive: viewport-relative width caused the native Chrome popup to shrink to 25px in a reproduced test. The actual toolbar popup now measures 520px, with quick settings visible before the specimen.
- Retain lavender as the primary accent: it marks selected controls and the working font specimen.
- Remove repeated slogan eyebrows and decorative hero blobs: settings titles should identify the task, and the specimen should demonstrate the fonts.
- Keep the typography page's existing pun: it gives Fontify a recognisable voice without obscuring the functional navigation labels.
@@ -23,7 +28,7 @@ Applied the Anti Slop plugin in during mode. Direction comes from Fontify's exis
- Add a persistent startup error and retry action: a failed catalogue load no longer leaves a loading screen with a disappearing toast.
- Ignore outdated asynchronous preview results and clear recovered errors: a slow previous selection cannot overwrite a later specimen.
-No new visual assets, navigation destinations, dependencies, or settings-schema changes.
+No new logo, navigation destinations, dependencies, or settings-schema changes. Requested sidebar symbols are bundled with Google's Apache-2.0 licence and attribution.
## Validation
@@ -31,7 +36,10 @@ No new visual assets, navigation destinations, dependencies, or settings-schema
- `npm run build`: Chrome and Firefox builds passed.
- `npm run lint:firefox`: 0 errors, 0 notices, 1 existing `UNSAFE_VAR_ASSIGNMENT` warning from Lit's bundled template parser, already documented in README.md.
- `npm run test:browser`: passed using Playwright 1.55.1 and Chromium 140 with the actual unpacked extension. Google font responses use a deterministic bundled-font fixture, so these tests validate loading and application behaviour rather than the visual appearance of each remote family.
-- Every settings section checked in light and dark at 1240, 768, 390, and 320px. Document overflow, rendered text contrast using the WCAG luminance formula, and control target height assertions passed. The popup also passed light/dark checks at 320px.
+- Every settings section checked in light and dark at 1240, 768, 390, and 320px. Document overflow, rendered text contrast using the WCAG luminance formula, and control target height assertions passed. The popup tab preview also passed light/dark checks at 320px and was captured at 520px.
+- Actual native Chromium toolbar popup opened through `chrome.action.openPopup`; its DevTools target measured 520px body/viewport width, no horizontal overflow, and All settings within the visible height. This validates native sizing separately from opening popup.html in a tab.
+- Selecting independent Bungee/Lora/Roboto regular/bold/italic profiles changes computed interface font families, including inputs, section headings, and M3E shadow-root labels. This checks propagation while keeping deterministic font data in the integration suite.
+- All ten local outlined/filled SVG variants fetch successfully. Reduced-motion emulation reports no picker animation and zero navigation-icon transition duration.
- Screenshots recorded for all 40 settings/theme/width combinations and the popup. Desktop typography, narrow typography/preferences, and popup captures were visually inspected.
- Keyboard tests verify initial editor focus, nested picker/editor Escape handling, restored trigger focus, and focus remaining in the popup dialog while tabbing. Source review confirms native focus outlines and M3E strong-focus remain enabled.
- Firefox interactive behaviour and physical mobile keyboards were not tested. Firefox validation is build/lint only. The builds remain unsigned development packages.
@@ -53,6 +61,7 @@ No new visual assets, navigation destinations, dependencies, or settings-schema
- Export produces a JSON backup; Import restores it after confirmation; Reset clears settings while retaining uploaded fonts.
- Popup Pause here and Resume here update the site's exception. All settings opens the options page.
- Failed catalogue startup shows a persistent error; Try again recovers the interface.
+- Interface-wide fonts change with the profiles; real native toolbar sizing is 520px; icon states load locally; added animations are disabled with reduced motion.
- Runtime page-error collection is empty. Existing external Source & feedback and M3E links retain their real destinations and explicit new-tab arrows; no new links were introduced.
## Delivery gate
@@ -65,7 +74,7 @@ Each PASS below is limited to the Chromium runtime, source review, and build/lin
- R-03 PASS: overflow and 44px target assertions pass at the four recorded widths in both themes.
- R-17 PASS: catalogue totals, result counts, and file sizes derive from real stored data.
- R-18 PASS: no testimonial or avatar content exists in the interface.
-- R-23 PASS: existing assets and navigation reused; no new visual asset created.
+- R-23 PASS: existing app assets and navigation reused; owner's requested rounded symbols bundled with their licence.
- R-24 PASS: all five navigation controls open existing settings views.
- R-25 PASS: rendered text passes computed WCAG AA contrast in the recorded views/themes and picker filters.
- R-26 PASS: shared control actions exercised in the interaction record; no placeholder controls added.
@@ -82,7 +91,7 @@ Each PASS below is limited to the Chromium runtime, source review, and build/lin
### Purpose gate
- R-01 PASS: existing lavender tonal tokens retained for state and specimen hierarchy; no gradients/glows added.
-- R-04 PASS: Aa/Ff denote typography; exception, override, settings, theme, and favourite marks denote real actions.
+- R-04 PASS: Material Symbols Rounded text_fields, collections_bookmark, block, tune, and settings identify their sections; theme and favourite marks denote real actions.
- R-06 PASS: Outfit retained for established identity; labels now use normal case without wide tracking.
- R-07 PASS: no background grid or ornamental texture.
- R-08 PASS: existing external/new-tab arrows retained for destination behaviour; no new decorative arrows.
@@ -91,12 +100,12 @@ Each PASS below is limited to the Chromium runtime, source review, and build/lin
- R-12 PASS: shadows limited to the modal layer and transient status message.
- R-13 PASS: no glow treatment.
- R-14 PASS: equivalent profile cards remain comparable; category controls, specimen, settings rows, and library lists use distinct layouts for their tasks.
-- R-19 PASS: no new animation; existing state transitions and reduced-motion handling retained.
+- R-19 PASS: short selection/press morphs and picker entrance communicate actual interaction; no looping animation; reduced-motion assertions pass.
- R-22 PASS: no illustrations created.
### Liveliness
-- Dials PASS: ENERGY 2 / RHYTHM 2 / MOTION 1 declared and maintained.
+- Dials PASS: ENERGY 2 / RHYTHM 2 / MOTION 2 declared and maintained.
- Focal point PASS: the working specimen anchors typography; other screens use task titles and the relevant action/list.
- Whitespace PASS: settings groups, profile controls, specimen, and explanation use distinct spacing levels.
- Accent PASS: lavender identifies selection, primary actions, and the specimen.
@@ -114,7 +123,7 @@ Each PASS below is limited to the Chromium runtime, source review, and build/lin
- R-11 PASS: profile cards, grouping panels, controls, and asymmetric selected shapes have distinct radii.
- R-15 PASS: library action now says Choose regular font; other actions name their behaviour.
- R-16 PASS: vague promotional UI copy replaced by specific setting descriptions.
-- R-20 PASS: existing Fontify icon, Outfit, lavender M3E palette, and typography specimen retain the product identity.
+- R-20 PASS: existing Fontify icon, Outfit fallback, lavender M3E palette, and typography specimen retain identity while the interface demonstrates the user's font choices.
- R-21 PASS: existing light/dark/system controls remain functional; system toggle corrected.
- R-29 PASS: existing coherent M3E tonal palette retained without additional core colours.
- R-30 PASS: no unrelated product's visual design copied.
diff --git a/public/symbols/LICENSE b/public/symbols/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/public/symbols/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/public/symbols/block-0.svg b/public/symbols/block-0.svg
new file mode 100644
index 0000000..a5e0afb
--- /dev/null
+++ b/public/symbols/block-0.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/symbols/block-1.svg b/public/symbols/block-1.svg
new file mode 100644
index 0000000..a5e0afb
--- /dev/null
+++ b/public/symbols/block-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/symbols/collections_bookmark-0.svg b/public/symbols/collections_bookmark-0.svg
new file mode 100644
index 0000000..d31dcaa
--- /dev/null
+++ b/public/symbols/collections_bookmark-0.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/symbols/collections_bookmark-1.svg b/public/symbols/collections_bookmark-1.svg
new file mode 100644
index 0000000..b3d5979
--- /dev/null
+++ b/public/symbols/collections_bookmark-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/symbols/settings-0.svg b/public/symbols/settings-0.svg
new file mode 100644
index 0000000..5f611c5
--- /dev/null
+++ b/public/symbols/settings-0.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/symbols/settings-1.svg b/public/symbols/settings-1.svg
new file mode 100644
index 0000000..0a59084
--- /dev/null
+++ b/public/symbols/settings-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/symbols/text_fields-0.svg b/public/symbols/text_fields-0.svg
new file mode 100644
index 0000000..5cd2463
--- /dev/null
+++ b/public/symbols/text_fields-0.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/symbols/text_fields-1.svg b/public/symbols/text_fields-1.svg
new file mode 100644
index 0000000..5cd2463
--- /dev/null
+++ b/public/symbols/text_fields-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/symbols/tune-0.svg b/public/symbols/tune-0.svg
new file mode 100644
index 0000000..4b10702
--- /dev/null
+++ b/public/symbols/tune-0.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/symbols/tune-1.svg b/public/symbols/tune-1.svg
new file mode 100644
index 0000000..4b10702
--- /dev/null
+++ b/public/symbols/tune-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/ui.css b/public/ui.css
index 8f2ca10..f9ce77e 100644
--- a/public/ui.css
+++ b/public/ui.css
@@ -5,8 +5,9 @@
font-display: swap;
}
:root {
- font-family: Outfit, system-ui, sans-serif;
- font-synthesis: none;
+ font-family: var(--fontify-regular-family, Outfit, system-ui, sans-serif);
+ font-weight: var(--fontify-regular-weight, 400);
+ font-synthesis: weight style;
color-scheme: dark;
background: #141217;
color: #ece6f0;
@@ -26,7 +27,23 @@ textarea {
button {
cursor: pointer;
}
+h1,
+h2,
+h3,
+strong,
+b {
+ font-family: var(--fontify-bold-family, inherit);
+ font-weight: var(--fontify-bold-weight, 700);
+}
+em,
+i {
+ font-family: var(--fontify-italic-family, inherit);
+ font-weight: var(--fontify-italic-weight, 400);
+}
m3e-theme {
+ font-family: var(--fontify-regular-family, Outfit, system-ui, sans-serif);
+ font-weight: var(--fontify-regular-weight, 400);
+ --m3e-button-label-text-font-weight: var(--fontify-regular-weight, 400);
--m3e-button-container-height: 44px;
--m3e-icon-button-container-height: 44px;
--m3e-icon-button-container-width: 44px;
@@ -61,18 +78,18 @@ p {
}
h1 {
font-size: clamp(38px, 5vw, 58px);
- font-weight: 650;
+ font-weight: var(--fontify-bold-weight, 700);
letter-spacing: -2px;
line-height: 1.08;
}
h2 {
font-size: 24px;
letter-spacing: -0.6px;
- font-weight: 600;
+ font-weight: var(--fontify-bold-weight, 700);
}
h3 {
font-size: 19px;
- font-weight: 550;
+ font-weight: var(--fontify-bold-weight, 700);
}
.muted {
color: var(--md-sys-color-on-surface-variant);
@@ -84,7 +101,7 @@ h3 {
color: var(--md-sys-color-on-surface-variant);
}
.shell {
- max-width: 1240px;
+ max-width: 1360px;
margin: auto;
padding: 30px 40px;
}
@@ -121,8 +138,8 @@ h3 {
}
.layout {
display: grid;
- grid-template-columns: 195px 1fr;
- gap: 38px;
+ grid-template-columns: 260px minmax(0, 1fr);
+ gap: 40px;
}
.layout > *,
.two-col > *,
@@ -142,32 +159,79 @@ m3e-button {
.nav {
display: flex;
flex-direction: column;
- gap: 7px;
+ gap: 10px;
}
.nav button {
- min-height: 44px;
+ min-height: 76px;
display: flex;
align-items: center;
- gap: 13px;
- padding: 16px 18px;
+ gap: 14px;
+ padding: 12px 16px;
+ font-size: 17px;
text-align: left;
background: transparent;
border: 0;
color: var(--md-sys-color-on-surface-variant);
border-radius: 24px;
transition:
- background 150ms,
- border-radius 150ms;
+ border-radius 260ms cubic-bezier(0.2, 0.8, 0.2, 1),
+ translate 260ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.nav button[aria-current="page"] {
background: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-on-secondary-container);
- font-weight: 600;
- border-radius: 28px 12px 28px 28px;
+ font-family: var(--fontify-bold-family, inherit);
+ font-weight: var(--fontify-bold-weight, 700);
+ border-radius: 32px 16px 32px 32px;
}
-.nav button:hover {
+.nav button:hover:not([aria-current="page"]) {
background: var(--md-sys-color-surface-container-high);
}
+.nav button:active {
+ border-radius: 18px;
+}
+.nav-icon {
+ width: 48px;
+ height: 48px;
+ display: grid;
+ place-items: center;
+ flex: none;
+ border-radius: 16px;
+ transition:
+ border-radius 280ms cubic-bezier(0.2, 0.8, 0.2, 1),
+ transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1);
+}
+.symbol {
+ grid-area: 1 / 1;
+ display: block;
+ width: 30px;
+ height: 30px;
+ background: currentColor;
+ mask: var(--symbol) center / contain no-repeat;
+ transition: opacity 180ms;
+}
+.symbol-filled {
+ opacity: 0;
+}
+.nav button[aria-current="page"] .nav-icon {
+ border-radius: 24px;
+ background: var(--md-sys-color-primary-container);
+ color: var(--md-sys-color-on-primary-container);
+}
+.nav button[aria-current="page"] .symbol-filled {
+ opacity: 1;
+}
+.nav button[aria-current="page"] .symbol-outline {
+ opacity: 0;
+}
+.nav button:hover .nav-icon {
+ transform: scale(1.08);
+ border-radius: 24px;
+}
+.nav button:active .nav-icon {
+ transform: scale(0.94);
+ border-radius: 14px;
+}
.nav-note {
padding: 22px 16px;
margin-top: 30px;
@@ -223,6 +287,7 @@ m3e-button {
color: var(--md-sys-color-on-secondary-container);
text-align: left;
gap: 8px;
+ transition: border-radius 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.font-choice span {
overflow: hidden;
@@ -230,7 +295,12 @@ m3e-button {
white-space: nowrap;
}
.font-choice:hover {
- filter: brightness(1.07);
+ border-radius: 24px;
+ background: var(--md-sys-color-primary-container);
+ color: var(--md-sys-color-on-primary-container);
+}
+.font-choice:active {
+ border-radius: 12px;
}
label {
display: block;
@@ -299,9 +369,7 @@ textarea {
.preview small {
display: block;
margin-top: 20px;
- font:
- 12px Outfit,
- sans-serif;
+ font-size: 12px;
}
.settings-row {
display: flex;
@@ -460,6 +528,7 @@ textarea {
flex-direction: column;
overflow: hidden;
box-shadow: 0 12px 50px #0003;
+ animation: picker-enter 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.picker-head {
padding: 24px 24px 16px;
@@ -482,6 +551,10 @@ textarea {
padding: 8px 13px;
font-size: 12px;
min-height: 44px;
+ transition: border-radius 220ms;
+}
+.chip:active {
+ border-radius: 12px;
}
.chip[aria-pressed="true"] {
background: var(--md-sys-color-secondary-container);
@@ -534,6 +607,13 @@ textarea {
width: 44px;
height: 44px;
font-size: 19px;
+ transition: border-radius 220ms;
+}
+.icon-btn:hover {
+ border-radius: 15px;
+}
+.icon-btn:active {
+ border-radius: 10px;
}
.icon-btn.active {
background: var(--md-sys-color-primary-container);
@@ -549,14 +629,17 @@ textarea {
gap: 12px;
}
.popup {
- width: min(390px, 100vw);
+ width: min(520px, 100vw);
min-height: 540px;
}
+.toolbar-popup {
+ width: 520px;
+}
.popup m3e-theme {
min-height: 540px;
}
.popup .shell {
- padding: 22px;
+ padding: 24px;
}
.popup .topbar {
margin-bottom: 22px;
@@ -565,14 +648,14 @@ textarea {
font-size: 26px;
}
.popup h1 {
- font-size: 39px;
- letter-spacing: -1.5px;
+ font-size: 30px;
+ letter-spacing: -0.8px;
}
.popup .hero {
- margin-bottom: 20px;
+ margin-bottom: 16px;
}
.popup .hero .muted {
- font-size: 13px;
+ font-size: 14px;
margin-top: 8px;
}
.popup .panel {
@@ -580,7 +663,7 @@ textarea {
border-radius: 22px;
}
.popup .preview {
- padding: 22px;
+ padding: 20px;
margin: 15px 0;
}
.popup .preview h2 {
@@ -590,6 +673,10 @@ textarea {
.popup .preview p {
font-size: 14px;
}
+.popup .preview strong,
+.popup .preview em {
+ margin-top: 8px;
+}
.popup .preview small {
margin-top: 12px;
}
@@ -668,6 +755,7 @@ textarea {
.nav button {
flex: none;
padding: 12px 16px;
+ min-height: 64px;
}
.nav-note {
display: none;
@@ -713,6 +801,15 @@ textarea {
padding: 12px 10px;
font-size: 13px;
}
+ .nav-icon {
+ width: 36px;
+ height: 36px;
+ border-radius: 14px;
+ }
+ .symbol {
+ width: 26px;
+ height: 26px;
+ }
.shell {
padding: 20px 16px;
}
@@ -771,7 +868,17 @@ textarea {
font-size: 29px;
}
.popup h1 {
- font-size: 32px;
+ font-size: 28px;
+ }
+}
+@keyframes picker-enter {
+ from {
+ opacity: 0;
+ transform: translateY(8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
diff --git a/src/ui.js b/src/ui.js
index ab67fea..45e5e89 100644
--- a/src/ui.js
+++ b/src/ui.js
@@ -8,6 +8,10 @@ import { normalise, DEFAULTS, matchesSite, alias, fontKey } from "./model.js";
import { customList, importFont, fontDB, loadFace } from "./fonts.js";
const popup = document.body.dataset.page === "popup";
document.body.classList.toggle("popup", popup);
+document.body.classList.toggle(
+ "toolbar-popup",
+ popup && api.extension.getViews({ type: "popup" }).includes(window),
+);
const app = document.getElementById("app");
let s = normalise(),
catalogue = [],
@@ -37,12 +41,24 @@ const loaded = new Set();
let pickerTrigger;
let editTrigger;
const nav = [
- ["typography", "Aa", "Typography"],
- ["library", "Ff", "Font library"],
- ["exceptions", "⊘", "Exceptions"],
- ["rules", "↳", "Overrides"],
- ["preferences", "⚙", "Preferences"],
+ ["typography", "text_fields", "Typography"],
+ ["library", "collections_bookmark", "Font library"],
+ ["exceptions", "block", "Exceptions"],
+ ["rules", "tune", "Overrides"],
+ ["preferences", "settings", "Preferences"],
];
+function navIcon(name) {
+ return html`
+
+
+ `;
+}
function tell(message) {
toast = message;
clearTimeout(toastTimer);
@@ -1282,10 +1298,51 @@ async function previewResult(font, button) {
}
}
let previewRun = 0;
+async function applyInterfaceFonts(run) {
+ const family = s.sansSerif.family || s.regular.family;
+ const profiles = [
+ [
+ "regular",
+ family,
+ s.sansSerif.family ? s.sansSerif.weight : s.regular.weight,
+ "normal",
+ ],
+ ["bold", s.bold.family || family, s.bold.weight, "normal"],
+ ["italic", s.italic.family || family, s.italic.weight, "italic"],
+ ];
+ const results = await Promise.allSettled(
+ profiles.map(async ([, family, weight, style]) => {
+ const key = fontKey(family, weight, style);
+ if (!loaded.has(key)) {
+ await loadFace({ family, weight, style });
+ loaded.add(key);
+ }
+ }),
+ );
+ if (run !== previewRun) return "";
+ profiles.forEach(([slot, family, weight], index) => {
+ const available = results[index].status === "fulfilled";
+ document.documentElement.style.setProperty(
+ `--fontify-${slot}-family`,
+ available
+ ? `"${alias(family)}", Outfit, system-ui, sans-serif`
+ : "Outfit, system-ui, sans-serif",
+ );
+ document.documentElement.style.setProperty(
+ `--fontify-${slot}-weight`,
+ String(weight),
+ );
+ });
+ return (
+ results.find((result) => result.status === "rejected")?.reason?.message ||
+ ""
+ );
+}
async function preview() {
if (!ready) return;
const run = ++previewRun;
- let errorMessage = "";
+ let errorMessage = await applyInterfaceFonts(run);
+ if (run !== previewRun) return;
for (const slot of [
"regular",
"bold",
@@ -1343,6 +1400,7 @@ function draw() {
html`