Implement Zen Welcome page with initial animation and styling; remove deprecated files

This commit is contained in:
mr. M
2025-02-14 00:42:18 +01:00
parent 721e121ebe
commit 425d0ea951
18 changed files with 126 additions and 1118 deletions

2
l10n

Submodule l10n updated: 19e2af33c4...91d6c42eeb

View File

@@ -38,6 +38,8 @@
gZenVerticalTabsManager.init();
gZenUIManager.init();
this._checkForWelcomePage();
document.l10n.setAttributes(document.getElementById('tabs-newtab-button'), 'tabs-toolbar-new-tab');
} catch (e) {
console.error('ZenThemeModifier: Error initializing browser layout', e);
@@ -119,6 +121,17 @@
gURLBar._initPasteAndGo();
gURLBar._initStripOnShare();
},
_checkForWelcomePage() {
if (!Services.prefs.getBoolPref('zen.welcome-screen.seen', false) && Services.prefs.getBoolPref('zen.welcome-screen.enabled', true)) {
//Services.prefs.setBoolPref('zen.welcome-screen.seen', true);
console.log('ZenStartup: Show welcome page');
Services.scriptloader.loadSubScript(
"chrome://browser/content/zen-components/ZenWelcome.mjs",
window
);
}
}
};
ZenStartup.init();

View File

@@ -1,5 +1,5 @@
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 019b168c1aeae7e1c97a3ae58c99a48a27f54134..1f051e8a1e8a58e8bb721196deecfa36f4089dd6 100644
index 019b168c1aeae7e1c97a3ae58c99a48a27f54134..5225d0539aa7dabf81a8fd60af3e839f203d296c 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -32,6 +32,7 @@ ChromeUtils.defineESModuleGetters(this, {
@@ -10,23 +10,7 @@ index 019b168c1aeae7e1c97a3ae58c99a48a27f54134..1f051e8a1e8a58e8bb721196deecfa36
DevToolsSocketStatus:
"resource://devtools/shared/security/DevToolsSocketStatus.sys.mjs",
DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
@@ -632,6 +633,15 @@ XPCOMUtils.defineLazyPreferenceGetter(
false
);
+const ZEN_WELCOME_PATH = "zen-welcome";
+const ZEN_WELCOME_ELEMENT_ATTR = "zen-dialog-welcome-element";
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "ProfileService",
+ "@mozilla.org/toolkit/profile-service;1",
+ "nsIToolkitProfileService"
+);
+
customElements.setElementCreationCallback("screenshots-buttons", () => {
Services.scriptloader.loadSubScript(
"chrome://browser/content/screenshots/screenshots-buttons.js",
@@ -3440,6 +3450,10 @@ var XULBrowserWindow = {
@@ -3440,6 +3441,10 @@ var XULBrowserWindow = {
AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
TranslationsParent.onLocationChange(gBrowser.selectedBrowser);
@@ -37,7 +21,7 @@ index 019b168c1aeae7e1c97a3ae58c99a48a27f54134..1f051e8a1e8a58e8bb721196deecfa36
PictureInPicture.updateUrlbarToggle(gBrowser.selectedBrowser);
if (!gMultiProcessBrowser) {
@@ -4435,7 +4449,7 @@ nsBrowserAccess.prototype = {
@@ -4435,7 +4440,7 @@ nsBrowserAccess.prototype = {
// Passing a null-URI to only create the content window,
// and pass true for aSkipLoad to prevent loading of
// about:blank
@@ -46,7 +30,7 @@ index 019b168c1aeae7e1c97a3ae58c99a48a27f54134..1f051e8a1e8a58e8bb721196deecfa36
null,
aParams,
aWhere,
@@ -4443,6 +4457,10 @@ nsBrowserAccess.prototype = {
@@ -4443,6 +4448,10 @@ nsBrowserAccess.prototype = {
aName,
true
);
@@ -57,16 +41,3 @@ index 019b168c1aeae7e1c97a3ae58c99a48a27f54134..1f051e8a1e8a58e8bb721196deecfa36
},
openURIInFrame: function browser_openURIInFrame(
@@ -7285,6 +7303,12 @@ var gDialogBox = {
parentElement.showModal();
this._didOpenHTMLDialog = true;
+ if (uri.includes(ZEN_WELCOME_PATH)) {
+ parentElement.setAttribute(ZEN_WELCOME_ELEMENT_ATTR, true);
+ } else if (parentElement.hasAttribute(ZEN_WELCOME_ELEMENT_ATTR)) {
+ parentElement.removeAttribute(ZEN_WELCOME_ELEMENT_ATTR);
+ }
+
// Disable menus and shortcuts.
this._updateMenuAndCommandState(false /* to disable */);

View File

@@ -24,6 +24,8 @@
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-compact-mode.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/skin/zen-icons/icons.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-branding.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-welcome.css" />
</linkset>
# Scripts used all over the browser

View File

@@ -24,6 +24,7 @@
content/browser/zen-components/ZenActorsManager.mjs (zen-components/ZenActorsManager.mjs)
content/browser/zen-components/ZenRices.mjs (zen-components/ZenRices.mjs)
content/browser/zen-components/ZenEmojies.mjs (zen-components/ZenEmojies.mjs)
content/browser/zen-components/ZenWelcome.mjs (zen-components/ZenWelcome.mjs)
content/browser/zen-styles/zen-theme.css (content/zen-styles/zen-theme.css)
content/browser/zen-styles/zen-buttons.css (content/zen-styles/zen-buttons.css)
@@ -47,11 +48,12 @@
content/browser/zen-styles/zen-gradient-generator.css (content/zen-styles/zen-gradient-generator.css)
content/browser/zen-styles/zen-rices.css (content/zen-styles/zen-rices.css)
content/browser/zen-styles/zen-branding.css (content/zen-styles/zen-branding.css)
content/browser/zen-styles/zen-welcome.css (content/zen-styles/zen-welcome.css)
content/browser/zen-styles/zen-panels/bookmarks.css (content/zen-styles/zen-panels/bookmarks.css)
content/browser/zen-styles/zen-panels/extensions.css (content/zen-styles/zen-panels/extensions.css)
content/browser/zen-styles/zen-panels/print.css (content/zen-styles/zen-panels/print.css)
content/browser/zen-styles/zen-panels/welcome.css (content/zen-styles/zen-panels/welcome.css)
content/browser/zen-styles/zen-panels/dialog.css (content/zen-styles/zen-panels/dialog.css)
* content/browser/zen-styles/zen-compact-mode.css (content/zen-styles/zen-compact-mode.css)

View File

@@ -0,0 +1,11 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Zen Welcome idalog override */
@media (prefers-color-scheme: dark) {
.dialogBox:not(.spotlightBox) {
border: 1px solid var(--zen-colors-border);
}
}

View File

@@ -1,33 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Zen Welcome idalog override */
@media (prefers-color-scheme: dark) {
.dialogBox:not(.spotlightBox) {
border: 1px solid var(--zen-colors-border);
}
}
#window-modal-dialog[zen-dialog-welcome-element='true'] .dialogBox:not(.spotlightBox) {
margin: 0 !important;
}
#window-modal-dialog[zen-dialog-welcome-element='true'],
#window-modal-dialog[zen-dialog-welcome-element='true'] .dialogOverlay,
#window-modal-dialog[zen-dialog-welcome-element='true'] .dialogFrame,
#window-modal-dialog[zen-dialog-welcome-element='true'] .dialogBox {
width: 100% !important;
height: 100% !important;
max-height: none !important;
max-width: none !important;
}
#window-modal-dialog[zen-dialog-welcome-element='true'] {
--zen-welcome-dialog-space: 7px;
margin: 0 auto !important;
max-width: calc(100% - calc(var(--zen-welcome-dialog-space) * 2)) !important;
max-height: calc(100% - calc(var(--zen-welcome-dialog-space) * 2)) !important;
margin-top: var(--zen-welcome-dialog-space) !important;
}

View File

@@ -6,7 +6,7 @@
@import url('chrome://browser/content/zen-styles/zen-panels/bookmarks.css');
@import url('chrome://browser/content/zen-styles/zen-panels/extensions.css');
@import url('chrome://browser/content/zen-styles/zen-panels/print.css');
@import url('chrome://browser/content/zen-styles/zen-panels/welcome.css');
@import url('chrome://browser/content/zen-styles/zen-panels/dialog.css');
:root {
--panel-subview-body-padding: 2px 0;

View File

@@ -0,0 +1,33 @@
#zen-welcome,
#zen-welcome-start {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
#zen-welcome-start {
flex-direction: column;
#zen-welcome-start-button {
opacity: 0;
list-style-image: url(chrome://browser/skin/zen-icons/forward.svg);
position: absolute;
bottom: 10%;
}
#zen-welcome-title {
text-align: center;
font-size: 5rem;
line-height: 1.1;
max-width: 50%;
font-weight: 500;
& > span {
display: block;
opacity: 0;
}
}
}

View File

@@ -0,0 +1,57 @@
{
function clearBrowserElements() {
for (const element of document.getElementById('browser').children) {
element.style.display = 'none';
}
}
function getMotion() {
return gZenUIManager.motion;
}
async function animate(...args) {
return getMotion().animate(...args);
}
function initializeZenWelcome() {
const XUL = `
<html:div id="zen-welcome">
<html:div id="zen-welcome-start">
<html:h1 class="zen-branding-title" id="zen-welcome-title"></html:h1>
<button id="zen-welcome-start-button">
</button>
</html:div>
</html:div>
`;
const fragment = window.MozXULElement.parseXULToFragment(XUL);
document.getElementById('browser').appendChild(fragment);
window.MozXULElement.insertFTLIfNeeded("browser/zen-welcome.ftl");
}
async function animateInitialStage() {
const [title1, title2] = await document.l10n.formatValues([{id:'zen-welcome-title-line1'}, {id:'zen-welcome-title-line2'}]);
const titleElement = document.getElementById('zen-welcome-title');
titleElement.innerHTML = `<html:span>${title1}</html:span><html:span>${title2}</html:span>`;
await animate("#zen-welcome-title span", { opacity: [0, 1], y: [100, 0] }, {
delay: getMotion().stagger(0.6),
type: 'spring',
ease: 'ease-out',
bounce: 0,
});
await animate("#zen-welcome-start-button", { opacity: [0, 1], y: [100, 0] }, {
delay: 0.5,
type: 'spring',
ease: 'ease-in-out',
bounce: 0.4,
});
}
function startZenWelcome() {
clearBrowserElements();
initializeZenWelcome();
animateInitialStage();
}
startZenWelcome();
}

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/BrowserGlue.sys.mjs b/browser/components/BrowserGlue.sys.mjs
index b888a753a7f23a9800fe04da51a4e6b898314ff2..35eea774e1ea4b1807ec65ebc767f423d81602bd 100644
index b888a753a7f23a9800fe04da51a4e6b898314ff2..a6a01cf035253b05ea7b20b434cf2002ff115d96 100644
--- a/browser/components/BrowserGlue.sys.mjs
+++ b/browser/components/BrowserGlue.sys.mjs
@@ -121,6 +121,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
@@ -18,28 +18,3 @@ index b888a753a7f23a9800fe04da51a4e6b898314ff2..35eea774e1ea4b1807ec65ebc767f423
// A channel for "remote troubleshooting" code...
let channel = new lazy.WebChannel(
"remote-troubleshooting",
@@ -4761,6 +4763,7 @@ BrowserGlue.prototype = {
},
async _maybeShowDefaultBrowserPrompt() {
+ this._ZenMaybeShowWelcomeScreen();
// Highest priority is about:welcome window modal experiment
// Second highest priority is the upgrade dialog, which can include a "primary
// browser" request and is limited in various ways, e.g., major upgrades.
@@ -5302,6 +5305,16 @@ BrowserGlue.prototype = {
"nsIObserver",
"nsISupportsWeakReference",
]),
+
+ _ZenMaybeShowWelcomeScreen() {
+ const welcomeEnabled = Services.prefs.getBoolPref("zen.welcome-screen.enabled", true)
+ const welcomeSeen = Services.prefs.getBoolPref("zen.welcome-screen.seen", false)
+ if (welcomeEnabled && !welcomeSeen) {
+ lazy.BrowserWindowTracker.getTopWindow().gDialogBox.open(
+ "chrome://browser/content/zen-welcome/welcome.html"
+ );
+ }
+ },
};
var ContentBlockingCategoriesPrefs = {

View File

@@ -1,12 +0,0 @@
diff --git a/browser/components/moz.build b/browser/components/moz.build
index 6cbb7ce0037c1457eeae5c331a996719691ebd6b..611707852198740c9b4103f5e2a66e8ee4099a21 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -27,6 +27,7 @@ with Files("controlcenter/**"):
DIRS += [
+ "zen-welcome",
"about",
"aboutlogins",
"aboutwelcome",

View File

@@ -1,7 +0,0 @@
# Important notes!
Inside browser.js, we hardcoded-ly detect the path name for `zen-welcome` so we can add special attributes to the welcome page. If this path name changes, the welcome page will not work properly.
Make sure to update the path name in browser.js if you change the path name of `zen-welcome`.
The constant that contains this path name is `ZEN_WELCOME_PATH`.

View File

@@ -1,9 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
browser.jar:
% content browser %content/browser/ contentaccessible=yes
content/browser/zen-welcome/welcome.html (welcome.html)
content/browser/zen-welcome/welcome.css (welcome.css)
content/browser/zen-welcome/welcome.js (welcome.js)

View File

@@ -1,4 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
JAR_MANIFESTS += ["jar.mn"]

View File

@@ -1,451 +0,0 @@
/*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
:root {
--noise: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAABBCAMAAAC5KTl3AAAAgVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtFS1lAAAAK3RSTlMWi3QSa1uQOKBWCTwcb6V4gWInTWYOqQSGfa6XLyszmyABlFFJXySxQ0BGn2PQBgAAC4NJREFUWMMV1kWO5UAQRdFk5kwzs/33v8Cunr7ZUehKAdaRUAse99ozDjF5BqswrPKm7btzJ2tRziN3rMYXC236humIV5Our7nHWnVdFOBojW2XVnkeu1IZHNJH5OPHj9TjgVxBGBwAAmp60WoA1gBBvg3XMFhxUQ4KuLqx0CritYZPPXinsOqB7I76+OHaZlPzLEcftrqOlOwjeXvuEuH6t6emkaofgVUDIb4fEZB6CmRAeFCTq11lxbAgUyx4rXkqlH9I4bTUDRRVD1xjbqb9HyUBn7rhtr1x+x9Y0e3BdX31/loYvZaLxqnjbRuokz+pPG7WebnSNKE3yE6Tka4aDEDMVYr6Neq126c+ZR2nzzm3yyiC7PGWG/1uueqZudrVGYNdsgOMDvt1cI8CXu63QIcPvYNY8z870WwYazTS7DqpDEknZqS0AFXObWUxTaw0q5pnHlq4oQImakpLfJkmErdvAfhsc7lod0DVT4tuob25C0tQjzdiFObCz7U7eaKGP3s6yQVgQ/y+q+nY6K5dfV75iXzcNlGIP38aj22sVwtWWKMRb7B5HoHPaBvI1Ve5TSXATi66vV6utxsV+aZNFu+93VvlrG/oj8Wp67YT8l+Oq6PjwdGatFm7SEAP13kE0y9CEcf9qhtEWCMIq5AGq71moEAI9vrmFcmO8+7ZyDnmRN/VUaFkM2ce8KuBGFzDMmY6myLfQGra2ofgHhbJRXuRDZ4H+HmliWBHXQ0ysLGfv6FetbxtxzRgIZWjIsGVFl5imPXeyvVyayNek+dSWzjXd4t310YBdaF8sXeKs481PjsXbAtIru2+wHbv3GVh3sQY6Dnu6pF3pZ714VYdDi9A5GkXR/6xgaZN/tpQ8wVV3zeBuB+njoBNE4wjc+uA523ysXGd/P2sntmOb3OdHNWP5OVrxD3eJHdtH8QVkEIAqCor3hReR96yqt6PkTQfenllooQ447h6tOrnnuzwA8fMpq+jqg1oW8fTYYIncAYpVeTvkEFr/khQSbjoE8ykx9049OkE5MQEO9lC24tT7DwThQgf4Fhf8nGgAo3GYaON3crODpOr2pu5dBABz69t7F5yJBBo+r6QJdeLDWEoO7r1tceR3haA7gc7eZrCvpxSXXeKpo4P+hRixo9DeOFbqQVjKyWfBg9pnrEZKzK7R437YTTwhfoySG/YOCt3fs4aXlU3FjKortqQ6XyXaD0+Y/8VoqpyU9TRW45eN4oBxAH8Y/jLnNXfELJW+/p/MgO9Z+mBli2qqAP7dV/Arc2+YZRZwtBW8/p32y5ZsEuCS4O5AAgfR7Dde7zhiGfgvurQkfAXIrUG61rmxc2EZo18ph4vaWZI+QM0JdsbNlBJlPlwf9uguujQJy0j7TgTHdtRnjybTg55Hkk9S6l2rpYahumSewKHVosa1bh2Y6r9JGkdKvIDN/eeAwScrfjoLkCxWJuFZQ53FNP5w9XbQd1HhgHcVB/0fATG3sUUid1RTfc2+7pZVKldFSsaEK0v4k90tapQOk2HIbMhaJQtrUEL5+3sDanh8sOpbYRoQoqXWu6SQcUTQL9jzOrXNPWCJwXge4U7tlU1hkF012cAmvp8llQxf1IEMcw14pURxVOWATz4ITnYQjuF+vDXg5hgoiqXzO6mS91FQUBheURHIJxUeU1i3P0WOMpsm7vFYk0JJi/Ev+X3FwYD69cARPuP5GIc0PxoAFjcLRbNur0iMTrQmBBNYJ2ngU4x7SWfdTRl52Bqv7LmYW3C1CyTCPTHeWWIAM/Whm32COHsaj+2UQ739XB9t6NV0o9E9b7CW3XNiXzi9e0KiE+3rntukdIDBWrU2jsfQWuyFJRANxq8StHVv1JPy2C3Byco7qdNbASrnNXZ8G0L/Wp/pif4Ai9aEZ9Bb+TRx+REBdGlkF/s0dUdMSMr+6YCbuGxqPWdzcdqutvqkBzCksFcwAtjf55TeuH79M6AQa7r5PLeXxMFIlQKrXP9VJ275WGX+ptpf+tvTDBsecPnYQAlAWrVbRVJ7K2pRHwIjtSpbX96Y/lbKk6ZWXlBmh15r8yAWQsYxXgBOXYMAfHnUXF+rDqnB8bXDRtAn7bCziIqetSboK3NexMePvsCRLvmsoREA+kH8j4HWFpnNEaWgOmR7xyXHfTaz3slHc/YA6H6tl/L8d5tPcIwwD0tjvRaq3Y5BmYBSDClpv0VIX4s8D0XK3sPdpAb94HjPLkgboEz9EdZATW6ZdcmQvtKUwoWw+nAVKA7IcdY1UHnvNnIBplKci+knzewLz5/GGnzkGuuGky+0LTjtGBGR85EQICDqKChnm5pH3Z44nnWAk1YRdyu3g7QoFZ0h8jkr2ffjKmi+Qvsp+9GvNGZHmgW+YQAGUw7PPt8IPKbdy432vhKtRJjKWcSqq7helj81o3nfmaxVZ7Sqie8OOBk9WsyTD/ab7fQ5aWwQeJvnH6+ayo4IdIkOSBJjzXkgr+1TPhAx1AXDsxtCCj3TzQTLA1p782f7a8vdgPfwwrXmZxxbqo2h+6Zlo6mcMY4V7cFBOLm17VCvx9Qa2tAnkxEB+KYyQgbgAAnmNDOdOO6y2Cb+lke1MWQc9o+EMdQf7ubIG3Ek8GZ4k1PtGjbhwgOMPp5Em59JMVk/jU8/aF73Xcrd3UBNZyueQu0/xz2aGtZT8CRziOax2BWFXaeDzgZNV7oRtUzFoijoETf3xkAFFk3OMb7SgPh5wxU1+MygDIp9gZChH2qEcpgLh8pBIK90PXT1ZSU+ZExFK4Vm4GL/J7+K13lS5dQkW4HQwl6GX4yLqu8GhGWS2k75yel5IZIfFNdAL0NpKr2N5dQesBnxa42DLgJd6agS1jJsp1mO1dip7PU4P6diLLoTsZ4m3Q0QweiqeFfIGPLgF6v6mSVv6xe85VBD/1Mpe3AurRbcJ9SEo8NszNVy8rOCEexyIFcJRvYAlI/wk2I7r3p60FFLQXoH2q9xri/m41svRPbW0/EnPn2DWsmk0IiPpB60aa3+hiFfWuC8ZvWKEd9LxAk3HcOof6d77RewPaPsGw5lQAHcZN2vx1448u9pLfMLGQ3BSRRjBzRhKt7HcCw/7aqjtCDs5q76b4ZGphxN2th1WeXYlfnozX3ebKtX4Te11hf1tZP1diiGjIDAB1cR4Sb9rcFPC/nBARjlgDxd+tCBb1t91j71xJcgGjT3g/dUFnXXNiDrxkyoHANPk58ACPUa42hj8tgGrhiXOCmygxFZBiT2wyAJTDJ4wJEPmp6JIrDaSWYNqv4xH2wwdSTGYb3E0pXnS39nmLUsqoVZxzSoegqzd0o06wdbTXsaHGL+IF4JtIcXddTcD/dCd8hVf+fWPSV553kjMmMEULLS8HcgmptDO955dLGX78PjiDA6IsTHPm5IA6bc5ha0gaGkoEttXuxU11B2dOJ65/Q08tEF1+Y9cr2Nh/VECfQ33GyvR/gsdN1LuIeLpKMCAF2yRr769g9/4aJLZNRI71m2S91+Kp+Q0zubTcxoG2/6gm1Q79wkMj2XNO2ui7nWw8ULtu27CCvqTGX2PffD+xcwgh/TrOKvGZMM5jRFGDTn4NO/lwnDR/GY/waDZtkWDUPI0O8ztcFVqp6r2ZW+2bvkJ3raptYagFqu95VdIaml2CIp6CKets34x+fH2C+zH4cVFO7vj+6k2FU39PtRhWluYeZ3gDz1TLB9K2v7SD9gJU1qDxoRDrAWcrFGLyndhdtd0505+gEP79adK8fmFCWNYC+ahzVNcRH79E8dA1iqX/N0qq22xcOc20ALxLDspEj4QCFBQMgaIwoKbxr0Bd7Sbws6GiRK6tqoPfpiCle23axejRLyO1I+ahsEpWrzT5ZsCyS5RcY9jMfENFxSnhKsrfW8JHH6/rdQUMfmQPT3Uz9gY0C/pu1yuCnrPUvio0a1qMEosA/EwIzzid7cqsAAAAASUVORK5CYII=');
}
html {
width: 100%;
height: 100%;
}
body {
display: flex;
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
user-select: none;
}
body {
background: var(--zen-branding-bg);
}
#main-view {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
width: fit-content;
margin: auto;
}
#welcome .zen-branding-title,
#thanks .zen-branding-title {
text-align: center;
font-size: 7rem;
}
#buttons-footer {
margin-top: auto;
padding: 20px 0;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 2;
& button {
opacity: 0;
animation: fadeIn 0.5s ease-in-out forwards;
animation-delay: 0.8s;
}
& button:nth-child(2) {
animation-delay: 1s;
}
}
body:has(:is(#welcome, #thanks):not([hidden='true'])) {
& #buttons-footer {
justify-content: center;
position: fixed;
bottom: 40px;
margin: 0 auto;
& button {
background: var(--zen-branding-paper) !important;
color: var(--zen-branding-dark) !important;
}
}
}
:is(#theme, #search) > div:nth-child(2) {
width: 100%;
}
#main-view:has(:is(#welcome, #thanks):not([hidden='true'])) {
width: 100%;
& #back {
display: none;
}
}
button {
padding: 8px;
border-radius: 999px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
}
button.primary {
background: var(--zen-branding-bg-reverse) !important;
color: var(--zen-branding-bg) !important;
transition: background 0.2s ease-in-out;
&:hover {
background: color-mix(in srgb, var(--zen-branding-bg-reverse) 80%, transparent 20%) !important;
}
}
.page {
display: flex;
flex-direction: column;
justify-content: center;
width: -moz-available;
align-items: center;
margin: 32px;
}
h2 {
font-size: 17px;
font-weight: 600;
margin: 35px 0 20px 0;
line-height: 1;
}
.page-split:not([hidden='true']) {
flex-direction: column;
margin: auto;
justify-content: start;
}
.page-split:not(#import, #theme) > div:first-child {
margin-bottom: 20px;
}
:not(#theme) .card h3 {
margin: auto;
text-align: center;
}
#theme {
& > div:first-child p {
margin-bottom: 0 !important;
}
}
#theme .card[disabled='true'] {
opacity: 0.7;
cursor: not-allowed;
}
.cardGroup {
display: flex;
flex-wrap: wrap;
align-items: stretch;
gap: 8px;
margin-bottom: 8px;
}
.cardGroup .card {
width: 140px;
display: flex;
flex-direction: column;
align-items: center;
align-content: space-between;
border: 2px solid transparent !important;
transition: all 0.1s ease-in-out !important;
margin: 0 10px;
border-radius: 7px;
outline: none !important;
cursor: auto;
user-select: none;
}
.cardGroup .card.selected {
border: 2px solid var(--zen-colors-primary) !important;
transform: scale(1.1);
}
h1 {
font-size: 32px;
font-weight: 700;
margin: 16px 0 5px 0;
line-height: 1;
}
.page > div:nth-child(2) h1 {
margin-bottom: 15px;
margin-top: 10px;
font-size: 18px;
}
p {
font-size: 16px;
opacity: 0.8;
margin: 0;
margin-bottom: 30px;
}
#zen-logo {
-moz-context-properties: fill;
fill: currentColor;
display: inline-block;
height: 42px;
width: 42px;
position: absolute;
top: 40px;
left: 50%;
transform: translateX(-50%);
}
.asset {
width: 500px;
padding-bottom: 32px;
}
@media (prefers-color-scheme: light) {
.dark-only {
display: none;
}
}
@media (prefers-color-scheme: dark) {
.light-only {
display: none;
}
}
input[type='checkbox'] {
display: inline-block;
vertical-align: middle;
}
#importNext {
margin-left: 30px;
color: var(--in-content-primary-button-background);
}
#themeNext,
#searchNext,
#thanksNext {
margin-top: 20px;
}
.page[hidden='true'] {
display: none;
}
.page:not([hidden='true']) {
display: flex;
}
.page:not([hidden='true']) > *:not(:has(.delay-animation)),
.delay-animation,
.delay-animation-2 {
opacity: 0;
animation: fadeIn 0.5s ease-in-out forwards;
}
#importBrowser {
width: 100%;
background: transparent !important;
border: 1px solid var(--zen-branding-bg-reverse);
color: var(--zen-branding-bg-reverse);
padding: 16px;
border-radius: 8px;
margin-bottom: 20px;
justify-content: start;
display: flex;
}
#layout {
text-align: center;
}
#layoutList {
display: flex;
gap: 20px;
& > [layout] {
display: flex;
flex-direction: column;
gap: 3px;
font-weight: 600;
cursor: pointer;
&[disabled='true'] {
opacity: 0.7;
cursor: not-allowed;
}
& img {
width: 250px;
border-radius: 10px;
border: 4px solid transparent;
}
&.selected img {
border: 4px solid var(--zen-colors-primary);
}
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translate3d(0, 40px, 0);
filter: blur(15px);
}
to {
opacity: 1;
filter: blur(0);
transform: translate3d(0, 0, 0);
}
}
/* There should be no more than 5 elements in a page */
.page:not([hidden='true']) > *:nth-child(2):not(:has(.delay-animation)) {
animation-delay: 0.2s;
}
.page:not([hidden='true']) > *:nth-child(3),
.delay-animation:nth-child(1) {
animation-delay: 0.4s;
}
.page:not([hidden='true']) > *:nth-child(4),
.delay-animation:nth-child(2) {
animation-delay: 0.6s;
}
.page:not([hidden='true']) > *:nth-child(5),
.delay-animation:nth-child(3),
.delay-animation-2:nth-child(1) {
animation-delay: 0.8s;
}
.page:not([hidden='true']) > *:nth-child(6),
.delay-animation:nth-child(4),
.delay-animation-2:nth-child(2) {
animation-delay: 1s;
}
#welcome {
& h1 {
animation-duration: 0.8s !important;
}
}
.card h3 {
margin-top: 10px;
}
#circular-progress {
--size: 220px;
--half-size: calc(var(--size) / 2);
--stroke-width: 20px;
--radius: calc((var(--size) - var(--stroke-width)) / 2);
--circumference: calc(var(--radius) * pi * 2);
--dash: calc((var(--progress) * var(--circumference)) / 100);
position: absolute;
top: 15px;
right: 10px;
width: 55px;
height: 40px;
}
#circular-progress circle {
cx: var(--half-size);
cy: var(--half-size);
r: var(--radius);
stroke-width: var(--stroke-width);
fill: none;
stroke-linecap: round;
}
#circular-progress circle.bg {
stroke: transparent;
}
#circular-progress circle.fg {
transform: rotate(-90deg);
transform-origin: var(--half-size) var(--half-size);
stroke-dasharray: var(--dash) calc(var(--circumference) - var(--dash));
transition: stroke-dasharray 0.3s linear 0s;
stroke: var(--zen-branding-coral);
}
#colorListWrapper {
display: flex;
align-items: center;
}
#colorListWrapper > div {
border-radius: 999px;
height: 20px;
width: 20px;
border: 3px solid transparent;
margin: 0 5px;
}
#colorListWrapper > div.selected {
border: 3px solid light-dark(#000, #fff);
}
#welcome,
#thanks {
justify-content: center;
}
#themeList {
display: flex;
flex-wrap: nowrap;
}
#themeList > svg {
border: 3px solid transparent;
border-radius: 15px;
height: -moz-available;
}
#themeList > svg.selected {
border: 3px solid var(--zen-colors-primary);
}
#searchList.cardGroup {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
#searchList .card {
width: -moz-available !important;
margin: 0;
display: flex;
flex-direction: row;
border-radius: 15px;
& h3 {
text-align: start;
margin: 0;
margin-left: 20px;
}
&.selected {
transform: scale(1.01);
}
& img {
width: 30px;
height: 30px;
border-radius: 15px;
}
}

View File

@@ -1,186 +0,0 @@
<!--
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="color-scheme" content="light dark">
<meta name="referrer" content="no-referrer" />
<linkset>
<link
rel="stylesheet"
type="text/css"
href="chrome://global/skin/in-content/common.css"
/>
<link rel="stylesheet" href="chrome://global/skin/global.css" />
<link
rel="stylesheet"
href="chrome://browser/content/zen-welcome/welcome.css"
/>
<link
rel="stylesheet"
href="chrome://browser/content/zen-styles/zen-branding.css"
/>
<link rel="localization" href="branding/brand.ftl" />
<link rel="localization" href="browser/zen-welcome.ftl" />
</linkset>
<script src="chrome://browser/content/zenThemeModifier.js"></script>
</head>
<body class="gradient-background">
<img id="zen-logo" src="chrome://branding/content/about-logo.png" />
<div id="main-view">
<div class="page" id="welcome">
<h1 class="zen-branding-title" data-l10n-id="welcome-dialog-welcome-title-part-1"></h1>
<h1 class="zen-branding-title" data-l10n-id="welcome-dialog-welcome-title-part-2"></h1>
</div>
<div class="page page-split" id="import">
<div>
<h1 data-l10n-id="welcome-dialog-import"></h1>
<p data-l10n-id="welcome-dialog-import-subtext"></p>
</div>
<button
id="importBrowser"
data-l10n-id="welcome-dialog-import-action"
></button>
</div>
<div class="page page-split" id="theme">
<div>
<h1 data-l10n-id="welcome-dialog-theme"></h1>
<p data-l10n-id="welcome-dialog-theme-subtext"></p>
</div>
<div>
<h2 data-l10n-id="welcome-dialog-theme-header-1" class="delay-animation"></h2>
<div id="colorListWrapper" class="delay-animation">
</div>
<h2 data-l10n-id="welcome-dialog-theme-header-2" class="delay-animation"></h2>
<div id="themeList" class="cardGroup">
<svg viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg" class="delay-animation-2">
<g clip-path="url(#clip0_404_2706)">
<rect width="700" height="700" fill="#F4F4F4"/>
<g filter="url(#filter0_d_404_2706)">
<path d="M107 130C107 103.49 128.49 82 155 82H700V738H107V130Z" fill="white"/>
<path d="M155 82.5H699.5V737.5H107.5V130C107.5 103.766 128.766 82.5 155 82.5Z" stroke="black" stroke-opacity="0.2"/>
</g>
<mask id="path-3-inside-1_404_2706" fill="white">
<path d="M257 83H700V701H257V83Z"/>
</mask>
<path d="M257 83H700V701H257V83Z" fill="#F1F1F1"/>
<path d="M258 701V83H256V701H258Z" fill="black" fill-opacity="0.1" mask="url(#path-3-inside-1_404_2706)"/>
</g>
<defs>
<filter id="filter0_d_404_2706" x="103" y="82" width="601" height="664" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_404_2706"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_404_2706" result="shape"/>
</filter>
<clipPath id="clip0_404_2706">
<rect width="700" height="700" fill="white"/>
</clipPath>
</defs>
</svg>
<svg viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg" class="delay-animation-2">
<g clip-path="url(#clip0_404_2709)">
<rect width="700" height="700" fill="#515151"/>
<g filter="url(#filter0_d_404_2709)">
<path d="M107 130C107 103.49 128.49 82 155 82H700V727H107V130Z" fill="#717171"/>
<path d="M155 82.5H699.5V726.5H107.5V130C107.5 103.766 128.766 82.5 155 82.5Z" stroke="white" stroke-opacity="0.57"/>
</g>
<mask id="path-3-inside-1_404_2709" fill="white">
<path d="M257 83H700V701H257V83Z"/>
</mask>
<path d="M257 83H700V701H257V83Z" fill="#565656"/>
<path d="M258 701V83H256V701H258Z" fill="black" fill-opacity="0.1" mask="url(#path-3-inside-1_404_2709)"/>
</g>
<defs>
<filter id="filter0_d_404_2709" x="103" y="82" width="601" height="653" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_404_2709"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_404_2709" result="shape"/>
</filter>
<clipPath id="clip0_404_2709">
<rect width="700" height="700" fill="white"/>
</clipPath>
</defs>
</svg>
</div>
</div>
</div>
<div class="page page-split" id="layout">
<div>
<h1 data-l10n-id="welcome-dialog-layout"></h1>
<p data-l10n-id="welcome-dialog-layout-subtext"></p>
</div>
<div id="layoutList" class="cardGroup">
<hbox layout="single" class="selected delay-animation">
<img src="chrome://browser/content/zen-images/layouts/single-toolbar.png" />
<p data-l10n-id="welcome-dialog-layout-single-toolbar"></p>
</hbox>
<hbox layout="multiple" class="delay-animation">
<img src="chrome://browser/content/zen-images/layouts/multiple-toolbar.png" />
<p data-l10n-id="welcome-dialog-layout-multiple-toolbar"></p>
</hbox>
<hbox layout="collapsed" class="delay-animation">
<img src="chrome://browser/content/zen-images/layouts/collapsed.png" />
<p data-l10n-id="welcome-dialog-layout-collapsed-toolbar"></p>
</hbox>
</div>
</div>
<div class="page page-split" id="search">
<div>
<h1 data-l10n-id="welcome-dialog-search"></h1>
<p data-l10n-id="welcome-dialog-search-subtext"></p>
</div>
<div>
<div id="searchList" class="cardGroup"></div>
</div>
</div>
<div class="page" id="thanks">
<h1 class="zen-branding-title" data-l10n-id="welcome-dialog-thanks-title-part-1"></h1>
<h1 class="zen-branding-title" data-l10n-id="welcome-dialog-thanks-title-part-2"></h1>
</div>
<div id="buttons-footer">
<button
id="back"
data-l10n-id="welcome-dialog-back-action"
disabled
></button>
<button
id="next"
class="primary"
data-l10n-id="welcome-dialog-next-action"
></button>
</div>
</div>
<svg
width="250" height="250" viewBox="0 0 250 250"
id="circular-progress" style="--progress: 50"
>
<circle class="bg"></circle>
<circle class="fg"></circle>
</svg>
<script src="./welcome.js"></script>
<script src="chrome://browser/content/contentTheme.js"></script>
</body>
</html>

View File

@@ -1,354 +0,0 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
const { XPCOMUtils } = ChromeUtils.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyModuleGetters(this, {
AddonManager: 'resource://gre/modules/AddonManager.jsm',
MigrationUtils: 'resource:///modules/MigrationUtils.jsm',
});
ChromeUtils.defineModuleGetter(this, 'ExtensionSettingsStore', 'resource://gre/modules/ExtensionSettingsStore.jsm');
Services.scriptloader.loadSubScript('chrome://browser/content/ZenUIManager.mjs');
const kWelcomeSeenPref = 'zen.welcome-screen.seen';
// =============================================================================
// Util stuff copied from browser/components/preferences/search.js
class EngineStore {
constructor() {
this._engines = [];
}
async init() {
const visibleEngines = await Services.search.getVisibleEngines();
this.initSpecificEngine(visibleEngines);
}
getEngine() {
return this._engines;
}
initSpecificEngine(engines) {
for (const engine of engines) {
try {
this._engines.push(this._cloneEngine(engine));
} catch (e) {
// Ignore engines that throw an exception when cloning.
console.error(e);
}
}
}
getEngineByName(name) {
return this._engines.find((engine) => engine.name == name);
}
_cloneEngine(aEngine) {
const clonedObj = {};
for (const i of ['name', 'alias', '_iconURI', 'hidden']) {
clonedObj[i] = aEngine[i];
}
clonedObj.originalEngine = aEngine;
return clonedObj;
}
async getDefaultEngine() {
let engineName = await Services.search.getDefault();
return this.getEngineByName(engineName._name);
}
async setDefaultEngine(engine) {
await Services.search.setDefault(engine.originalEngine, Ci.nsISearchService.CHANGE_REASON_USER);
}
}
// =============================================================================
const sleep = (duration) => new Promise((resolve) => setTimeout(resolve, duration));
class Page {
/**
* A basic controller for individual pages
* @param {string} id The id of the element that represents this page.
*/
constructor(id) {
this.element = document.getElementById(id);
}
/**
*
* @param {Pages} pages The pages wrapper
*/
setPages(pages) {
this.pages = pages;
}
hide() {
this.element.setAttribute('hidden', 'true');
}
show() {
this.element.removeAttribute('hidden');
}
}
class Themes extends Page {
constructor(id) {
super(id);
this.loadThemes();
}
async loadThemes() {
window.addEventListener('DOMContentLoaded', this.setColorBar);
await sleep(1000);
const themes = (await AddonManager.getAddonsByTypes(['theme'])).filter((theme) => theme.id !== 'default-theme@mozilla.org');
const themeList = document.getElementById('themeList');
const themeElements = ['firefox-compact-light@mozilla.org', 'firefox-compact-dark@mozilla.org'];
themeElements.forEach((theme, i) => {
let container = themeList.children[i];
container.addEventListener(
'click',
(() => {
if (container.hasAttribute('disabled')) {
return;
}
for (const el of themeList.children) {
el.classList.remove('selected');
}
container.classList.add('selected');
themes.find((t) => t.id === theme).enable();
}).bind(this, i, container, theme)
);
if (themes.find((t) => t.id === theme).isActive) {
container.classList.add('selected');
}
});
}
setColorBar() {
const colorList = document.getElementById('colorListWrapper');
const colors = ['#aac7ff', '#74d7cb', '#a0d490', '#dec663', '#ffb787', '#ffb1c0', '#ddbfc3', '#f6b0ea', '#d4bbff'];
colors.forEach((color) => {
const container = document.createElement('div');
container.classList.add('color');
container.style.backgroundColor = color;
container.setAttribute('data-color', color);
container.addEventListener(
'click',
(() => {
Services.prefs.setStringPref('zen.theme.accent-color', color);
colorList.querySelectorAll('.selected').forEach((el) => el.classList.remove('selected'));
container.classList.add('selected');
}).bind(this, color, container)
);
colorList.appendChild(container);
});
}
}
class Layout extends Page {
constructor(id) {
super(id);
this.loadLayouts();
}
loadLayouts() {
const kExtendedSidebar = 'zen.view.sidebar-expanded';
const kSingleToolbar = 'zen.view.use-single-toolbar';
for (const layout of document.getElementById('layoutList').children) {
layout.addEventListener('click', () => {
if (layout.hasAttribute('disabled')) {
return;
}
for (const el of document.getElementById('layoutList').children) {
el.classList.remove('selected');
}
layout.classList.add('selected');
Services.prefs.setBoolPref(kExtendedSidebar, layout.getAttribute('layout') != 'collapsed');
Services.prefs.setBoolPref(kSingleToolbar, layout.getAttribute('layout') == 'single');
});
}
}
}
class Thanks extends Page {
constructor(id) {
super(id);
// Thanks :)
}
}
class Search extends Page {
constructor(id) {
super(id);
this.store = new EngineStore();
this.searchList = [];
this.loadSearch();
}
async loadSearch() {
await sleep(1100);
await this.store.init();
const defaultEngine = await Services.search.getDefault();
const searchElements = document.getElementById('searchList');
this.store.getEngine().forEach(async (search) => {
const container = await this.loadSpecificSearch(search, defaultEngine);
searchElements.appendChild(container);
this.searchList.push(container);
});
}
/**
* @returns {HTMLDivElement}
*/
async loadSpecificSearch(search, defaultSearch) {
const container = document.createElement('div');
container.classList.add('card');
container.classList.add('card-no-hover');
if (search.name == defaultSearch._name) {
container.classList.add('selected');
}
container.addEventListener('click', () => {
this.searchList.forEach((el) => el.classList.remove('selected'));
container.classList.add('selected');
this.store.setDefaultEngine(search);
});
const img = document.createElement('img');
img.src = await search.originalEngine.getIconURL();
const name = document.createElement('h3');
name.textContent = search.name;
container.appendChild(img);
container.appendChild(name);
return container;
}
}
class Import extends Page {
constructor(id) {
super(id);
const importButton = document.getElementById('importBrowser');
importButton.addEventListener('click', async () => {
MigrationUtils.showMigrationWizard(window, {
zenBlocking: true,
});
});
}
}
class Pages {
/**
* A wrapper around all pages
* @param {Page[]} pages The pages
*/
constructor(pages) {
this.pages = pages;
this.currentPage = 0;
this.pages.forEach((page) => page.setPages(this));
this._displayCurrentPage();
this.nextEl = document.getElementById(`next`);
this.prevEl = document.getElementById(`back`);
this.nextEl.addEventListener('click', () => {
this.next();
this.prevEl.removeAttribute('disabled');
});
this.prevEl.addEventListener('click', () => {
this.currentPage--;
this._displayCurrentPage();
if (this.pages.currentPage === 1) {
this.prevEl.setAttribute('disabled', 'true');
}
for (const button of document.getElementById('buttons-footer').children) {
button.style.display = 'none';
// Re-animate the buttons
setTimeout(() => {
button.style.removeProperty('display');
});
}
});
}
next() {
this.currentPage++;
document.getElementById('main-view').setAttribute('data-page', this.currentPage);
if (this.currentPage >= this.pages.length) {
// We can use internal js apis to close the window. We also want to set
// the settings api for welcome seen to false to stop it showing again
Services.prefs.setBoolPref(kWelcomeSeenPref, true);
close();
return;
}
for (const button of document.getElementById('buttons-footer').children) {
button.style.display = 'none';
// Re-animate the buttons
setTimeout(() => {
button.style.removeProperty('display');
});
}
this._displayCurrentPage();
}
_displayCurrentPage() {
let progress = document.getElementById('circular-progress');
progress.style.setProperty('--progress', ((this.currentPage + 1) / this.pages.length) * 100);
for (const page of this.pages) {
page.hide();
}
if (this.currentPage >= 1) {
document.body.classList.remove('gradient-background');
} else {
document.body.classList.add('gradient-background');
}
this.pages[this.currentPage].show();
}
}
const pages = new Pages([
new Page('welcome'),
new Themes('theme'),
new Layout('layout'),
new Import('import'),
new Search('search'),
new Thanks('thanks'),
]);