Font Customizer

Customize fonts for any website through the Tampermonkey menu

  1. // ==UserScript==
  2. // @name Font Customizer
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1
  5. // @description Customize fonts for any website through the Tampermonkey menu
  6. // @author Cursor, claude-3.7, and me(qooo).
  7. // @license MIT
  8. // @match *://*/*
  9. // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik05LjkzIDEzLjVoNC4xNEwxMiA3Ljk4ek0yMCAySDRjLTEuMSAwLTIgLjktMiAydjE2YzAgMS4xLjkgMiAyIDJoMTZjMS4xIDAgMi0uOSAyLTJWNGMwLTEuMS0uOS0yLTItMm0tNC4wNSAxNi41bC0xLjE0LTNIOS4xN2wtMS4xMiAzSDUuOTZsNS4xMS0xM2gxLjg2bDUuMTEgMTN6Ii8+PC9zdmc+
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_unregisterMenuCommand
  12. // @grant GM_addStyle
  13. // @grant GM_xmlhttpRequest
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @connect fonts.googleapis.com
  17. // @connect fonts.gstatic.com
  18. // ==/UserScript==
  19.  
  20. (function () {
  21. "use strict";
  22.  
  23. // Storage keys
  24. const STORAGE_KEY_PREFIX = "fontCustomizer_";
  25. const ENABLED_SUFFIX = "_enabled";
  26. const FONT_SUFFIX = "_font";
  27. const FONT_LIST_KEY = "fontCustomizer_globalFonts";
  28.  
  29. // Common web fonts from Google Fonts
  30. const WEB_FONTS = [
  31. {
  32. name: "Roboto",
  33. family: "Roboto, sans-serif",
  34. url: "https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap",
  35. },
  36. {
  37. name: "Open Sans",
  38. family: "'Open Sans', sans-serif",
  39. url: "https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&display=swap",
  40. },
  41. {
  42. name: "Lato",
  43. family: "Lato, sans-serif",
  44. url: "https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap",
  45. },
  46. {
  47. name: "Montserrat",
  48. family: "Montserrat, sans-serif",
  49. url: "https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap",
  50. },
  51. {
  52. name: "Poppins",
  53. family: "Poppins, sans-serif",
  54. url: "https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap",
  55. },
  56. {
  57. name: "Source Sans Pro",
  58. family: "'Source Sans Pro', sans-serif",
  59. url: "https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;600;700&display=swap",
  60. },
  61. {
  62. name: "Ubuntu",
  63. family: "Ubuntu, sans-serif",
  64. url: "https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500;700&display=swap",
  65. },
  66. {
  67. name: "Nunito",
  68. family: "Nunito, sans-serif",
  69. url: "https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700&display=swap",
  70. },
  71. {
  72. name: "Playfair Display",
  73. family: "'Playfair Display', serif",
  74. url: "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700&display=swap",
  75. },
  76. {
  77. name: "Merriweather",
  78. family: "Merriweather, serif",
  79. url: "https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&display=swap",
  80. },
  81. ];
  82.  
  83. // Load a web font
  84. function loadWebFont(fontUrl) {
  85. GM_xmlhttpRequest({
  86. method: "GET",
  87. url: fontUrl,
  88. onload: function (response) {
  89. const style = document.createElement("style");
  90. style.textContent = response.responseText;
  91. document.head.appendChild(style);
  92. },
  93. });
  94. }
  95.  
  96. // Get saved fonts or initialize with empty array
  97. function getSavedFonts() {
  98. return GM_getValue(FONT_LIST_KEY, []);
  99. }
  100.  
  101. // Save a font to the list if it doesn't exist already
  102. function saveFontToList(font) {
  103. const fonts = getSavedFonts();
  104. if (!fonts.includes(font)) {
  105. fonts.push(font);
  106. GM_setValue(FONT_LIST_KEY, fonts);
  107. }
  108. }
  109.  
  110. // Remove a font from the saved list
  111. function removeFontFromList(font) {
  112. const fonts = getSavedFonts();
  113. const index = fonts.indexOf(font);
  114. if (index !== -1) {
  115. fonts.splice(index, 1);
  116. GM_setValue(FONT_LIST_KEY, fonts);
  117. }
  118. }
  119.  
  120. // Get current hostname
  121. const hostname = window.location.hostname;
  122.  
  123. // Storage helper functions
  124. function getStorageKey(suffix) {
  125. return STORAGE_KEY_PREFIX + hostname + suffix;
  126. }
  127.  
  128. function isEnabledForSite() {
  129. return localStorage.getItem(getStorageKey(ENABLED_SUFFIX)) === "true";
  130. }
  131.  
  132. function setEnabledForSite(enabled) {
  133. localStorage.setItem(getStorageKey(ENABLED_SUFFIX), enabled.toString());
  134. }
  135.  
  136. function getFontForSite() {
  137. return localStorage.getItem(getStorageKey(FONT_SUFFIX)) || "";
  138. }
  139.  
  140. function setFontForSite(font) {
  141. localStorage.setItem(getStorageKey(FONT_SUFFIX), font);
  142. }
  143.  
  144. // Apply font to the website
  145. function applyFont() {
  146. if (isEnabledForSite()) {
  147. const font = getFontForSite();
  148. GM_addStyle(`
  149. * {
  150. font-family: ${font} !important;
  151. }
  152. `);
  153. }
  154. }
  155.  
  156. // Remove applied font styles
  157. function removeAppliedFont() {
  158. const styleId = "font-customizer-styles";
  159. const existingStyle = document.getElementById(styleId);
  160. if (existingStyle) {
  161. existingStyle.remove();
  162. }
  163. applyStyles();
  164. }
  165.  
  166. // Apply all necessary styles
  167. function applyStyles() {
  168. if (isEnabledForSite()) {
  169. const font = getFontForSite();
  170. const styleElement = document.createElement("style");
  171. styleElement.id = "font-customizer-styles";
  172. styleElement.textContent = `
  173. * {
  174. font-family: ${font} !important;
  175. }
  176. `;
  177. document.head.appendChild(styleElement);
  178. }
  179. }
  180.  
  181. // Menu command IDs
  182. let toggleCommandId = null;
  183. let fontCommandId = null;
  184.  
  185. // Register menu commands
  186. function registerMenuCommands() {
  187. if (toggleCommandId !== null) {
  188. GM_unregisterMenuCommand(toggleCommandId);
  189. }
  190. if (fontCommandId !== null) {
  191. GM_unregisterMenuCommand(fontCommandId);
  192. }
  193.  
  194. const enabled = isEnabledForSite();
  195. const toggleText = enabled
  196. ? "🟢 Enabled on the site"
  197. : "🔴 Disabled on the site";
  198.  
  199. toggleCommandId = GM_registerMenuCommand(toggleText, function () {
  200. const newEnabledState = !enabled;
  201. setEnabledForSite(newEnabledState);
  202.  
  203. if (newEnabledState) {
  204. applyStyles();
  205. } else {
  206. removeAppliedFont();
  207. }
  208.  
  209. registerMenuCommands();
  210. });
  211.  
  212. const currentFont = getFontForSite();
  213. // Truncate the font name if it's too long (more than x characters)
  214. const truncatedFont =
  215. currentFont && currentFont.length > 10
  216. ? currentFont.substring(0, 10) + "..."
  217. : currentFont || "None";
  218.  
  219. fontCommandId = GM_registerMenuCommand(
  220. `✨ Select Font (Current: ${truncatedFont})`,
  221. showFontSelector
  222. );
  223. }
  224.  
  225. // Create and show font selector popup
  226. function showFontSelector() {
  227. // Remove existing popup if any
  228. const existingPopup = document.getElementById("font-customizer-popup");
  229. if (existingPopup) {
  230. existingPopup.remove();
  231. }
  232.  
  233. // Create popup container
  234. const popup = document.createElement("div");
  235. popup.id = "font-customizer-popup";
  236.  
  237. // Add styles for the popup
  238. GM_addStyle(`
  239. #font-customizer-popup {
  240. position: fixed;
  241. top: 50%;
  242. left: 50%;
  243. transform: translate(-50%, -50%);
  244. background-color: var(--popup-bg, #ffffff);
  245. color: var(--popup-text, #000000);
  246. border: 1px solid var(--popup-border, #cccccc);
  247. border-radius: 12px;
  248. padding: 24px;
  249. z-index: 9999;
  250. min-width: 380px;
  251. width: 400px;
  252. height: fit-content;
  253. overflow-y: auto;
  254. box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  255. font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  256. animation: popup-fade-in 0.2s ease-out;
  257. }
  258.  
  259. @keyframes popup-fade-in {
  260. from { opacity: 0; transform: translate(-50%, -48%); }
  261. to { opacity: 1; transform: translate(-50%, -50%); }
  262. }
  263.  
  264. #font-customizer-popup h2 {
  265. margin-top: 0;
  266. margin-bottom: 20px;
  267. font-size: 20px;
  268. font-weight: 600;
  269. text-align: center;
  270. color: var(--popup-title, inherit);
  271. }
  272.  
  273. #font-customizer-popup .font-input-container {
  274. margin-bottom: 16px;
  275. }
  276.  
  277. #font-customizer-popup .font-input {
  278. width: 100%;
  279. padding: 12px;
  280. border: 1px solid var(--popup-border, #cccccc);
  281. border-radius: 8px;
  282. box-sizing: border-box;
  283. font-size: 14px;
  284. transition: border-color 0.2s;
  285. margin-bottom: 8px;
  286. }
  287.  
  288. #font-customizer-popup .font-input:focus {
  289. border-color: var(--popup-button, #4a86e8);
  290. outline: none;
  291. box-shadow: 0 0 0 2px rgba(74, 134, 232, 0.2);
  292. }
  293.  
  294. #font-customizer-popup .add-font-button {
  295. display: block;
  296. width: 100%;
  297. padding: 8px 16px;
  298. background-color: var(--popup-button, #4a86e8);
  299. color: white;
  300. border: none;
  301. border-radius: 8px;
  302. cursor: pointer;
  303. font-weight: 600;
  304. font-size: 14px;
  305. transition: background-color 0.2s, transform 0.1s;
  306. }
  307.  
  308. #font-customizer-popup .add-font-button:hover {
  309. background-color: var(--popup-button-hover, #3b78e7);
  310. }
  311.  
  312. #font-customizer-popup .add-font-button:active {
  313. transform: scale(0.98);
  314. }
  315.  
  316. #font-customizer-popup .section-title {
  317. font-size: 16px;
  318. font-weight: 600;
  319. margin: 16px 0 8px 0;
  320. color: var(--popup-title, inherit);
  321. display: flex;
  322. align-items: center;
  323. justify-content: space-between;
  324. }
  325.  
  326. #font-customizer-popup .section-title .title-with-info {
  327. display: flex;
  328. align-items: center;
  329. gap: 8px;
  330. }
  331. #font-customizer-popup .toggle-section{
  332. cursor: pointer;
  333. border: 1px solid var(--popup-border, #cccccc);
  334. border-radius: 8px;
  335. padding: 4px 8px;
  336. font-size: 12px;
  337. font-weight: 600;
  338. color: var(--popup-text-secondary);
  339. }
  340. #font-customizer-popup .toggle-section:hover {
  341. background-color: var(--popup-hover, #f5f5f5);
  342. }
  343.  
  344. #font-customizer-popup .info-icon {
  345. cursor: help;
  346. color: var(--popup-text-secondary);
  347. font-size: 14px;
  348. position: relative;
  349. }
  350.  
  351. #font-customizer-popup .info-tooltip {
  352. visibility: hidden;
  353. position: absolute;
  354. left: 50%;
  355. transform: translateX(-50%);
  356. bottom: calc(100% + 8px);
  357. color: var(--popup-tooltip-text);
  358. padding: 8px 12px;
  359. background-color: var(--popup-tooltip-bg);
  360. border-radius: 6px;
  361. font-size: 12px;
  362. font-weight: normal;
  363. white-space: normal;
  364. text-wrap: wrap;
  365. z-index: 10000;
  366. opacity: 0;
  367. transition: opacity 0.2s, visibility 0.2s;
  368. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  369. width: 250px;
  370. line-height: 1.4;
  371. }
  372.  
  373. #font-customizer-popup .info-tooltip::after {
  374. content: '';
  375. position: absolute;
  376. top: 100%;
  377. left: 50%;
  378. transform: translateX(-50%);
  379. border-width: 5px;
  380. border-style: solid;
  381. border-color: var(--popup-tooltip-bg) transparent transparent transparent;
  382. }
  383.  
  384. #font-customizer-popup .info-icon:hover .info-tooltip {
  385. visibility: visible;
  386. opacity: 1;
  387. }
  388.  
  389. #font-customizer-popup .no-fonts-message {
  390. color: var(--popup-text-secondary, #666666);
  391. font-style: italic;
  392. text-align: center;
  393. padding: 16px 0;
  394. }
  395.  
  396. #font-customizer-popup ul {
  397. list-style: none;
  398. padding: 0;
  399. margin: 0 0 16px 0;
  400. max-height: 200px;
  401. overflow-y: auto;
  402. border-radius: 8px;
  403. border: 1px solid var(--popup-border, #eaeaea);
  404. }
  405.  
  406. #font-customizer-popup ul:empty {
  407. display: none;
  408. }
  409.  
  410. #font-customizer-popup ul::-webkit-scrollbar {
  411. width: 8px;
  412. }
  413.  
  414. #font-customizer-popup ul::-webkit-scrollbar-track {
  415. background: var(--popup-scrollbar-track, #f1f1f1);
  416. border-radius: 0 8px 8px 0;
  417. }
  418.  
  419. #font-customizer-popup ul::-webkit-scrollbar-thumb {
  420. background: var(--popup-scrollbar-thumb, #c1c1c1);
  421. border-radius: 4px;
  422. }
  423.  
  424. #font-customizer-popup ul::-webkit-scrollbar-thumb:hover {
  425. background: var(--popup-scrollbar-thumb-hover, #a1a1a1);
  426. }
  427.  
  428. #font-customizer-popup li {
  429. padding: 10px 16px;
  430. cursor: pointer;
  431. transition: all 0.15s ease;
  432. border-bottom: 1px solid var(--popup-border, #eaeaea);
  433. display: flex;
  434. align-items: center;
  435. justify-content: space-between;
  436. }
  437.  
  438. #font-customizer-popup li:last-child {
  439. border-bottom: none;
  440. }
  441.  
  442. #font-customizer-popup li:hover {
  443. background-color: var(--popup-hover, #f5f5f5);
  444. }
  445.  
  446. #font-customizer-popup li.selected {
  447. background-color: var(--popup-selected, #e8f0fe);
  448. font-weight: 500;
  449. }
  450.  
  451. #font-customizer-popup li.selected .font-name::before {
  452. content: "✓";
  453. margin-right: 8px;
  454. color: var(--popup-check, #4a86e8);
  455. font-weight: bold;
  456. }
  457.  
  458. #font-customizer-popup li:not(.selected) .font-name {
  459. padding-left: 24px;
  460. }
  461.  
  462. #font-customizer-popup .font-actions {
  463. display: flex;
  464. opacity: 0;
  465. transition: opacity 0.2s;
  466. }
  467.  
  468. #font-customizer-popup li:hover .font-actions {
  469. opacity: 1;
  470. }
  471.  
  472. #font-customizer-popup .delete-font {
  473. color: var(--popup-delete, #e53935);
  474. cursor: pointer;
  475. font-size: 16px;
  476. padding: 4px;
  477. border-radius: 4px;
  478. transition: background-color 0.2s;
  479. }
  480.  
  481. #font-customizer-popup .delete-font:hover {
  482. background-color: var(--popup-delete-hover, rgba(229, 57, 53, 0.1));
  483. }
  484.  
  485. #font-customizer-popup .close-button {
  486. display: block;
  487. width: 100%;
  488. margin: 16px auto 0;
  489. padding: 12px 16px;
  490. background-color: var(--popup-button-secondary, #757575);
  491. color: white;
  492. border: none;
  493. border-radius: 8px;
  494. cursor: pointer;
  495. font-weight: 600;
  496. font-size: 15px;
  497. transition: background-color 0.2s, transform 0.1s;
  498. }
  499.  
  500. #font-customizer-popup .close-button:hover {
  501. background-color: var(--popup-button-secondary-hover, #616161);
  502. }
  503.  
  504. #font-customizer-popup .close-button:active {
  505. transform: scale(0.98);
  506. }
  507.  
  508. #font-customizer-popup .web-font-item {
  509. font-family: inherit;
  510. }
  511.  
  512. #font-customizer-popup .web-font-item .font-name {
  513. font-family: inherit;
  514. }
  515.  
  516. /* Dark mode detection and styles */
  517. @media (prefers-color-scheme: dark) {
  518. #font-customizer-popup {
  519. --popup-bg: #222222;
  520. --popup-text: #ffffff;
  521. --popup-text-secondary: #aaaaaa;
  522. --popup-title: #ffffff;
  523. --popup-border: #444444;
  524. --popup-hover: #333333;
  525. --popup-selected: #2c3e50;
  526. --popup-check: #64b5f6;
  527. --popup-button: #4a86e8;
  528. --popup-button-hover: #3b78e7;
  529. --popup-button-secondary: #616161;
  530. --popup-button-secondary-hover: #757575;
  531. --popup-delete: #f44336;
  532. --popup-delete-hover: rgba(244, 67, 54, 0.2);
  533. --popup-scrollbar-track: #333333;
  534. --popup-scrollbar-thumb: #555555;
  535. --popup-scrollbar-thumb-hover: #666666;
  536. --popup-tooltip-bg: #4a4a4a;
  537. --popup-tooltip-text: #ffffff;
  538. }
  539. }
  540.  
  541. /* Light mode styles */
  542. @media (prefers-color-scheme: light) {
  543. #font-customizer-popup {
  544. --popup-bg: #ffffff;
  545. --popup-text: #333333;
  546. --popup-text-secondary: #666666;
  547. --popup-title: #222222;
  548. --popup-border: #eaeaea;
  549. --popup-hover: #f5f5f5;
  550. --popup-selected: #e8f0fe;
  551. --popup-check: #4a86e8;
  552. --popup-button: #4a86e8;
  553. --popup-button-hover: #3b78e7;
  554. --popup-button-secondary: #757575;
  555. --popup-button-secondary-hover: #616161;
  556. --popup-delete: #e53935;
  557. --popup-delete-hover: rgba(229, 57, 53, 0.1);
  558. --popup-scrollbar-track: #f1f1f1;
  559. --popup-scrollbar-thumb: #c1c1c1;
  560. --popup-scrollbar-thumb-hover: #a1a1a1;
  561. --popup-tooltip-bg: #ffffff;
  562. --popup-tooltip-text: #333333;
  563. }
  564. }
  565.  
  566. /* Overlay to prevent clicking outside */
  567. #font-customizer-overlay {
  568. position: fixed;
  569. top: 0;
  570. left: 0;
  571. width: 100%;
  572. height: 100%;
  573. background: rgba(0, 0, 0, 0.5);
  574. z-index: 9998;
  575. animation: overlay-fade-in 0.2s ease-out;
  576. }
  577.  
  578. @keyframes overlay-fade-in {
  579. from { opacity: 0; }
  580. to { opacity: 1; }
  581. }
  582. `);
  583.  
  584. // Create overlay to prevent clicking outside
  585. const overlay = document.createElement("div");
  586. overlay.id = "font-customizer-overlay";
  587. document.body.appendChild(overlay);
  588.  
  589. // Add click event to overlay to close popup
  590. overlay.addEventListener("click", closePopup);
  591.  
  592. // Create popup content
  593. popup.innerHTML = `
  594. <h2>Font Customizer</h2>
  595. <div class="font-input-container">
  596. <input type="text" id="new-font-input" class="font-input" placeholder="Enter font name (e.g., Arial, sans-serif)">
  597. <button id="add-font-button" class="add-font-button">Add & Apply Font</button>
  598. </div>
  599. <div class="section-title">
  600. <div class="title-with-info">
  601. <span>Your Saved Fonts</span>
  602. <span class="info-icon">ℹ️
  603. <span class="info-tooltip">Custom fonts will only work if they are installed on your system. Use exact font names for best results.</span>
  604. </span>
  605. </div>
  606. <span class="toggle-section" id="toggle-saved">Hide</span>
  607. </div>
  608. <ul id="saved-fonts-list"></ul>
  609. <div id="no-saved-fonts" class="no-fonts-message">No saved fonts or Hiden yet. Add one above!</div>
  610. <div class="section-title">
  611. <div class="title-with-info">
  612. <span>Web Fonts</span>
  613. <span class="info-icon">ℹ️
  614. <span class="info-tooltip">These fonts will be loaded from Google Fonts when selected. They work on any system but require internet connection.</span>
  615. </span>
  616. </div>
  617. <span class="toggle-section" id="toggle-web">Show</span>
  618. </div>
  619. <ul id="web-fonts-list" style="display: none;"></ul>
  620. <button class="close-button" id="close-popup">Close</button>
  621. `;
  622.  
  623. document.body.appendChild(popup);
  624.  
  625. // Get current font and saved fonts
  626. const currentFont = getFontForSite();
  627. const savedFonts = getSavedFonts();
  628.  
  629. // Populate saved fonts list
  630. const savedFontsList = document.getElementById("saved-fonts-list");
  631. const noSavedFonts = document.getElementById("no-saved-fonts");
  632.  
  633. // Show/hide no fonts message
  634. if (savedFonts.length === 0) {
  635. noSavedFonts.style.display = "block";
  636. } else {
  637. noSavedFonts.style.display = "none";
  638. }
  639.  
  640. // Add saved fonts to the list
  641. savedFonts.forEach((font) => {
  642. addFontToList(font, savedFontsList, true);
  643. });
  644.  
  645. // Add web fonts to the list
  646. const webFontsList = document.getElementById("web-fonts-list");
  647. WEB_FONTS.forEach((font) => {
  648. addFontToList(font.name, webFontsList, false, font);
  649. });
  650.  
  651. // Toggle sections
  652. document
  653. .getElementById("toggle-saved")
  654. .addEventListener("click", function () {
  655. const savedList = document.getElementById("saved-fonts-list");
  656. const noSaved = document.getElementById("no-saved-fonts");
  657. const isHidden = savedList.style.display === "none";
  658.  
  659. savedList.style.display = isHidden ? "block" : "none";
  660. noSaved.style.display =
  661. isHidden && getSavedFonts().length === 0 ? "block" : "none";
  662. this.textContent = isHidden ? "Hide" : "Show";
  663. });
  664.  
  665. document
  666. .getElementById("toggle-web")
  667. .addEventListener("click", function () {
  668. const webList = document.getElementById("web-fonts-list");
  669. const isHidden = webList.style.display === "none";
  670.  
  671. webList.style.display = isHidden ? "block" : "none";
  672. this.textContent = isHidden ? "Hide" : "Show";
  673. });
  674.  
  675. // Handle new font input
  676. const newFontInput = document.getElementById("new-font-input");
  677. const addFontButton = document.getElementById("add-font-button");
  678.  
  679. // Focus the input field
  680. newFontInput.focus();
  681.  
  682. // Add font when button is clicked
  683. addFontButton.addEventListener("click", addNewFont);
  684.  
  685. // Add font when Enter is pressed
  686. newFontInput.addEventListener("keydown", (e) => {
  687. if (e.key === "Enter") {
  688. addNewFont();
  689. }
  690. });
  691.  
  692. // Function to add a font to the list
  693. function addFontToList(font, listElement, isSaved = false, webFont = null) {
  694. const li = document.createElement("li");
  695. if (webFont) {
  696. li.classList.add("web-font-item");
  697. li.style.fontFamily = webFont.family;
  698. }
  699.  
  700. li.innerHTML = `
  701. <span class="font-name">${font}</span>
  702. ${
  703. isSaved
  704. ? `
  705. <div class="font-actions">
  706. <span class="delete-font" title="Remove font">🗑️</span>
  707. </div>
  708. `
  709. : ""
  710. }
  711. `;
  712.  
  713. if (font === currentFont) {
  714. li.classList.add("selected");
  715. }
  716.  
  717. // Select font when clicked
  718. li.addEventListener("click", (e) => {
  719. if (e.target.classList.contains("delete-font")) {
  720. return;
  721. }
  722.  
  723. // Remove selected class from all items
  724. document
  725. .querySelectorAll("#font-customizer-popup li")
  726. .forEach((item) => {
  727. item.classList.remove("selected");
  728. });
  729.  
  730. // Add selected class to clicked item
  731. li.classList.add("selected");
  732.  
  733. // Set the selected font
  734. if (webFont) {
  735. setFontForSite(webFont.family);
  736. loadWebFont(webFont.url);
  737. } else {
  738. setFontForSite(font);
  739. }
  740.  
  741. // Apply the font if enabled
  742. if (isEnabledForSite()) {
  743. removeAppliedFont();
  744. applyStyles();
  745. }
  746.  
  747. // Update menu commands
  748. registerMenuCommands();
  749. });
  750.  
  751. // Delete font when delete button is clicked (only for saved fonts)
  752. if (isSaved) {
  753. const deleteButton = li.querySelector(".delete-font");
  754. deleteButton.addEventListener("click", (e) => {
  755. e.stopPropagation();
  756.  
  757. removeFontFromList(font);
  758. li.remove();
  759.  
  760. if (font === currentFont) {
  761. const remainingFonts = getSavedFonts();
  762. if (remainingFonts.length > 0) {
  763. setFontForSite(remainingFonts[0]);
  764. const firstFont = document.querySelector("#saved-fonts-list li");
  765. if (firstFont) {
  766. firstFont.classList.add("selected");
  767. }
  768. } else {
  769. setFontForSite("");
  770. }
  771.  
  772. if (isEnabledForSite()) {
  773. removeAppliedFont();
  774. applyStyles();
  775. }
  776.  
  777. registerMenuCommands();
  778. }
  779.  
  780. if (savedFontsList.children.length === 0) {
  781. noSavedFonts.style.display = "block";
  782. }
  783. });
  784. }
  785.  
  786. listElement.appendChild(li);
  787. }
  788.  
  789. // Function to add a new font
  790. function addNewFont() {
  791. const fontName = newFontInput.value.trim();
  792. if (fontName) {
  793. saveFontToList(fontName);
  794. newFontInput.value = "";
  795. noSavedFonts.style.display = "none";
  796. addFontToList(fontName, savedFontsList, true);
  797. setFontForSite(fontName);
  798.  
  799. document
  800. .querySelectorAll("#font-customizer-popup li")
  801. .forEach((item) => {
  802. item.classList.remove("selected");
  803. });
  804.  
  805. const newFontElement = Array.from(
  806. document.querySelectorAll("#saved-fonts-list li")
  807. ).find((li) => li.querySelector(".font-name").textContent === fontName);
  808. if (newFontElement) {
  809. newFontElement.classList.add("selected");
  810. }
  811.  
  812. if (isEnabledForSite()) {
  813. removeAppliedFont();
  814. applyStyles();
  815. }
  816.  
  817. registerMenuCommands();
  818. }
  819. }
  820.  
  821. // Function to close the popup
  822. function closePopup() {
  823. const popup = document.getElementById("font-customizer-popup");
  824. const overlay = document.getElementById("font-customizer-overlay");
  825. if (popup) popup.remove();
  826. if (overlay) overlay.remove();
  827. }
  828.  
  829. // Close button functionality
  830. document
  831. .getElementById("close-popup")
  832. .addEventListener("click", closePopup);
  833.  
  834. // Prevent closing when clicking on the popup itself
  835. popup.addEventListener("click", (e) => {
  836. e.stopPropagation();
  837. });
  838.  
  839. // Allow Escape key to close the popup
  840. document.addEventListener("keydown", function handleEscape(e) {
  841. if (e.key === "Escape") {
  842. closePopup();
  843. document.removeEventListener("keydown", handleEscape);
  844. }
  845. });
  846. }
  847.  
  848. // Initialize
  849. function init() {
  850. registerMenuCommands();
  851. applyStyles();
  852.  
  853. const observer = new MutationObserver(function (mutations) {
  854. if (!isEnabledForSite()) return;
  855.  
  856. const styleElement = document.getElementById("font-customizer-styles");
  857. if (!styleElement) {
  858. applyStyles();
  859. }
  860. });
  861.  
  862. observer.observe(document.documentElement, {
  863. childList: true,
  864. subtree: true,
  865. });
  866. }
  867.  
  868. // Run the script
  869. init();
  870. })();