diff --git a/locales/en-US/browser/browser/zen-welcome.ftl b/locales/en-US/browser/browser/zen-welcome.ftl
index 1967595a9..c351f271a 100644
--- a/locales/en-US/browser/browser/zen-welcome.ftl
+++ b/locales/en-US/browser/browser/zen-welcome.ftl
@@ -5,29 +5,28 @@
zen-welcome-title-line1 = Welcome to
zen-welcome-title-line2 = a calmer internet
-zen-welcome-import-title = A Fresh Start, Same Bookmarks
-zen-welcome-import-description-1 = Your bookmarks, history, and passwords are like a trail of breadcrumbs through the internet—don’t leave them behind!
-zen-welcome-import-description-2 = Easily bring them over from another browser and pick up right where you left off.
-zen-welcome-import-button = Import now
+zen-welcome-back = Back
+zen-welcome-skip = Skip for now
-zen-welcome-set-default-browser = Set { -brand-short-name } as your default browser
-zen-welcome-dont-set-default-browser = DON’T set { -brand-short-name } as your default browser
+zen-welcome-import-title = Bring your stuff along.
+zen-welcome-import-description = Import your bookmarks, history and passwords from another browser and pick up right where you left off.
+zen-welcome-import-yes = YES , import from another browser.
+zen-welcome-import-no = NO , start fresh.
-zen-welcome-initial-essentials-title = Your Key Tabs, Always Within Reach
-zen-welcome-initial-essentials-description-1 = Keep your most important tabs easily accessible and always at hand, no matter how many you open.
-zen-welcome-initial-essentials-description-2 = Essential tabs are always visible, no matter what workspace you are in.
+zen-welcome-default-browser-title = Make { -brand-short-name } your default?
+zen-welcome-default-browser-description = Links from other apps will open in { -brand-short-name }. You can always change this later.
+zen-welcome-set-default-browser = YES , make { -brand-short-name } default.
+zen-welcome-dont-set-default-browser = NO , still exploring my options.
-zen-welcome-workspace-colors-title = Your Workspaces, Your Colors
-zen-welcome-workspace-colors-description = Personalize your browser by giving each workspace its own unique color identity.
-
-zen-welcome-start-browsing-title = All set?
- Let’s get rolling!
-zen-welcome-start-browsing-description-1 = You’re all set up and ready to go. Click the button below to start browsing with { -brand-short-name }.
-zen-welcome-start-browsing = Dive in!
-
-zen-welcome-default-search-title = Your Default Search Engine
+zen-welcome-default-search-title = Pick your search engine.
zen-welcome-default-search-description = Choose your default search engine. You can always change it later!
-zen-welcome-skip-button = Skip
+zen-welcome-essentials-title = Choose the apps you use most.
+zen-welcome-essentials-description = Select your favorite apps to keep them handy in your sidebar.
-zen-welcome-finished = Your Zen has been set up correctly!
+zen-welcome-workspace-colors-title = Your space, your colors.
+zen-welcome-workspace-colors-description = Personalize your browser by giving each space its own unique color identity.
+
+zen-welcome-start-browsing-title = All set? Let’s get rolling!
+zen-welcome-start-browsing-description-1 = You’re all set up and ready to go. Click the button below to start browsing with { -brand-short-name }.
+zen-welcome-start-browsing = Dive in!
diff --git a/src/browser/base/content/zen-panels/theme-picker.inc b/src/browser/base/content/zen-panels/theme-picker.inc
index 524126c55..7f7f0b495 100644
--- a/src/browser/base/content/zen-panels/theme-picker.inc
+++ b/src/browser/base/content/zen-panels/theme-picker.inc
@@ -14,14 +14,14 @@
-
-
-
+
+
+
-
-
-
+
+
+
@@ -29,82 +29,82 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
# Start from black to white in a span on 8 steps and.
# They must all go from the middle to the right side. They must always stay verically centered.
# And reach to 180 on the right side, meaning we must divide the width in 16 segments.
-
-
-
-
-
-
-
-
-
diff --git a/src/browser/components/preferences/preferences-js.patch b/src/browser/components/preferences/preferences-js.patch
index 3e3ad8c86..d7367f2ae 100644
--- a/src/browser/components/preferences/preferences-js.patch
+++ b/src/browser/components/preferences/preferences-js.patch
@@ -1,5 +1,5 @@
diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js
-index a76699af75252bec31e17b562463c37236cbbfe5..6355a6ee92754846ffcdd123ac49545d520470c7 100644
+index 94612d8e28af58fcb04914440348b899a0a1e08f..da3b1ffd09745d09589283356d1001dbfb76a468 100644
--- a/browser/components/preferences/preferences.js
+++ b/browser/components/preferences/preferences.js
@@ -132,6 +132,7 @@ ChromeUtils.defineLazyGetter(this, "gSubDialog", function () {
@@ -10,7 +10,7 @@ index a76699af75252bec31e17b562463c37236cbbfe5..6355a6ee92754846ffcdd123ac49545d
],
resizeCallback: async ({ title, frame }) => {
// Search within main document and highlight matched keyword.
-@@ -462,6 +463,8 @@ const CONFIG_PANES = Object.freeze({
+@@ -471,6 +472,8 @@ const CONFIG_PANES = Object.freeze({
tabsBrowsing: {
l10nId: "tabs-browsing-section",
groupIds: [
@@ -19,7 +19,7 @@ index a76699af75252bec31e17b562463c37236cbbfe5..6355a6ee92754846ffcdd123ac49545d
"browserLayout",
"tabs",
"pageNavigation",
-@@ -509,7 +512,7 @@ function register_module(categoryName, categoryObject) {
+@@ -518,7 +521,7 @@ function register_module(categoryName, categoryObject) {
}
this._initted = true;
let template = document.getElementById("template-" + categoryName);
@@ -28,7 +28,7 @@ index a76699af75252bec31e17b562463c37236cbbfe5..6355a6ee92754846ffcdd123ac49545d
// Replace the template element with the nodes inside of it.
template.replaceWith(template.content);
-@@ -553,6 +556,10 @@ function init_all() {
+@@ -562,6 +565,10 @@ function init_all() {
register_module("paneHome", gHomePane);
register_module("paneSearch", gSearchPane);
register_module("panePrivacy", gPrivacyPane);
diff --git a/src/browser/installer/windows/nsis/uninstaller-nsi.patch b/src/browser/installer/windows/nsis/uninstaller-nsi.patch
index ad4d6025a..6c8873660 100644
--- a/src/browser/installer/windows/nsis/uninstaller-nsi.patch
+++ b/src/browser/installer/windows/nsis/uninstaller-nsi.patch
@@ -1,9 +1,18 @@
diff --git a/browser/installer/windows/nsis/uninstaller.nsi b/browser/installer/windows/nsis/uninstaller.nsi
old mode 100755
new mode 100644
-index becbb0f70c84eb7f2d9bff5a3aedcbb7a78a61cf..05835d2088ab631316a25a9f0728898bd7998d8d
+index becbb0f70c84eb7f2d9bff5a3aedcbb7a78a61cf..a23a8a1b160932368377fc841b501df9355918db
--- a/browser/installer/windows/nsis/uninstaller.nsi
+++ b/browser/installer/windows/nsis/uninstaller.nsi
+@@ -150,7 +150,7 @@ OutFile "helper.exe"
+ !endif
+ ShowUnInstDetails nevershow
+
+-!define URLUninstallSurvey "https://qsurvey.mozilla.com/s3/FF-Desktop-Post-Uninstall?channel=${UpdateChannel}&version=${AppVersion}&osversion="
++!define URLUninstallSurvey "https://links.zen-browser.app/uninstall-feedback?osversion="
+
+ ; Support for the profile refresh feature
+ !define URLProfileRefreshHelp "https://support.mozilla.org/kb/refresh-firefox-reset-add-ons-and-settings"
@@ -516,6 +516,7 @@ Section "Uninstall"
${un.RegCleanFileHandler} ".svg" "FirefoxHTML-$AppUserModelID"
${un.RegCleanFileHandler} ".webp" "FirefoxHTML-$AppUserModelID"
diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css
index a42d0021c..e76187864 100644
--- a/src/browser/themes/shared/zen-icons/icons.css
+++ b/src/browser/themes/shared/zen-icons/icons.css
@@ -812,7 +812,8 @@
#toolbar-context-toggle-vertical-tabs,
#toolbar-context-customize-sidebar,
-#sidebarRevampSeparator {
+#sidebarRevampSeparator,
+.fxa-menu-sign-in-promo-image {
display: none !important;
}
diff --git a/src/zen/common/modules/ZenUIManager.mjs b/src/zen/common/modules/ZenUIManager.mjs
index 977a7d8df..d205f6951 100644
--- a/src/zen/common/modules/ZenUIManager.mjs
+++ b/src/zen/common/modules/ZenUIManager.mjs
@@ -543,6 +543,10 @@ window.gZenUIManager = {
return false;
}
+ if (document.documentElement.hasAttribute("zen-welcome-stage")) {
+ return false;
+ }
+
const shouldOpenURLBar =
overridePreferance ||
(gZenVerticalTabsManager._canReplaceNewTab &&
@@ -1282,7 +1286,7 @@ window.gZenVerticalTabsManager = {
if (!this._hasSetSingleToolbar) {
height = AppConstants.platform == "macosx" ? 34 : 32;
} else if (!gURLBar.hasAttribute("breakout-extend")) {
- height = 36;
+ height = 38;
}
if (typeof height !== "undefined") {
gURLBar.style.setProperty("--urlbar-height", `${height}px`);
diff --git a/src/zen/common/styles/zen-omnibox.css b/src/zen/common/styles/zen-omnibox.css
index 98659ef8d..7ad2a72b2 100644
--- a/src/zen/common/styles/zen-omnibox.css
+++ b/src/zen/common/styles/zen-omnibox.css
@@ -136,6 +136,11 @@
& #identity-box {
margin-inline-end: 0 !important;
+
+ &[pageproxystate="invalid"] {
+ margin-inline-end: -4px !important;
+ }
+
&.chromeUI:not([pageproxystate="invalid"]) #identity-icon-box {
border-radius: 10px !important;
}
diff --git a/src/zen/common/styles/zen-theme.css b/src/zen/common/styles/zen-theme.css
index 51a4447f7..fd5a01175 100644
--- a/src/zen/common/styles/zen-theme.css
+++ b/src/zen/common/styles/zen-theme.css
@@ -286,7 +286,7 @@
* 1. If the native radius - the separation is less than 4px, use 4px.
* 2. Otherwise, use the the calculated value (inner radius = outer radius - separation).
*/
- max(5px, calc(var(--zen-border-radius) - var(--zen-element-separation) / 2))
+ max(5px, calc((var(--zen-border-radius) - var(--zen-element-separation) / 2) * var(--zen-squircle-value)))
);
/** Other theme-related styles */
diff --git a/src/zen/common/zenThemeModifier.js b/src/zen/common/zenThemeModifier.js
index 314e5fc6e..84fa26405 100644
--- a/src/zen/common/zenThemeModifier.js
+++ b/src/zen/common/zenThemeModifier.js
@@ -109,7 +109,7 @@
if (AppConstants.platform == "macosx") {
const targetRadius = window.matchMedia("(-moz-mac-tahoe-theme)")
.matches
- ? 14
+ ? 12
: 10;
document.documentElement.style.setProperty(
"--zen-border-radius",
diff --git a/src/zen/images/favicons/tuta.svg b/src/zen/images/favicons/tuta.svg
index 8dc5ca458..c7bc7e62a 100644
--- a/src/zen/images/favicons/tuta.svg
+++ b/src/zen/images/favicons/tuta.svg
@@ -3,8 +3,7 @@
- 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/.
-->
-
-
+
diff --git a/src/zen/live-folders/providers/GithubLiveFolder.sys.mjs b/src/zen/live-folders/providers/GithubLiveFolder.sys.mjs
index 5b88bce61..f565e7b8f 100644
--- a/src/zen/live-folders/providers/GithubLiveFolder.sys.mjs
+++ b/src/zen/live-folders/providers/GithubLiveFolder.sys.mjs
@@ -26,7 +26,8 @@ export class nsGithubLiveFolderProvider extends nsZenLiveFolderProvider {
const hasAnyFilterEnabled =
(this.state.options.authorMe ?? false) ||
(this.state.options.assignedMe ?? true) ||
- (this.state.options.reviewRequested ?? false);
+ (this.state.options.reviewRequested ?? false) ||
+ (this.state.options.includeDrafts ?? true);
if (!hasAnyFilterEnabled) {
return "zen-live-folder-github-no-filter";
diff --git a/src/zen/spaces/ZenGradientGenerator.mjs b/src/zen/spaces/ZenGradientGenerator.mjs
index 28a47f31a..a4ffd4f8a 100644
--- a/src/zen/spaces/ZenGradientGenerator.mjs
+++ b/src/zen/spaces/ZenGradientGenerator.mjs
@@ -353,7 +353,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
// Add elements in a circular pattern, where the center is the center of the wrapper
for (let i = 0; i < 16; i++) {
const dot = document.createElement("div");
- dot.classList.add("zen-theme-picker-texture-dot");
+ dot.classList.add("zen-theme-picker-texture-dot", "no-squircles");
const position = (i / 16) * Math.PI * 2 + wrapperWidth;
dot.style.left = `${Math.cos(position) * 50 + 50}%`;
dot.style.top = `${Math.sin(position) * 50 + 50}%`;
@@ -361,6 +361,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
}
this._textureHandler = document.createElement("div");
this._textureHandler.id = "PanelUI-zen-gradient-generator-texture-handler";
+ this._textureHandler.classList.add("no-squircles");
this._textureHandler.addEventListener(
"mousedown",
this.onTextureHandlerMouseDown.bind(this)
@@ -598,7 +599,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
color.position || this.calculateInitialPosition([r, g, b]);
const dotPad = this.panel.querySelector(".zen-theme-picker-gradient");
- dot.classList.add("zen-theme-picker-dot");
+ dot.classList.add("zen-theme-picker-dot", "no-squircles");
dot.style.left = `${x}px`;
dot.style.top = `${y}px`;
diff --git a/src/zen/spaces/ZenSpaceCreation.mjs b/src/zen/spaces/ZenSpaceCreation.mjs
index 09cf6d539..12f2eb34e 100644
--- a/src/zen/spaces/ZenSpaceCreation.mjs
+++ b/src/zen/spaces/ZenSpaceCreation.mjs
@@ -10,6 +10,7 @@ ChromeUtils.defineLazyGetter(lazy, "l10n", () => {
class nsZenWorkspaceCreation extends MozXULElement {
#wasInCollapsedMode = false;
+ #urlbarDimmed = false;
promiseInitialized = new Promise(resolve => {
this.resolveInitialized = resolve;
@@ -77,16 +78,56 @@ class nsZenWorkspaceCreation extends MozXULElement {
return this.getAttribute("previous-workspace-id");
}
- get elementsToAnimate() {
- return [
- this.querySelector(".zen-workspace-creation-title"),
- this.querySelector(".zen-workspace-creation-label").parentElement,
- this.querySelector(".zen-workspace-creation-name-wrapper"),
- this.querySelector(".zen-workspace-creation-profile-wrapper"),
- this.querySelector(".zen-workspace-creation-edit-theme-button"),
- this.createButton.parentNode,
- this.cancelButton,
- ];
+ get #spaceSwitchDuration() {
+ return (
+ Services.prefs.getIntPref("zen.workspaces.switch-animation-duration") /
+ 1000
+ );
+ }
+
+ #dimUrlbar() {
+ if (this.#urlbarDimmed || !gZenVerticalTabsManager._hasSetSingleToolbar) {
+ return;
+ }
+ this.#urlbarDimmed = true;
+ const animation = gZenUIManager.motion.animate(
+ gURLBar,
+ {
+ opacity: [1, 0],
+ },
+ {
+ duration: this.#spaceSwitchDuration,
+ type: "spring",
+ bounce: 0,
+ }
+ );
+ if (gReduceMotion) {
+ animation.complete();
+ }
+ }
+
+ #restoreUrlbar() {
+ if (!this.#urlbarDimmed) {
+ return;
+ }
+ this.#urlbarDimmed = false;
+ const animation = gZenUIManager.motion.animate(
+ gURLBar,
+ {
+ opacity: [0, 1],
+ },
+ {
+ duration: this.#spaceSwitchDuration,
+ type: "spring",
+ bounce: 0,
+ }
+ );
+ if (gReduceMotion) {
+ animation.complete();
+ }
+ animation.then(() => {
+ gURLBar.style.removeProperty("opacity");
+ });
}
connectedCallback() {
@@ -109,10 +150,6 @@ class nsZenWorkspaceCreation extends MozXULElement {
".zen-workspace-creation-cancel-button"
);
- for (const element of this.elementsToAnimate) {
- element.style.opacity = 0;
- }
-
this.#wasInCollapsedMode =
document.documentElement.getAttribute("zen-sidebar-expanded") !== "true";
@@ -128,7 +165,7 @@ class nsZenWorkspaceCreation extends MozXULElement {
this.handleZenWorkspacesChange.bind(this);
for (const element of this.parentElement.children) {
- if (element !== this) {
+ if (element !== this && !element.hidden) {
element.hidden = true;
this.#hiddenElements.push(element);
}
@@ -207,48 +244,15 @@ class nsZenWorkspaceCreation extends MozXULElement {
document.getElementById("zen-sidebar-splitter").style.pointerEvents =
"none";
- gZenUIManager.motion
- .animate(
- [gBrowser.tabContainer, gURLBar],
- {
- opacity: [1, 0],
- },
- {
- duration: 0.3,
- type: "spring",
- bounce: 0,
- }
- )
- .then(() => {
- gBrowser.tabContainer.style.visibility = "collapse";
- if (gZenVerticalTabsManager._hasSetSingleToolbar) {
- document.getElementById("nav-bar").style.visibility = "collapse";
- }
- this.style.visibility = "visible";
- gZenCompactModeManager.getAndApplySidebarWidth({});
- this.resolveInitialized();
- let animation = gZenUIManager.motion.animate(
- this.elementsToAnimate,
- {
- y: [20, 0],
- opacity: [0, 1],
- filter: ["blur(2px)", "blur(0)"],
- },
- {
- duration: 0.6,
- type: "spring",
- bounce: 0,
- delay: gZenUIManager.motion.stagger(0.05, { startDelay: 0.2 }),
- }
- );
- if (gReduceMotion) {
- animation.complete();
- }
- animation.then(() => {
- this.inputName.focus();
- gZenWorkspaces.workspaceElement(this.workspaceId).hidden = false;
- });
- });
+ gZenCompactModeManager.getAndApplySidebarWidth({});
+ this.#dimUrlbar();
+ this.resolveInitialized();
+ }
+
+ disconnectedCallback() {
+ if (gZenWorkspaces.creatingWorkspaceId === this.workspaceId) {
+ gZenWorkspaces.creatingWorkspaceId = null;
+ }
}
async onCreateButtonCommand() {
@@ -258,7 +262,7 @@ class nsZenWorkspaceCreation extends MozXULElement {
workspace.containerTabId = this.currentProfile;
await gZenWorkspaces.saveWorkspace(workspace);
- await this.#cleanup();
+ this.#cleanup();
gZenWorkspaces._organizeWorkspaceStripLocations(workspace, true);
gZenWorkspaces.updateTabsContainers();
@@ -268,6 +272,7 @@ class nsZenWorkspaceCreation extends MozXULElement {
async onCancelButtonCommand() {
document.documentElement.removeAttribute("zen-creating-workspace");
+ this.#restoreUrlbar();
await gZenWorkspaces.changeWorkspaceWithID(this.previousWorkspaceId);
}
@@ -332,34 +337,18 @@ class nsZenWorkspaceCreation extends MozXULElement {
}
finishSetup() {
+ this.inputName.focus();
gZenWorkspaces.addChangeListeners(this.handleZenWorkspacesChangeBind, {
once: true,
});
}
async handleZenWorkspacesChange() {
+ this.#cleanup();
await gZenWorkspaces.removeWorkspace(this.workspaceId);
- await this.#cleanup();
}
- async #cleanup() {
- if (!gReduceMotion) {
- await gZenUIManager.motion.animate(
- this.elementsToAnimate.reverse(),
- {
- y: [0, 20],
- opacity: [1, 0],
- filter: ["blur(0)", "blur(2px)"],
- },
- {
- duration: 0.4,
- type: "spring",
- bounce: 0,
- delay: gZenUIManager.motion.stagger(0.05),
- }
- );
- }
-
+ #cleanup() {
document.getElementById("zen-sidebar-splitter").style.pointerEvents = "";
gZenWorkspaces.removeChangeListeners(this.handleZenWorkspacesChangeBind);
@@ -377,41 +366,23 @@ class nsZenWorkspaceCreation extends MozXULElement {
document.documentElement.removeAttribute("zen-creating-workspace");
- gBrowser.tabContainer.style.visibility = "";
- gBrowser.tabContainer.style.opacity = 0;
- if (gZenVerticalTabsManager._hasSetSingleToolbar) {
- document.getElementById("nav-bar").style.visibility = "";
- gURLBar.style.opacity = 0;
- }
-
this.remove();
+ this.#restoreUrlbar();
- const workspace = gZenWorkspaces.getActiveWorkspace();
- gZenWorkspaces._organizeWorkspaceStripLocations(workspace);
- gZenWorkspaces.updateTabsContainers();
-
- await gZenUIManager.motion.animate(
- [gBrowser.tabContainer, gURLBar],
- {
- opacity: [0, 1],
- },
- {
- duration: 0.3,
- type: "spring",
- bounce: 0,
- }
- );
-
- gBrowser.tabContainer.style.opacity = "";
- if (gZenVerticalTabsManager._hasSetSingleToolbar) {
- gURLBar.style.opacity = "";
- }
-
- for (const element of this.#hiddenElements) {
+ // Give the space back its own contents.
+ const revealedElements = this.#hiddenElements;
+ this.#hiddenElements = [];
+ for (const element of revealedElements) {
element.hidden = false;
}
- this.#hiddenElements = [];
+ // Now that the form is gone the space owns its essentials again.
+ const workspace = gZenWorkspaces.getActiveWorkspace();
+ gZenWorkspaces._organizeWorkspaceStripLocations(workspace);
+ gZenWorkspaces.updateTabsContainers();
+ // The essentials slid away when we arrived, slide them back in.
+ gZenWorkspaces.restoreEssentialsPosition();
+
gZenUIManager.updateTabsToolbar();
}
}
diff --git a/src/zen/spaces/ZenSpaceManager.mjs b/src/zen/spaces/ZenSpaceManager.mjs
index 8439a1a67..c0defb1fa 100644
--- a/src/zen/spaces/ZenSpaceManager.mjs
+++ b/src/zen/spaces/ZenSpaceManager.mjs
@@ -40,6 +40,13 @@ class nsZenWorkspaces {
#inChangingWorkspace = false;
draggedElement = null;
+ /**
+ * The space currently showing the creation form, if any. It shows the form
+ * instead of tabs and essentials, so it never owns an essentials section.
+ * Cleared by the form element once it is removed from the DOM.
+ */
+ creatingWorkspaceId = null;
+
#hasInitialized = false;
#canDebug = Services.prefs.getBoolPref("zen.workspaces.debug", false);
@@ -345,10 +352,7 @@ class nsZenWorkspaces {
}
get shouldAnimateEssentials() {
- return (
- this.containerSpecificEssentials ||
- document.documentElement.hasAttribute("zen-creating-workspace")
- );
+ return this.containerSpecificEssentials || !!this.creatingWorkspaceId;
}
get activeWorkspaceElement() {
@@ -458,9 +462,6 @@ class nsZenWorkspaces {
workspaceWrapper.active = true;
}
- if (document.documentElement.hasAttribute("zen-creating-workspace")) {
- workspaceWrapper.hidden = true; // Hide workspace while creating it
- }
container.appendChild(workspaceWrapper);
this.#organizeTabsToWorkspaceSections(workspace, workspaceWrapper, tabs);
workspaceWrapper.checkPinsExistence();
@@ -911,6 +912,9 @@ class nsZenWorkspaces {
// Wait for the next event loop to ensure that the startup focus logic by
// firefox has finished doing it's thing.
setTimeout(() => {
+ if (document.documentElement.hasAttribute("zen-welcome-stage")) {
+ return;
+ }
if (gZenVerticalTabsManager._canReplaceNewTab && shownEmptyTab) {
BrowserCommands.openTab();
} else if (shownEmptyTab || initialTabWasEmpty) {
@@ -1236,7 +1240,7 @@ class nsZenWorkspaces {
}
}
- saveWorkspace(workspaceData) {
+ saveWorkspace(workspaceData, { insertAfterId = null } = {}) {
if (this.privateWindowOrDisabled) {
return;
}
@@ -1244,8 +1248,13 @@ class nsZenWorkspaces {
const index = workspacesData.findIndex(
ws => ws.uuid === workspaceData.uuid
);
+ const insertAfterIndex = insertAfterId
+ ? workspacesData.findIndex(ws => ws.uuid === insertAfterId)
+ : -1;
if (index !== -1) {
workspacesData[index] = workspaceData;
+ } else if (insertAfterIndex !== -1) {
+ workspacesData.splice(insertAfterIndex + 1, 0, workspaceData);
} else {
workspacesData.push(workspaceData);
}
@@ -1426,6 +1435,7 @@ class nsZenWorkspaces {
const previousWorkspace = this.getActiveWorkspace();
document.documentElement.setAttribute("zen-creating-workspace", "true");
await this.createAndSaveWorkspace("Space", undefined, false, 0, {
+ insertAfterId: previousWorkspace?.uuid,
beforeChangeCallback: async workspace => {
createForm = document.createXULElement("zen-workspace-creation");
createForm.setAttribute("workspace-id", workspace.uuid);
@@ -1433,7 +1443,18 @@ class nsZenWorkspaces {
"previous-workspace-id",
previousWorkspace?.uuid || ""
);
- gBrowser.tabContainer.after(createForm);
+ this.creatingWorkspaceId = workspace.uuid;
+ const spaceElement = this.workspaceElement(workspace.uuid);
+ // No essentials sit above the form, so there's no room to reserve for
+ // them. The switch animation needs a value to
+ // animate the padding from.
+ spaceElement.style.paddingTop = "0px";
+ spaceElement.appendChild(createForm);
+ // Keep the strip sitting on the previous space so that the new one,
+ // together with its creation form, slides in like any other space.
+ if (previousWorkspace) {
+ this._organizeWorkspaceStripLocations(previousWorkspace, true);
+ }
await createForm.promiseInitialized;
},
});
@@ -1803,10 +1824,6 @@ class nsZenWorkspaces {
justMove = false,
offsetPixels = 0
) {
- if (document.documentElement.hasAttribute("zen-creating-workspace")) {
- // If we are creating a workspace, we don't want to animate the strip
- return;
- }
this._organizingWorkspaceStrip = true;
const workspaces = this.getWorkspaces();
let workspaceIndex = workspaces.findIndex(w => w.uuid === workspace.uuid);
@@ -1827,6 +1844,7 @@ class nsZenWorkspaces {
}
const workspaceContextId = workspace.containerTabId;
const nextWorkspaceContextId = workspaces[nextSpaceIdx]?.containerTabId;
+ const isCreatingWorkspace = workspace.uuid === this.creatingWorkspaceId;
for (const otherWorkspace of workspaces) {
const element = this.workspaceElement(otherWorkspace.uuid);
let diff = workspaces.indexOf(otherWorkspace) - workspaceIndex;
@@ -1843,8 +1861,9 @@ class nsZenWorkspaces {
// Get the next workspace contextId, if it's the same, dont apply offsetPixels
// if it's not we do apply it
if (
- container.getAttribute("container") != workspace.containerTabId &&
- this.shouldAnimateEssentials
+ isCreatingWorkspace ||
+ (container.getAttribute("container") != workspace.containerTabId &&
+ this.shouldAnimateEssentials)
) {
container.setAttribute("hidden", "true");
} else {
@@ -1919,6 +1938,40 @@ class nsZenWorkspaces {
delete this._organizingWorkspaceStrip;
}
+ /**
+ * Slide the essentials back from wherever a creation form parked them.
+ */
+ restoreEssentialsPosition() {
+ const duration =
+ Services.prefs.getIntPref("zen.workspaces.switch-animation-duration") /
+ 1000;
+ for (const container of document.querySelectorAll(
+ "#zen-essentials .zen-essentials-container"
+ )) {
+ const existingTransform = container.style.transform;
+ const parkedOffset = parseFloat(existingTransform.split("(")[1]) || 0;
+ if (!parkedOffset || gReduceMotion) {
+ container.style.removeProperty("transform");
+ continue;
+ }
+ gZenUIManager.motion
+ .animate(
+ container,
+ {
+ transform: [existingTransform, "translateX(0%)"],
+ },
+ {
+ type: "spring",
+ bounce: 0,
+ duration,
+ }
+ )
+ .then(() => {
+ container.style.removeProperty("transform");
+ });
+ }
+ }
+
updateWorkspaceIndicator(currentWorkspace, workspaceIndicator) {
if (!workspaceIndicator) {
return;
@@ -1992,17 +2045,21 @@ class nsZenWorkspaces {
const isGoingLeft = diff < 0;
const essentialsAnimData = [];
if (shouldAnimate && this.shouldAnimateEssentials && previousWorkspace) {
- const containerIds = new Map();
+ const creatingWorkspaceId = this.creatingWorkspaceId;
+ const essentialsElements = new Map();
for (const workspace of workspaces) {
- const containerId = workspace.containerTabId;
- if (!containerIds.has(containerId)) {
- containerIds.set(containerId, []);
+ if (workspace.uuid === creatingWorkspaceId) {
+ continue;
}
- containerIds.get(containerId).push(workspace);
+ const element = this.getEssentialsSection(workspace.containerTabId);
+ if (!essentialsElements.has(element)) {
+ essentialsElements.set(element, []);
+ }
+ essentialsElements.get(element).push(workspace);
}
- for (const [containerId, spaces] of containerIds) {
+ for (const [element, spaces] of essentialsElements) {
essentialsAnimData.push({
- element: this.getEssentialsSection(containerId),
+ element,
workspaces: spaces,
});
}
@@ -2143,6 +2200,7 @@ class nsZenWorkspaces {
existingOffset = currentTransform || (isGoingLeft ? -100 : 100);
newOffset = 0;
}
+ data.finalOffset = newOffset;
const newTransform = `translateX(${newOffset}%)`;
let existingTransform = `translateX(${existingOffset}%)`;
if (shouldAnimate) {
@@ -2181,6 +2239,12 @@ class nsZenWorkspaces {
document.documentElement.removeAttribute("animating-background");
if (shouldAnimate) {
for (const data of essentialsAnimData) {
+ if (this.creatingWorkspaceId && data.finalOffset) {
+ // The space being created has no essentials of its own, leave the
+ // ones we just slid away parked off screen instead of snapping them
+ // back under the creation form.
+ continue;
+ }
data.element.style.removeProperty("transform");
}
this._alwaysAnimatePaddingTop = true;
@@ -2527,7 +2591,9 @@ class nsZenWorkspaces {
icon = undefined,
dontChange = false,
containerTabId = 0,
- { beforeChangeCallback } = { beforeChangeCallback: null } // Callback to run before changing workspace
+ // beforeChangeCallback runs before changing workspace, insertAfterId places
+ // the new space right after an existing one instead of at the end.
+ { beforeChangeCallback = null, insertAfterId = null } = {}
) {
if (!this.workspaceEnabled) {
return null;
@@ -2548,7 +2614,7 @@ class nsZenWorkspaces {
this.#createWorkspaceTabsSection(workspaceData, extraTabs);
this._organizeWorkspaceStripLocations(workspaceData);
}
- this.saveWorkspace(workspaceData);
+ this.saveWorkspace(workspaceData, { insertAfterId });
if (!dontChange) {
if (beforeChangeCallback) {
try {
@@ -2670,6 +2736,11 @@ class nsZenWorkspaces {
workspacesIds.push(originalWorkspaceId);
}
for (const workspaceId of workspacesIds) {
+ if (workspaceId === this.creatingWorkspaceId) {
+ // The space being created shows the creation form instead of its
+ // essentials, so it doesn't reserve any room for them.
+ continue;
+ }
const workspaceElement = this.workspaceElement(workspaceId);
const workspaceObject = this.getWorkspaceFromId(workspaceId);
if (!workspaceElement || !workspaceObject) {
diff --git a/src/zen/spaces/create-workspace-form.css b/src/zen/spaces/create-workspace-form.css
index 2a96165bb..64a9caf40 100644
--- a/src/zen/spaces/create-workspace-form.css
+++ b/src/zen/spaces/create-workspace-form.css
@@ -6,9 +6,10 @@
zen-workspace-creation {
flex: 1;
- max-width: calc(var(--zen-sidebar-width) - var(--zen-toolbox-padding));
- padding: 3px;
- visibility: collapse;
+ flex-direction: column;
+ min-height: 0;
+ width: 100%;
+ padding: 3px var(--zen-toolbox-padding);
& .zen-workspace-creation {
justify-content: center;
diff --git a/src/zen/spaces/zen-workspaces.css b/src/zen/spaces/zen-workspaces.css
index b510c7d18..89141de8d 100644
--- a/src/zen/spaces/zen-workspaces.css
+++ b/src/zen/spaces/zen-workspaces.css
@@ -324,7 +324,10 @@ zen-workspace {
overflow: hidden;
color: var(--toolbox-textcolor);
- --tab-background-color-selected: color-mix(in srgb, light-dark(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.18)) 95%, var(--zen-primary-color)) !important;
+ --tab-background-color-selected: light-dark(
+ color-mix(in srgb, rgba(255, 255, 255, 0.8) 98%, var(--zen-primary-color)),
+ color-mix(in srgb, rgba(255, 255, 255, 0.18) 95%, var(--zen-primary-color))
+ ) !important;
--tab-box-shadow-selected: 0 0.8px 1.5px 0px light-dark(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.05)) !important;
--tab-selected-textcolor: color-mix(in srgb, var(--toolbox-textcolor) 95%, var(--zen-primary-color)) !important;
diff --git a/src/zen/sync/ZenSpacesSync.sys.mjs b/src/zen/sync/ZenSpacesSync.sys.mjs
index e94e0c13d..68752270b 100644
--- a/src/zen/sync/ZenSpacesSync.sys.mjs
+++ b/src/zen/sync/ZenSpacesSync.sys.mjs
@@ -139,7 +139,7 @@ export class ZenSpacesSyncEngine extends SyncEngine {
}
get version() {
- return 1;
+ return 3;
}
get syncPriority() {
diff --git a/src/zen/tabs/zen-tabs/vertical-tabs.css b/src/zen/tabs/zen-tabs/vertical-tabs.css
index 671373fe1..813899459 100644
--- a/src/zen/tabs/zen-tabs/vertical-tabs.css
+++ b/src/zen/tabs/zen-tabs/vertical-tabs.css
@@ -103,7 +103,7 @@
/* stylelint-disable-next-line stylelint-plugin-mozilla/no-has-selector */
& #urlbar-container[breakout="true"]:has(#urlbar[zen-floating-urlbar="true"]) {
--urlbar-container-height: 36px !important;
- --urlbar-height: 36px !important;
+ --urlbar-height: 38px !important;
}
& #nav-bar {
@@ -284,8 +284,12 @@
--focus-outline-color: transparent;
+ @media (-moz-platform: windows) {
+ font-size: 14px;
+ }
+
@media (-moz-platform: macos) {
- font-size: 1.25rem; /* Slightly larger font on macOS */
+ font-size: 1.25rem;
}
--tab-inner-inline-margin: 0;
@@ -558,7 +562,7 @@
}
& #zen-essentials {
- --tab-min-height: 44px;
+ --tab-min-height: 46px;
}
&[zen-right-side="true"] {
@@ -1149,8 +1153,7 @@
}
}
-.tabbrowser-tab[zen-essential="true"],
-#zen-welcome-initial-essentials-browser-sidebar-essentials .tabbrowser-tab {
+.tabbrowser-tab[zen-essential="true"] {
--toolbarbutton-padding-inner: 0;
max-width: unset;
width: 100% !important;
diff --git a/src/zen/tests/welcome/browser_welcome.js b/src/zen/tests/welcome/browser_welcome.js
index aeedb2482..8ac815b10 100644
--- a/src/zen/tests/welcome/browser_welcome.js
+++ b/src/zen/tests/welcome/browser_welcome.js
@@ -11,191 +11,195 @@ ChromeUtils.defineESModuleGetters(lazy, {
/* eslint-disable mozilla/no-arbitrary-setTimeout */
+function wait(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+function currentPageContent() {
+ return document.querySelector(
+ "#zen-welcome-page-content > .zen-welcome-page:not([exiting])"
+ );
+}
+
add_task(async function test_Welcome_Steps() {
const selectedTab = gBrowser.selectedTab;
- await new Promise(resolve => {
- setTimeout(async () => {
- await waitForFocus();
- await EventUtils.synthesizeMouseAtCenter(
- document.getElementById("zen-welcome-start-button"),
- {}
- );
- setTimeout(() => {
- resolve();
- }, 4000); // Wait for the transition to complete
- }, 2000); // Give tons of time for the welcome start button to be clicked
- });
+ await wait(2000); // Give tons of time for the welcome start button to be clicked
+ await waitForFocus();
+ await EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("zen-welcome-start-button"),
+ {}
+ );
+ await wait(1500); // Wait for the transition to complete
ok(true, "Welcome start button clicked successfully");
- Assert.equal(
+ Assert.notEqual(
window.windowState,
window.STATE_MAXIMIZED,
- "Window should be maximized after clicking the welcome start button"
+ "Window should not be maximized by the welcome screen"
);
- const welcomeContent = document.getElementById("zen-welcome-page-content");
-
- for (const button of document.querySelectorAll(
- "#zen-welcome-page-sidebar-buttons button"
- )) {
- Assert.notStrictEqual(
- getComputedStyle(button).pointerEvents,
- "none",
- `Button with l10n-id "${button.getAttribute("data-l10n-id")}" should be clickable`
- );
- }
-
- await goNextWelcomePage("zen-welcome-skip-button");
- ok(true, "Welcome Import Step Test Finished");
-
- Assert.greater(
- welcomeContent.children.length,
- 0,
- "Welcome page content should have children after clicking next action"
+ const backButton = document.getElementById("zen-welcome-back");
+ ok(
+ backButton.hasAttribute("disabled"),
+ "Back button should be hidden on the first page"
+ );
+ Assert.equal(
+ currentPageContent().querySelectorAll(".zen-welcome-option").length,
+ 2,
+ "Import page should offer two options"
);
-
- for (const child of welcomeContent.children) {
- ok(
- child.querySelector("img").getAttribute("src").includes("blob:"),
- "Welcome page content should have an image with a base64 data URL"
- );
- }
-
- await EventUtils.synthesizeMouseAtCenter(welcomeContent.children[1], {});
-
- await new Promise(resolve => {
- setTimeout(async () => {
- let engineName = await lazy.SearchService.getDefault();
- const selectedLabel = welcomeContent.children[1];
- ok(
- selectedLabel.querySelector("input").checked,
- "The selected label should be checked"
- );
- Assert.equal(
- engineName.name,
- selectedLabel.querySelector("label").textContent.trim(),
- "The default search engine should match the selected label"
- );
- resolve();
- }, 100); // Wait for the transition to complete
- });
await goNextWelcomePage("zen-generic-next");
- ok(true, "Welcome Search Step Test Finished");
+ ok(true, "Welcome Import Step Test Finished");
- await new Promise(resolve => {
- setTimeout(async () => {
- const essentials = welcomeContent.querySelector(
- "#zen-welcome-initial-essentials-browser-sidebar-essentials"
- ).children;
- Assert.greater(
- essentials.length,
- 3,
- "Welcome page content should have more than 3 essentials after clicking next action"
- );
- await EventUtils.synthesizeMouseAtCenter(essentials[0], {});
- await EventUtils.synthesizeMouseAtCenter(essentials[2], {});
-
- ok(
- essentials[0].hasAttribute("visuallyselected"),
- "The first essential should be visually selected"
- );
- ok(
- !essentials[1].hasAttribute("visuallyselected"),
- "The second essential should be visually selected"
- );
- ok(
- essentials[2].hasAttribute("visuallyselected"),
- "The third essential should be visually selected"
- );
- const urlsToCheck = [
- essentials[0].getAttribute("data-url"),
- essentials[2].getAttribute("data-url"),
- ];
- for (const url of urlsToCheck) {
- ok(
- url.startsWith("https://"),
- `The URL "${url}" should start with "https://"`
- );
- }
-
- await goNextWelcomePage("zen-generic-next");
-
- await new Promise(r => {
- setTimeout(async () => {
- for (const url of urlsToCheck) {
- ok(
- await PlacesUtils.history.hasVisits(url),
- `The URL "${url}" should have visits in history`
- );
- }
- r();
- }, 1000); // Wait for the transition to complete
- });
- resolve();
- }, 1000); // Wait for the transition to complete
- });
+ ok(
+ !backButton.hasAttribute("disabled"),
+ "Back button should be visible after the first page"
+ );
+ await EventUtils.synthesizeMouseAtCenter(backButton, {});
+ await wait(800);
+ ok(
+ backButton.hasAttribute("disabled"),
+ "Going back should return to the first page"
+ );
+ await goNextWelcomePage("zen-generic-next");
await goNextWelcomePage("zen-generic-next");
ok(true, "Welcome Theme Step Test Finished");
+ let options = currentPageContent().querySelectorAll(".zen-welcome-option");
+ Assert.greater(
+ options.length,
+ 0,
+ "Search page should list the available engines"
+ );
+ for (const option of options) {
+ ok(
+ option.querySelector("img").getAttribute("src").includes("blob:"),
+ "Each engine option should have an image with a blob URL"
+ );
+ }
+
+ await EventUtils.synthesizeMouseAtCenter(options[1], {});
+ await wait(100);
+ let engineName = await lazy.SearchService.getDefault();
+ ok(
+ options[1].querySelector("input").checked,
+ "The selected option should be checked"
+ );
+ Assert.equal(
+ engineName.name,
+ options[1].querySelector(".zen-welcome-option-label").textContent.trim(),
+ "The default search engine should match the selected option"
+ );
+
+ await goNextWelcomePage("zen-generic-next");
+ ok(true, "Welcome Search Step Test Finished");
+
+ const essentials = currentPageContent().querySelectorAll(
+ "#zen-welcome-essentials .zen-welcome-essential"
+ );
+ Assert.greater(
+ essentials.length,
+ 3,
+ "Welcome page content should have more than 3 essentials"
+ );
+ await EventUtils.synthesizeMouseAtCenter(essentials[0], {});
+ await EventUtils.synthesizeMouseAtCenter(essentials[2], {});
+ ok(
+ essentials[0].hasAttribute("selected"),
+ "The first essential should be selected"
+ );
+ ok(
+ !essentials[1].hasAttribute("selected"),
+ "The second essential should not be selected"
+ );
+ ok(
+ essentials[2].hasAttribute("selected"),
+ "The third essential should be selected"
+ );
+ const urlsToCheck = [essentials[0].dataset.url, essentials[2].dataset.url];
+ for (const url of urlsToCheck) {
+ ok(
+ url.startsWith("https://"),
+ `The URL "${url}" should start with "https://"`
+ );
+ }
+
+ await goNextWelcomePage("zen-generic-next");
+ ok(true, "Welcome Essentials Step Test Finished");
+
+ options = currentPageContent().querySelectorAll(".zen-welcome-option");
+ Assert.equal(options.length, 2, "Default browser page should have 2 options");
+ await EventUtils.synthesizeMouseAtCenter(options[0], {});
+ ok(
+ options[0].querySelector("input").checked,
+ "Clicking the option should check its radio"
+ );
+ // Don't actually change the default browser in the test.
+ await EventUtils.synthesizeMouseAtCenter(options[1], {});
+
+ await goNextWelcomePage("zen-generic-next");
+ ok(true, "Welcome Default Browser Step Test Finished");
+
await goNextWelcomePage("zen-welcome-start-browsing");
ok(true, "Welcome Finish Step Test Finished");
- await new Promise(resolve => {
- setTimeout(async () => {
- Assert.greater(
- gBrowser._numZenEssentials,
- 3,
- "There should be more than 3 Zen Essentials after the welcome process"
- );
- Assert.equal(
- gBrowser.tabs.filter(
- tab => tab.pinned && !tab.hasAttribute("zen-essential")
- ).length,
- 3,
- "There should be 3 pinned tabs after the welcome process"
- );
+ await wait(3000); // Wait for the finish animation to complete
- gBrowser.selectedTab = selectedTab;
- const groups = gBrowser.tabGroups;
- Assert.equal(
- groups.length,
- 1,
- "There should be one tab group after the welcome process"
- );
- const group = groups[0];
- Assert.equal(
- group.tabs.length,
- 3,
- "The first tab group should have 3 tabs after the welcome process"
+ for (const url of urlsToCheck) {
+ ok(
+ await PlacesUtils.history.hasVisits(url),
+ `The URL "${url}" should have visits in history`
+ );
+ }
+ Assert.equal(
+ gBrowser._numZenEssentials,
+ 2,
+ "There should be 2 Essentials after the welcome process"
+ );
+ Assert.equal(
+ gBrowser.tabs.filter(
+ tab => tab.pinned && !tab.hasAttribute("zen-essential")
+ ).length,
+ 2,
+ "There should be 2 pinned tabs after the welcome process"
+ );
+
+ gBrowser.selectedTab = selectedTab;
+ const groups = gBrowser.tabGroups;
+ Assert.equal(
+ groups.length,
+ 1,
+ "There should be one tab group after the welcome process"
+ );
+ const group = groups[0];
+ Assert.equal(
+ group.tabs.length,
+ 2,
+ "The first tab group should have 2 tabs after the welcome process"
+ );
+ Assert.equal(
+ group.label,
+ "zen basics",
+ 'The first tab group should be labeled "zen basics" after the welcome process'
+ );
+ for (const tab of gBrowser.tabs) {
+ if (tab.hasAttribute("zen-empty-tab")) {
+ continue;
+ }
+ if (tab.pinned && !tab.hasAttribute("zen-essential")) {
+ ok(
+ tab.hasAttribute("zen-workspace-id"),
+ "Pinned tabs should have a zen-workspace-id attribute"
);
Assert.equal(
- group.label,
- "zen basics",
- 'The first tab group should be labeled "zen basics" after the welcome process'
+ tab.group,
+ group,
+ "Pinned tabs should belong to the first tab group"
);
- for (const tab of gBrowser.tabs) {
- if (tab.hasAttribute("zen-empty-tab")) {
- continue;
- }
- if (tab.pinned) {
- if (!tab.hasAttribute("zen-essential")) {
- ok(
- tab.hasAttribute("zen-workspace-id"),
- "Pinned tabs should have a zen-workspace-id attribute"
- );
- Assert.equal(
- tab.group,
- group,
- "Pinned tabs should belong to the first tab group"
- );
- }
- }
- }
- group.delete();
- resolve();
- }, 3000); // Wait for the transition to complete
- });
+ }
+ }
+ group.delete();
for (const tab of gBrowser.tabs) {
if (tab.pinned) {
gBrowser.removeTab(tab);
diff --git a/src/zen/tests/welcome/head.js b/src/zen/tests/welcome/head.js
index f5ad262ae..b1f903797 100644
--- a/src/zen/tests/welcome/head.js
+++ b/src/zen/tests/welcome/head.js
@@ -16,7 +16,7 @@ async function goNextWelcomePage(l10nId) {
setTimeout(() => {
resolve();
}, 0);
- }, 3000); // Wait for the transition to complete
+ }, 800); // Wait for the transition to complete
});
}
diff --git a/src/zen/welcome/ZenWelcome.mjs b/src/zen/welcome/ZenWelcome.mjs
index d57446cc2..bed6b00a0 100644
--- a/src/zen/welcome/ZenWelcome.mjs
+++ b/src/zen/welcome/ZenWelcome.mjs
@@ -3,7 +3,6 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
{
- let _tabsToPinEssentials = [];
let lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
@@ -15,6 +14,23 @@
"zen-toast-container",
];
+ const kEssentialApps = [
+ { url: "https://obsidian.md", icon: "obsidian", color: "#7c3aed" },
+ { url: "https://discord.com", icon: "discord", color: "#5865f2" },
+ { url: "https://trello.com", icon: "trello", color: "#0052cc" },
+ { url: "https://slack.com", icon: "slack", color: "#e01e5a" },
+ { url: "https://github.com", icon: "github", color: "#57606a" },
+ { url: "https://app.tuta.com/", icon: "tuta", color: "#c00002" },
+ { url: "https://notion.com", icon: "notion", color: "#37352f" },
+ { url: "https://calendar.google.com", icon: "calendar", color: "#4285f4" },
+ { url: "https://figma.com", icon: "figma", color: "#f24e1e" },
+ ];
+
+ const gChoices = {
+ setDefaultBrowser: false,
+ essentials: new Set(),
+ };
+
function clearBrowserElements() {
for (const element of document.getElementById("browser").children) {
if (kZenElementsToIgnore.includes(element.id)) {
@@ -28,33 +44,39 @@
return gZenUIManager.motion;
}
- async function animate(...args) {
+ function animate(...args) {
return getMotion().animate(...args);
}
+ function parseXUL(xul) {
+ return window.MozXULElement.parseXULToFragment(xul);
+ }
+
function initializeZenWelcome() {
document.documentElement.setAttribute("zen-welcome-stage", "true");
const XUL = `
+
-
+
-
-
-
+
+
-
+
+
`;
- const fragment = window.MozXULElement.parseXULToFragment(XUL);
- document.getElementById("browser").appendChild(fragment);
+ document.getElementById("browser").appendChild(parseXUL(XUL));
+ const video = document.getElementById("zen-welcome-video");
+ video.play().catch(() => {});
window.MozXULElement.insertFTLIfNeeded("browser/zen-welcome.ftl");
}
@@ -82,175 +104,212 @@
return data;
}
+ function createOption({ id, group, checked, l10n, text, extra }) {
+ const label = document.createElement("label");
+ label.className = "zen-welcome-option";
+ label.setAttribute("for", id);
+ const input = document.createElement("input");
+ input.type = "radio";
+ input.id = id;
+ input.name = group;
+ input.checked = !!checked;
+ label.appendChild(input);
+ const radio = document.createElement("span");
+ radio.className = "zen-welcome-option-radio no-squircles";
+ label.appendChild(radio);
+ if (extra) {
+ label.appendChild(extra);
+ }
+ const labelText = document.createElement("span");
+ labelText.className = "zen-welcome-option-label";
+ if (l10n) {
+ document.l10n.setAttributes(labelText, l10n);
+ } else {
+ labelText.textContent = text;
+ }
+ label.appendChild(labelText);
+ return label;
+ }
+
+ function removeVideoBackground() {
+ const video = document.getElementById("zen-welcome-video");
+ if (!video) {
+ return;
+ }
+ animate(video, { opacity: 0 }, { duration: 0.6, ease: "easeOut" }).then(
+ () => {
+ video.pause();
+ video.remove();
+ }
+ );
+ }
+
+ const kSpring = { type: "spring", bounce: 0.35, visualDuration: 0.35 };
+ const kExit = { duration: 0.12, ease: "easeIn" };
+ const kFade = { duration: 0.25, ease: "easeOut" };
+
class nsZenWelcomePages {
+ #index = -1;
+ #pages;
+ #content = null;
+ #renderToken = 0;
+ #buttonsToken = 0;
+
constructor(pages) {
- this._currentPage = -1;
- this._pages = pages;
+ this.#pages = pages;
this.init();
this.next();
}
+ get textContainer() {
+ return document.getElementById("zen-welcome-page-sidebar-content");
+ }
+
+ get contentContainer() {
+ return document.getElementById("zen-welcome-page-content");
+ }
+
+ get buttonsContainer() {
+ return document.getElementById("zen-welcome-page-sidebar-buttons");
+ }
+
+ get backButton() {
+ return document.getElementById("zen-welcome-back");
+ }
+
+ get currentPage() {
+ return this.#pages[this.#index];
+ }
+
+ get content() {
+ return this.#content;
+ }
+
init() {
- document.getElementById("zen-welcome-pages").style.display = "flex";
document.getElementById("zen-welcome-start").remove();
- window.maximize();
- animate(
- "#zen-welcome-pages",
- { opacity: [0, 1] },
- { delay: 0.2, duration: 0.1 }
- );
+ const pages = document.getElementById("zen-welcome-pages");
+ pages.style.display = "flex";
+ animate(pages, { opacity: [0, 1] }, { ...kFade, duration: 0.2 });
+ this.backButton.addEventListener("click", () => this.back());
}
- async fadeInTitles(page) {
- const [title1, description1, description2] =
- await document.l10n.formatValues(page.text);
- const titleElement = document.getElementById(
- "zen-welcome-page-sidebar-content"
- );
- /* eslint-disable no-unsanitized/property */
- titleElement.innerHTML =
- `${title1} ${description1} ` +
- (description2 ? `${description2} ` : "");
- await animate(
- "#zen-welcome-page-sidebar-content > *",
- { x: ["150%", 0] },
- {
- delay: getMotion().stagger(0.05),
- type: "spring",
- bounce: 0.2,
- }
- );
+ next() {
+ this.currentPage?.commit?.(this.#content);
+ this.#show(this.#index + 1, 1);
}
- async fadeInButtons(page) {
- const buttons = document.getElementById(
- "zen-welcome-page-sidebar-buttons"
- );
- let i = 0;
- const insertedButtons = [];
- for (const button of page.buttons) {
- const buttonElement = document.createXULElement("button");
- document.l10n.setAttributes(buttonElement, button.l10n);
- if (i++ === 0) {
- buttonElement.classList.add("primary");
- }
- buttonElement.classList.add("footer-button");
- buttonElement.addEventListener("click", async () => {
- const shouldSkip = await button.onclick();
- if (shouldSkip) {
- this.next();
- }
- });
- buttonElement.style.pointerEvents = "none"; // Disable pointer events until animation is done
- insertedButtons.push(buttonElement);
- buttons.appendChild(buttonElement);
- }
- await animate(
- "#zen-welcome-page-sidebar-buttons button",
- { x: ["150%", 0] },
- {
- delay: getMotion().stagger(0.1, { startDelay: 0.4 }),
- type: "spring",
- bounce: 0.2,
- }
- );
- for (const button of insertedButtons) {
- button.style.pointerEvents = ""; // Enable pointer events after animation
+ back() {
+ if (this.#index <= 0) {
+ return;
}
+ this.#show(this.#index - 1, -1);
}
- async fadeInContent() {
- await animate(
- "#zen-welcome-page-content > *",
- { opacity: [0, 1] },
- {
- delay: getMotion().stagger(0.1),
- type: "spring",
- bounce: 0.2,
- }
- );
- }
-
- async fadeOutButtons() {
- await animate(
- "#zen-welcome-page-sidebar-buttons button",
- { x: [0, "-150%"] },
- {
- type: "spring",
- bounce: 0,
- delay: getMotion().stagger(0.1, { startDelay: 0.4 }),
- }
- );
- document.getElementById("zen-welcome-page-sidebar-buttons").innerHTML =
- "";
- document.getElementById("zen-welcome-page-sidebar-content").innerHTML =
- "";
- }
-
- async fadeOutTitles() {
- await animate(
- "#zen-welcome-page-sidebar-content > *",
- { x: [0, "-150%"] },
- {
- delay: getMotion().stagger(0.05, { startDelay: 0.3 }),
- type: "spring",
- bounce: 0,
- }
- );
- }
-
- async fadeOutContent() {
- await animate(
- "#zen-welcome-page-content > *",
- { opacity: [1, 0] },
- {
- delay: getMotion().stagger(0.05, { startDelay: 0.3 }),
- type: "spring",
- bounce: 0,
- duration: 0.1,
- }
- );
- }
-
- async next() {
- if (this._currentPage !== -1) {
- const previousPage = this._pages[this._currentPage];
- const promises = [this.fadeOutTitles(), this.fadeOutButtons()];
- if (!previousPage.dontFadeOut) {
- promises.push(this.fadeOutContent());
- }
- await Promise.all(promises);
- await previousPage.fadeOut();
- document.getElementById("zen-welcome-page-content").innerHTML = "";
- }
- this._currentPage++;
- const currentPage = this._pages[this._currentPage];
- if (!currentPage) {
+ #show(index, direction) {
+ const previous = this.currentPage;
+ this.#index = index;
+ const page = this.currentPage;
+ if (!page) {
this.finish();
return;
}
- await Promise.all([
- this.fadeInTitles(currentPage),
- this.fadeInButtons(currentPage),
- ]);
- await currentPage.fadeIn();
- await this.fadeInContent();
+ previous?.leave?.();
+ this.#exit(this.textContainer, { x: [0, -80 * direction] });
+ this.#exit(this.contentContainer, {});
+ this.backButton.toggleAttribute("disabled", index === 0);
+ this.updateButtons();
+ this.#renderText(page, direction);
+ this.#renderContent(page);
+ }
+
+ #exit(container, keyframes) {
+ for (const element of container.children) {
+ if (element.hasAttribute("exiting")) {
+ continue;
+ }
+ element.setAttribute("exiting", "");
+ animate(element, { opacity: [1, 0], ...keyframes }, kExit).then(() =>
+ element.remove()
+ );
+ }
+ }
+
+ async #renderText(page, direction) {
+ const token = ++this.#renderToken;
+ const text = document.createElement("div");
+ text.className = "zen-welcome-text";
+ const title = document.createElement("h1");
+ document.l10n.setAttributes(title, page.title);
+ text.appendChild(title);
+ for (const id of page.descriptions ?? []) {
+ const p = document.createElement("p");
+ document.l10n.setAttributes(p, id);
+ text.appendChild(p);
+ }
+ await document.l10n.translateFragment(text);
+ if (token !== this.#renderToken) {
+ return;
+ }
+ this.textContainer.appendChild(text);
+ animate(
+ [...text.children],
+ { opacity: [0, 1], x: [120 * direction, 0] },
+ {
+ ...kSpring,
+ bounce: 0.4,
+ visualDuration: 0.3,
+ delay: getMotion().stagger(0.03),
+ }
+ );
+ }
+
+ #renderContent(page) {
+ const content = document.createElement("div");
+ content.className = "zen-welcome-page";
+ if (page.id) {
+ content.setAttribute("page", page.id);
+ }
+ page.render(content);
+ this.contentContainer.appendChild(content);
+ this.#content = content;
+ animate(content, { opacity: [0, 1] }, kFade);
+ }
+
+ async updateButtons() {
+ const token = ++this.#buttonsToken;
+ const fragment = document.createDocumentFragment();
+ for (const button of this.currentPage.buttons) {
+ const element = document.createElement("button");
+ element.className = button.primary
+ ? "zen-welcome-button primary"
+ : "zen-welcome-button";
+ document.l10n.setAttributes(element, button.l10n);
+ element.addEventListener("click", () => {
+ if (button.onclick?.(this) !== false) {
+ this.next();
+ }
+ });
+ fragment.appendChild(element);
+ }
+ await document.l10n.translateFragment(fragment);
+ if (token === this.#buttonsToken) {
+ this.buttonsContainer.replaceChildren(fragment);
+ }
}
async finish() {
- _iconToData = undefined; // Unload icon data
+ ++this.#buttonsToken;
+ this.buttonsContainer.replaceChildren();
+ this.backButton.toggleAttribute("disabled", true);
gZenWorkspaces.reorganizeTabsAfterWelcome();
- await animate(
- "#zen-welcome-page-content",
- { x: [0, "100%"] },
- { bounce: 0 }
- );
- document.getElementById("zen-welcome-page-content").remove();
- await this.animHeart();
- await this.#pinRemainingTabs();
- await animate("#zen-welcome-pages", { opacity: [1, 0] });
+ const promises = [this.#applyChoices()];
+ await animate("#zen-welcome", { opacity: [1, 0] });
+ this.contentContainer.remove();
+ await Promise.all(promises);
+ _iconToData = undefined; // Unload icon data
document.getElementById("zen-welcome").remove();
document.documentElement.removeAttribute("zen-welcome-stage");
+ unlockWindowSize();
for (const element of document.getElementById("browser").children) {
if (kZenElementsToIgnore.includes(element.id)) {
continue;
@@ -265,14 +324,10 @@
await animate(`#browser > *:not(${elementsToIgnore})`, {
opacity: [0, 1],
});
- gZenUIManager.showToast("zen-welcome-finished");
}
- async #pinRemainingTabs() {
- for (const tab of _tabsToPinEssentials) {
- tab.removeAttribute("pending"); // Make it appear loaded
- gZenPinnedTabManager.addToEssentials(tab);
- }
+ async #applyChoices() {
+ await this.#pinEssentials();
let tabsToGroup = [];
if (!gBrowser.selectedTab.hasAttribute("zen-empty-tab")) {
tabsToGroup.push(gBrowser.selectedTab);
@@ -283,22 +338,39 @@
});
}
- async animHeart() {
- const heart = document.createElement("div");
- heart.id = "zen-welcome-heart";
- const sidebar = document.getElementById("zen-welcome-page-sidebar");
- sidebar.style.width = "100%";
- sidebar.appendChild(heart);
- sidebar.setAttribute("animate-heart", "true");
- await animate(
- "#zen-welcome-heart",
- { opacity: [0, 1, 1, 1, 0], scale: [0.5, 1, 1.2, 1, 1.2] },
- {
- duration: 1.5,
- delay: 0.2,
- bounce: 0,
- }
+ async #pinEssentials() {
+ const apps = kEssentialApps.filter(app =>
+ gChoices.essentials.has(app.url)
);
+ if (!apps.length) {
+ return;
+ }
+ await PlacesUtils.history.insertMany(
+ apps.map(app => ({
+ url: app.url,
+ visits: [{ transition: PlacesUtils.history.TRANSITIONS.TYPED }],
+ }))
+ );
+ const { TabStateCache } = ChromeUtils.importESModule(
+ "resource:///modules/sessionstore/TabStateCache.sys.mjs"
+ );
+ for (const app of apps) {
+ const tab = window.gBrowser.addTrustedTab(app.url, {
+ inBackground: true,
+ createLazyBrowser: true,
+ });
+ const icon = await getIconData(
+ `chrome://browser/content/zen-images/favicons/${app.icon}.svg`
+ );
+ // Update the persistent tab state cache with |tabData| information.
+ TabStateCache.update(tab.linkedBrowser.permanentKey, {
+ history: { entries: [{ url: app.url }], index: 0 },
+ image: icon,
+ });
+ gBrowser.setIcon(tab, icon);
+ tab.removeAttribute("pending"); // Make it appear loaded
+ gZenPinnedTabManager.addToEssentials(tab);
+ }
}
}
@@ -362,363 +434,230 @@
}
}
+ const kNextButton = { l10n: "zen-generic-next", primary: true };
+
+ async function setDefaultBrowser() {
+ const shellSvc =
+ AppConstants.HAVE_SHELL_SERVICE && window.getShellService();
+ if (!shellSvc) {
+ return;
+ }
+ try {
+ await shellSvc.setDefaultBrowser(false);
+ } catch (ex) {
+ console.error(ex);
+ }
+ }
+
function getWelcomePages() {
return [
{
- text: [
- {
- id: "zen-welcome-import-title",
- },
- {
- id: "zen-welcome-import-description-1",
- },
- {
- id: "zen-welcome-import-description-2",
- },
- ],
- buttons: [
- {
- l10n: "zen-welcome-import-button",
- onclick: async () => {
- MigrationUtils.showMigrationWizard(window, {
- isStartupMigration: true,
- });
- document
- .querySelector("#zen-welcome-page-sidebar-buttons button")
- .remove();
- const newButton = document.querySelector(
- "#zen-welcome-page-sidebar-buttons button"
- );
- newButton.classList.add("primary");
- document.l10n.setAttributes(newButton, "zen-generic-next");
- return false;
- },
- },
- {
- l10n: "zen-welcome-skip-button",
- onclick: async () => {
- return true;
- },
- },
- ],
- fadeIn() {
- const xul = `
-
-
-
-
-
-
-
-
- `;
- const fragment = window.MozXULElement.parseXULToFragment(xul);
- document
- .getElementById("zen-welcome-page-content")
- .appendChild(fragment);
- },
- async fadeOut() {
- const shouldSetDefault = document.getElementById(
- "zen-welcome-set-default-browser"
- ).checked;
- if (AppConstants.HAVE_SHELL_SERVICE && shouldSetDefault) {
- let shellSvc = window.getShellService();
- if (!shellSvc) {
- return;
- }
-
- try {
- await shellSvc.setDefaultBrowser(false);
- } catch (ex) {
- console.error(ex);
- }
- }
- },
- },
- {
- text: [
- {
- id: "zen-welcome-default-search-title",
- },
- {
- id: "zen-welcome-default-search-description",
- },
- ],
- buttons: [
- {
- l10n: "zen-generic-next",
- onclick: async () => {
- return true;
- },
- },
- ],
- async fadeIn() {
- const content = document.getElementById("zen-welcome-page-content");
- const engineStore = new ZenSearchEngineStore();
- engineStore.init();
-
- content.setAttribute("select-engine", "true");
-
- const defaultEngine = await lazy.SearchService.getDefault();
- const promises = [];
- engineStore.getEngines().forEach(engine => {
- const label = document.createElement("label");
- const engineId = engine.name.replace(/\s+/g, "-").toLowerCase();
- label.setAttribute("for", engineId);
- const input = document.createElement("input");
- input.setAttribute("type", "radio");
- input.setAttribute("id", engineId);
- input.setAttribute("name", "zen-welcome-set-default-browser");
- input.setAttribute("hidden", "true");
- if (engine.name === defaultEngine.name) {
- input.setAttribute("checked", true);
- }
- label.appendChild(input);
- const engineLabel = document.createXULElement("label");
- engineLabel.textContent = engine.name;
- const icon = document.createElement("img");
- promises.push(
- (async () => {
- icon.setAttribute(
- "src",
- await engine.originalEngine.getIconURL()
- );
- })()
- );
- icon.setAttribute("width", "32");
- icon.setAttribute("height", "32");
- icon.setAttribute("class", "engine-icon");
- label.appendChild(icon);
- label.appendChild(engineLabel);
- content.appendChild(label);
- label.addEventListener("click", async () => {
- const selectedEngine = engineStore.getEngineByName(engine.name);
- if (selectedEngine) {
- await engineStore.setDefaultEngine(selectedEngine);
- }
- });
+ id: "import",
+ title: "zen-welcome-import-title",
+ descriptions: ["zen-welcome-import-description"],
+ buttons: [kNextButton],
+ render(content) {
+ const yes = createOption({
+ id: "zen-welcome-import-yes",
+ group: "zen-welcome-import",
+ l10n: "zen-welcome-import-yes",
});
- await Promise.all(promises);
- },
- async fadeOut() {
- document
- .getElementById("zen-welcome-page-content")
- .removeAttribute("select-engine");
- },
- },
- {
- text: [
- {
- id: "zen-welcome-initial-essentials-title",
- },
- {
- id: "zen-welcome-initial-essentials-description-1",
- },
- {
- id: "zen-welcome-initial-essentials-description-2",
- },
- ],
- buttons: [
- {
- l10n: "zen-generic-next",
- onclick: async () => {
- return true;
- },
- },
- ],
- fadeIn() {
- const xul = `
-
-
-
- `;
- const fragment = window.MozXULElement.parseXULToFragment(xul);
- document
- .getElementById("zen-welcome-page-content")
- .appendChild(fragment);
- document
- .getElementById(
- "zen-welcome-initial-essentials-browser-sidebar-essentials"
- )
- .addEventListener("click", async event => {
- const tab = event.target.closest(".tabbrowser-tab");
- if (!tab) {
- return;
- }
- tab.toggleAttribute("visuallyselected");
- });
- },
- async fadeOut() {
- const selectedTabs = document
- .getElementById(
- "zen-welcome-initial-essentials-browser-sidebar-essentials"
- )
- .querySelectorAll(".tabbrowser-tab[visuallyselected]");
-
- if (selectedTabs.length) {
- await PlacesUtils.history.insertMany(
- [...selectedTabs].map(tab => ({
- url: tab.getAttribute("data-url"),
- visits: [
- {
- transition: PlacesUtils.history.TRANSITIONS.TYPED,
- },
- ],
- }))
- );
- }
-
- const { TabStateCache } = ChromeUtils.importESModule(
- "resource:///modules/sessionstore/TabStateCache.sys.mjs"
+ content.appendChild(yes);
+ content.appendChild(
+ createOption({
+ id: "zen-welcome-import-no",
+ group: "zen-welcome-import",
+ l10n: "zen-welcome-import-no",
+ checked: true,
+ })
);
- for (const tab of selectedTabs) {
- const url = tab.getAttribute("data-url");
- const createdTab = window.gBrowser.addTrustedTab(url, {
- inBackground: true,
- createLazyBrowser: true,
+ },
+ commit(content) {
+ if (content.querySelector("#zen-welcome-import-yes").checked) {
+ MigrationUtils.showMigrationWizard(window, {
+ isStartupMigration: true,
});
- let essentialIconUrl = tab.style.getPropertyValue(
- "--zen-essential-tab-icon"
- );
- // Remove url() from the icon URL
- essentialIconUrl = essentialIconUrl
- .replace(/url\(['"]?/, "")
- .replace(/['"]?\)/, "");
- essentialIconUrl = await getIconData(essentialIconUrl);
- // Update the persistent tab state cache with |tabData| information.
- TabStateCache.update(createdTab.linkedBrowser.permanentKey, {
- history: { entries: [{ url }], index: 0 },
- image: essentialIconUrl,
- });
- gBrowser.setIcon(createdTab, essentialIconUrl);
- _tabsToPinEssentials.push(createdTab);
}
},
},
{
- text: [
- {
- id: "zen-welcome-workspace-colors-title",
- },
- {
- id: "zen-welcome-workspace-colors-description",
- },
- ],
- buttons: [
- {
- l10n: "zen-generic-next",
- onclick: async () => {
- return true;
- },
- },
- ],
- fadeIn() {
+ id: "colors",
+ title: "zen-welcome-workspace-colors-title",
+ descriptions: ["zen-welcome-workspace-colors-description"],
+ buttons: [kNextButton],
+ render(content) {
+ removeVideoBackground();
const anchor = document.createElement("div");
anchor.id = "zen-welcome-workspace-colors-anchor";
- document
- .getElementById("zen-welcome-page-content")
- .appendChild(anchor);
- gZenThemePicker.panel.setAttribute("noautohide", "true");
- gZenThemePicker.panel.setAttribute("consumeoutsideclicks", "false");
- gZenThemePicker.panel.setAttribute("nonnative", "");
- gZenThemePicker.panel.addEventListener(
+ content.appendChild(anchor);
+ const panel = gZenThemePicker.panel;
+ panel.setAttribute("noautohide", "true");
+ panel.setAttribute("consumeoutsideclicks", "false");
+ panel.setAttribute("nonnative", "");
+ panel.addEventListener(
"popupshowing",
() => {
- const panelRect = gZenThemePicker.panel.getBoundingClientRect();
+ const panelRect = panel.getBoundingClientRect();
// 20 is the shadow width * 2
- anchor.style.height = panelRect.height - 20 + "px";
+ anchor.style.height =
+ panelRect.height -
+ (AppConstants.platform == "macosx" ? -72 : 20) +
+ "px";
anchor.style.width = panelRect.width - 20 + "px";
},
{ once: true }
);
- PanelMultiView.openPopup(gZenThemePicker.panel, anchor, {
- position: "overlap",
- });
+ PanelMultiView.openPopup(panel, anchor, { position: "overlap" });
},
- dontFadeOut: true,
- async fadeOut() {
- gZenThemePicker.panel.removeAttribute("noautohide");
- gZenThemePicker.panel.removeAttribute("consumeoutsideclicks");
- gZenThemePicker.panel.removeAttribute("nonnative");
- await animate(gZenThemePicker.panel, { opacity: [1, 0] });
- gZenThemePicker.panel.hidePopup();
- gZenThemePicker.panel.removeAttribute("style");
+ leave() {
+ const panel = gZenThemePicker.panel;
+ panel.removeAttribute("noautohide");
+ panel.removeAttribute("consumeoutsideclicks");
+ panel.removeAttribute("nonnative");
+ animate(panel, { opacity: [1, 0] }, kExit).then(() => {
+ panel.hidePopup();
+ panel.removeAttribute("style");
+ });
},
},
{
- text: [
- {
- id: "zen-welcome-start-browsing-title",
- },
- {
- id: "zen-welcome-start-browsing-description-1",
- },
- ],
+ id: "search",
+ title: "zen-welcome-default-search-title",
+ descriptions: ["zen-welcome-default-search-description"],
+ buttons: [kNextButton],
+ async render(content) {
+ const engineStore = new ZenSearchEngineStore();
+ await engineStore.init();
+ const defaultEngine = await lazy.SearchService.getDefault();
+ for (const engine of engineStore.getEngines()) {
+ const iconWrapper = document.createElement("div");
+ iconWrapper.className = "engine-icon-wrapper";
+ const iconBackdrop = document.createElement("img");
+ iconBackdrop.className = "engine-icon-backdrop";
+ const icon = document.createElement("img");
+ icon.className = "engine-icon";
+ icon.width = icon.height = 40;
+ iconWrapper.append(iconBackdrop, icon);
+ engine.originalEngine.getIconURL(96).then(url => {
+ icon.src = url;
+ iconBackdrop.src = url;
+ });
+ const option = createOption({
+ id: "zen-welcome-engine-" + engine.name.replace(/\s+/g, "-"),
+ group: "zen-welcome-search-engine",
+ text: engine.name,
+ checked: engine.name === defaultEngine.name,
+ extra: iconWrapper,
+ });
+ option
+ .querySelector("input")
+ .addEventListener("change", () =>
+ engineStore.setDefaultEngine(engine)
+ );
+ content.appendChild(option);
+ }
+ },
+ },
+ {
+ id: "essentials",
+ title: "zen-welcome-essentials-title",
+ descriptions: ["zen-welcome-essentials-description"],
buttons: [
+ kNextButton,
{
- l10n: "zen-welcome-start-browsing",
- onclick: async () => {
- return true;
+ l10n: "zen-welcome-skip",
+ onclick(pages) {
+ for (const button of pages.content.querySelectorAll(
+ ".zen-welcome-essential[selected]"
+ )) {
+ button.removeAttribute("selected");
+ }
},
},
],
- fadeIn() {},
- fadeOut() {},
+ render(content) {
+ content.appendChild(
+ parseXUL(`
+
+
+
+ `)
+ );
+ const grid = content.querySelector("#zen-welcome-essentials");
+ for (const app of kEssentialApps) {
+ const button = document.createElement("button");
+ button.className = "zen-welcome-essential";
+ button.dataset.url = app.url;
+ button.style.setProperty(
+ "--zen-welcome-app-icon",
+ `url("chrome://browser/content/zen-images/favicons/${app.icon}.svg")`
+ );
+ button.style.setProperty("--zen-welcome-app-color", app.color);
+ button.toggleAttribute(
+ "selected",
+ gChoices.essentials.has(app.url)
+ );
+ button.addEventListener("click", () => {
+ button.toggleAttribute("selected");
+ });
+ grid.appendChild(button);
+ }
+ },
+ commit(content) {
+ gChoices.essentials = new Set(
+ [
+ ...content.querySelectorAll(".zen-welcome-essential[selected]"),
+ ].map(button => button.dataset.url)
+ );
+ },
+ },
+ {
+ id: "default-browser",
+ title: "zen-welcome-default-browser-title",
+ descriptions: ["zen-welcome-default-browser-description"],
+ buttons: [kNextButton],
+ render(content) {
+ content.appendChild(
+ createOption({
+ id: "zen-welcome-set-default-browser",
+ group: "zen-welcome-default-browser",
+ l10n: "zen-welcome-set-default-browser",
+ checked: gChoices.setDefaultBrowser,
+ })
+ );
+ content.appendChild(
+ createOption({
+ id: "zen-welcome-dont-set-default-browser",
+ group: "zen-welcome-default-browser",
+ l10n: "zen-welcome-dont-set-default-browser",
+ checked: !gChoices.setDefaultBrowser,
+ })
+ );
+ },
+ commit(content) {
+ gChoices.setDefaultBrowser = content.querySelector(
+ "#zen-welcome-set-default-browser"
+ ).checked;
+ if (gChoices.setDefaultBrowser) {
+ setDefaultBrowser();
+ }
+ },
+ },
+ {
+ id: "finish",
+ title: "zen-welcome-start-browsing-title",
+ descriptions: ["zen-welcome-start-browsing-description-1"],
+ buttons: [{ l10n: "zen-welcome-start-browsing", primary: true }],
+ render() {},
},
];
}
@@ -729,33 +668,44 @@
{ id: "zen-welcome-title-line2" },
]);
const titleElement = document.getElementById("zen-welcome-title");
- /* eslint-disable no-unsanitized/property */
- titleElement.innerHTML = `${title1} ${title2} `;
+ for (const line of [title1, title2]) {
+ const lineElement = document.createElement("span");
+ for (const char of line) {
+ if (char === " ") {
+ lineElement.append(" ");
+ continue;
+ }
+ const charElement = document.createElement("span");
+ charElement.className = "zen-welcome-char";
+ charElement.textContent = char;
+ lineElement.appendChild(charElement);
+ }
+ titleElement.appendChild(lineElement);
+ }
+ const chars = titleElement.querySelectorAll(".zen-welcome-char");
await animate(
- "#zen-welcome-title span",
- { opacity: [0, 1], y: [20, 0], filter: ["blur(2px)", "blur(0px)"] },
+ chars,
+ { opacity: [0, 1], y: [50, 0] },
{
- delay: getMotion().stagger(0.6, { startDelay: 0.2 }),
+ delay: getMotion().stagger(0.035, { startDelay: 0.2 }),
type: "spring",
- stiffness: 300,
- damping: 20,
- mass: 1.8,
+ bounce: 0.3,
+ visualDuration: 0.45,
}
);
const button = document.getElementById("zen-welcome-start-button");
- button.addEventListener("click", async () => {
- await animate(
- "#zen-welcome-title span, #zen-welcome-start-button",
- { opacity: [1, 0], y: [0, -10], filter: ["blur(0px)", "blur(2px)"] },
- {
- type: "spring",
- ease: [0.755, 0.05, 0.855, 0.06],
- bounce: 0.4,
- delay: getMotion().stagger(0.4),
- }
- );
- new nsZenWelcomePages(getWelcomePages());
- });
+ button.addEventListener(
+ "click",
+ async () => {
+ await animate(
+ "#zen-welcome-title .zen-welcome-char, #zen-welcome-start-button",
+ { opacity: [1, 0], y: [0, -14] },
+ { ...kSpring, delay: getMotion().stagger(0.012) }
+ );
+ new nsZenWelcomePages(getWelcomePages());
+ },
+ { once: true }
+ );
await animate(
button,
{ opacity: [0, 1], y: [20, 0], filter: ["blur(2px)", "blur(0px)"] },
@@ -769,20 +719,45 @@
);
}
+ const kSizeLockProperties = [
+ "min-width",
+ "max-width",
+ "min-height",
+ "max-height",
+ ];
+
+ function lockWindowSize(width, height) {
+ const style = document.documentElement.style;
+ style.setProperty("min-width", `${width}px`, "important");
+ style.setProperty("max-width", `${width}px`, "important");
+ style.setProperty("min-height", `${height}px`, "important");
+ style.setProperty("max-height", `${height}px`, "important");
+ }
+
+ function unlockWindowSize() {
+ const style = document.documentElement.style;
+ for (const property of kSizeLockProperties) {
+ style.removeProperty(property);
+ }
+ }
+
function centerWindowOnScreen() {
window.addEventListener(
"MozAfterPaint",
function () {
- window.resizeTo(875, 560);
+ const width = Math.min(1200, screen.availWidth);
+ const height = Math.min(720, screen.availHeight);
+ window.resizeTo(width, height);
window.focus();
const appWin = window.docShell.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIAppWindow);
appWin.rollupAllPopups();
window.moveTo(
- screen.availLeft + (screen.availWidth - outerWidth) / 2,
- screen.availTop + (screen.availHeight - outerHeight) / 2
+ screen.availLeft + (screen.availWidth - width) / 2,
+ screen.availTop + (screen.availHeight - height) / 2
);
+ lockWindowSize(width, height);
},
{ once: true }
);
diff --git a/src/zen/welcome/jar.inc.mn b/src/zen/welcome/jar.inc.mn
index 7295cfbaf..dc8cc2305 100644
--- a/src/zen/welcome/jar.inc.mn
+++ b/src/zen/welcome/jar.inc.mn
@@ -4,3 +4,4 @@
content/browser/zen-components/ZenWelcome.mjs (../../zen/welcome/ZenWelcome.mjs)
content/browser/zen-styles/zen-welcome.css (../../zen/welcome/zen-welcome.css)
+ content/browser/zen-videos/welcome-background.mp4 (../../zen/welcome/welcome-background.mp4)
diff --git a/src/zen/welcome/welcome-background.mp4 b/src/zen/welcome/welcome-background.mp4
new file mode 100644
index 000000000..6619322d8
Binary files /dev/null and b/src/zen/welcome/welcome-background.mp4 differ
diff --git a/src/zen/welcome/zen-welcome.css b/src/zen/welcome/zen-welcome.css
index 0f7307366..13247db21 100644
--- a/src/zen/welcome/zen-welcome.css
+++ b/src/zen/welcome/zen-welcome.css
@@ -15,6 +15,21 @@
-moz-window-dragging: drag;
}
+#zen-welcome {
+ position: relative;
+ isolation: isolate;
+}
+
+#zen-welcome-video {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ pointer-events: none;
+ will-change: opacity;
+}
+
:root[zen-welcome-stage] {
min-width: 875px;
min-height: 560px;
@@ -24,32 +39,111 @@
}
}
+#zen-welcome {
+ --zen-welcome-accent: light-dark(
+ oklch(from var(--zen-primary-color) clamp(0.35, l, 0.6) c h),
+ oklch(from var(--zen-primary-color) clamp(0.7, l, 0.85) c h)
+ );
+ --zen-welcome-accent-button: light-dark(
+ oklch(from var(--zen-primary-color) clamp(0.35, l, 0.6) c h),
+ oklch(from var(--zen-primary-color) clamp(0.55, l, 0.7) c h)
+ );
+ --zen-welcome-sidebar-bg: light-dark(#ffffff, #1c1c1e);
+ --zen-welcome-content-bg: light-dark(#f6f6f8f5, #2b2b30ed);
+ --zen-welcome-surface-bg: light-dark(#ffffff, #1c1c1e);
+ --zen-welcome-tile-bg: light-dark(#ececee, #333338);
+ --zen-welcome-border: light-dark(
+ rgba(0, 0, 0, 0.14),
+ rgba(255, 255, 255, 0.16)
+ );
+ --zen-welcome-text-secondary: light-dark(
+ rgba(0, 0, 0, 0.6),
+ rgba(255, 255, 255, 0.6)
+ );
+}
+
+.zen-welcome-button {
+ appearance: none;
+ border: none;
+ border-radius: 12px;
+ padding: 1.2rem 1rem;
+ background: transparent;
+ color: var(--zen-welcome-accent);
+ font: inherit;
+ font-size: 1.2rem;
+ font-weight: 600;
+ cursor: pointer;
+ will-change: transform;
+ transition: scale 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
+
+ &:hover {
+ scale: 1.03;
+ filter: brightness(1.1);
+ }
+
+ &:active {
+ scale: 1;
+ }
+
+ &.primary {
+ background: var(--zen-welcome-accent-button);
+ color: white;
+
+ &:hover {
+ filter: brightness(1.1);
+ }
+ }
+}
+
#zen-welcome-start {
flex-direction: column;
-moz-window-dragging: drag;
- --zen-primary-color: light-dark(black, white);
+ color: white;
#zen-welcome-start-button {
opacity: 0;
list-style-image: url(chrome://browser/skin/zen-icons/forward.svg);
- position: absolute;
- bottom: 10%;
padding: 1em !important;
min-width: 100px;
+ background: rgb(62, 115, 62) !important;
+ fill: white !important;
+
+ will-change: transform;
+ transition: scale 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
+
+ &:hover {
+ scale: 1.03;
+ filter: brightness(1.1);
+ }
+
+ &:active {
+ scale: 1;
+ transform: none;
+ }
}
#zen-welcome-title {
text-align: center;
- font-size: 5rem;
- line-height: 1.1;
+ font-size: 5.2rem;
+ font-weight: 700;
+ line-height: 1.05;
+ letter-spacing: -0.03em;
max-width: 50%;
- font-weight: 500;
white-space: nowrap;
+ /* Layered (transformed) glyphs are drawn with grayscale AA on macOS;
+ * use it for the whole title so animating characters don't look bolder. */
+ -moz-osx-font-smoothing: grayscale;
+ transform: translateY(-5%);
& > span {
display: block;
+ }
+
+ .zen-welcome-char {
+ display: inline-block;
opacity: 0;
+ will-change: transform;
}
}
}
@@ -57,28 +151,18 @@
#zen-welcome-pages {
-moz-window-dragging: no-drag;
opacity: 0;
- justify-content: start;
- align-items: start;
display: none;
- background: var(--zen-branding-bg);
- border-radius: 1em;
+ justify-content: start;
+ align-items: stretch;
+ background: var(--zen-welcome-content-bg);
+ color: light-dark(#111111, #ffffff);
+ border-radius: 1.4rem;
position: relative;
- width: 60%;
- height: 60%;
- box-shadow: var(--zen-big-shadow);
+ width: 86%;
+ height: 82%;
+ box-shadow: 0 3px 8px rgba(0,0,0,.05);
overflow: hidden;
- /* Small screens */
- @media (max-width: 1400px) {
- width: 80%;
- height: 80%;
- }
-
- @media (max-width: 1200px) {
- width: 90%;
- height: 90%;
- }
-
@media (max-width: 1000px) {
width: 100%;
height: 100%;
@@ -86,218 +170,338 @@
}
#zen-welcome-page-sidebar {
+ position: relative;
+ z-index: 1;
+ display: flex;
flex-direction: column;
- justify-content: space-between;
- padding: 3.8rem;
- width: 40%;
+ width: 38%;
height: 100%;
- overflow-x: hidden;
- overflow-y: auto;
+ padding: 4rem 3.8rem 3.6rem;
+ box-sizing: border-box;
+ overflow: hidden;
+ background: var(--zen-welcome-sidebar-bg);
+ box-shadow: 4px 0 24px rgba(0, 0, 0, 0.05);
+ }
- & #zen-welcome-heart {
- width: 100%;
- height: 100%;
- opacity: 0;
- color: currentColor;
+ #zen-welcome-back {
+ align-self: flex-start;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin: 0 0 2.2rem;
+ padding: 0;
+ appearance: none;
+ border: none;
+ background: none;
+ font: inherit;
+ font-size: 1.1rem;
+ font-weight: 600;
+ color: var(--zen-welcome-accent);
+
+ &::before {
+ content: "";
+ width: 16px;
+ height: 16px;
+ background: url(chrome://browser/skin/zen-icons/back.svg) center / contain
+ no-repeat;
+ -moz-context-properties: fill;
fill: currentColor;
- -moz-context-properties: fill, fill-opacity;
- background-image: url(chrome://browser/skin/zen-icons/essential-add.svg);
- background-size: 15%;
- background-repeat: no-repeat;
- background-position: center;
}
- &[animate-heart] {
- overflow: hidden;
+ &[disabled] {
+ visibility: hidden;
+ }
+ }
+
+ #zen-welcome-page-sidebar-content {
+ flex: 1;
+ position: relative;
+ }
+
+ .zen-welcome-text {
+ position: absolute;
+ inset: 0;
+
+ & > * {
+ opacity: 0;
+ will-change: transform, opacity;
+ }
+
+ & h1 {
+ margin: 0 0 2.4rem;
+ font-size: 3rem;
+ font-weight: 700;
+ line-height: 1.1;
+ letter-spacing: -0.02em;
+ }
+
+ & p {
+ margin: 0 0 1.2rem;
+ font-size: 1.25rem;
+ line-height: 1.6;
+ color: var(--zen-welcome-text-secondary);
}
}
#zen-welcome-page-sidebar-buttons {
+ display: flex;
flex-direction: column;
- gap: 10px;
- }
-
- #zen-welcome-page-sidebar-content {
- & h1 {
- font-size: xx-large;
- font-weight: 600;
- margin-bottom: 1rem;
- }
-
- & p {
- margin: 0 0 1.1rem 0;
- color: light-dark(rgba(0, 0, 0, 0.6), rgba(255, 255, 255, 0.6));
- }
-
- & > * {
- transform: translate(300%);
- }
- }
-
- & button {
- justify-content: center;
- align-items: center;
- transform: translate(300%);
+ gap: 1rem;
}
#zen-welcome-page-content {
- background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1));
- width: 60%;
- max-width: 80rem;
- height: 100%;
+ flex: 1;
position: relative;
+ height: 100%;
overflow: hidden;
- justify-content: center;
- align-items: center;
+
+ &::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+ background-image: url(chrome://browser/content/zen-images/grain-bg.png);
+ opacity: 0.3;
+ mix-blend-mode: overlay;
+ pointer-events: none;
+ }
+ }
+
+ .zen-welcome-page {
+ position: absolute;
+ inset: 0;
+ opacity: 0;
display: flex;
flex-direction: column;
- gap: 1.6rem;
+ justify-content: center;
+ align-items: center;
+ gap: 2.5rem;
+ padding: 3rem 4rem;
+ box-sizing: border-box;
- &[select-engine="true"] {
- display: grid;
- grid-template-columns: 1fr 1fr;
- box-sizing: border-box;
- padding: 5rem;
+ &[exiting] {
+ pointer-events: none;
}
- & > label {
- opacity: 0;
- transition:
- scale 0.1s,
- box-shadow 0.1s;
- padding: 1.5rem 1.1rem;
- border-radius: 0.6rem;
- width: 50%;
- gap: 0.8rem;
- display: flex;
- border: 2px solid var(--zen-colors-border);
- background: light-dark(rgba(255, 255, 255, 0.7), rgba(0, 0, 0, 0.4));
- align-items: center;
+ &[page="essentials"] {
+ justify-content: flex-end;
+ align-items: flex-end;
+ padding: 3rem 0 0 6rem;
+ }
- &:hover {
- box-shadow: var(--zen-big-shadow);
+ &[page="search"] {
+ justify-content: safe center;
+ overflow-y: auto;
+ padding-top: 6rem;
+ padding-bottom: 6rem;
+
+ mask-image: linear-gradient(
+ transparent,
+ black 6rem,
+ black calc(100% - 6rem),
+ transparent
+ );
+
+ .zen-welcome-option {
+ max-width: 24rem;
+ min-height: 7.5rem;
+ padding: 0 0 0 1.8rem;
+ overflow: hidden;
+ font-weight: 600;
}
- /* stylelint-disable-next-line stylelint-plugin-mozilla/no-has-selector */
- &:has(:checked) {
- box-shadow: var(--zen-big-shadow);
- border: 2px solid var(--zen-primary-color);
- scale: 1.03;
+ .engine-icon-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ align-self: stretch;
+ flex-shrink: 0;
+ width: 34%;
+ margin-left: auto;
+ overflow: hidden;
+ background: var(--zen-welcome-tile-bg);
}
- }
- &[select-engine="true"] > label {
- flex-direction: column;
- font-weight: 600;
- aspect-ratio: 1 / 0.7;
- align-items: center;
- width: 60%;
- justify-self: center;
- align-self: center;
- justify-content: space-around;
- }
-
- /* 3 or more engines */
- /* stylelint-disable-next-line stylelint-plugin-mozilla/no-has-selector */
- &[select-engine="true"]:has(label:nth-of-type(3)) > label {
- height: 50%;
- }
-
- #zen-welcome-workspace-colors-anchor {
- width: 1px;
- height: 1px;
- }
-
- #zen-welcome-initial-essentials-browser {
- min-width: 70%;
- min-height: 80%;
- display: flex;
- margin-left: auto;
- margin-top: auto;
- border-top-left-radius: 1.2em;
- box-shadow: var(--zen-big-shadow);
- background: light-dark(rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.2));
- padding-right: 20%;
- overflow: hidden;
- opacity: 0;
- border: 1px solid var(--zen-colors-border);
- border-bottom-width: 0;
- border-right-width: 0;
-
- #zen-welcome-initial-essentials-browser-sidebar {
+ .engine-icon-backdrop {
+ position: absolute;
+ inset: 0;
width: 100%;
- padding: 1.4rem;
- gap: 1.2rem;
- background: light-dark(rgba(255, 255, 255, 0.7), rgba(0, 0, 0, 0.4));
+ height: 100%;
+ object-fit: cover;
+ scale: 3;
+ filter: blur(30px) saturate(1.8);
+ opacity: 0.2;
+ }
- #zen-welcome-initial-essentials-browser-sidebar-win-buttons {
- gap: 0.5rem;
- align-items: center;
+ .engine-icon {
+ position: relative;
+ width: 26px;
+ height: 26px;
+ border-radius: 10px;
+ }
- & > div {
- width: 15px;
- height: 15px;
- border-radius: 50%;
- background: var(--zen-toolbar-element-bg);
- }
- }
+ .zen-welcome-option-label {
+ order: -1;
+ padding-right: 1rem;
+ }
- #zen-welcome-initial-essentials-browser-sidebar-essentials {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 1rem;
- visibility: visible;
+ .zen-welcome-option-radio {
+ order: -2;
+ }
+ }
+ }
- & * {
- visibility: visible;
- }
+ .zen-welcome-option {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ width: 100%;
+ max-width: 30rem;
+ padding: 1.8rem 1.9rem;
+ font-size: 1.3rem;
+ box-sizing: border-box;
+ border-radius: 20px;
+ border: 1px solid var(--zen-welcome-border);
+ background: var(--zen-welcome-surface-bg);
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
+ cursor: pointer;
+ will-change: transform;
+ transition: scale 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
- & .extra-tab {
- width: 100%;
- height: 4rem;
- border-radius: var(--border-radius-large);
- margin-top: 0.5rem;
- background: var(--zen-toolbar-element-bg);
- grid-column: span 3;
- }
+ &:hover {
+ scale: 1.03;
+ }
- & .tabbrowser-tab {
- --tab-min-height: 6rem !important;
- min-width: 5rem !important;
- transition: transform 0.1s;
- position: relative;
+ & input {
+ display: none;
+ }
- &::after {
- position: absolute;
- content: "";
- width: 1.8rem;
- height: 1.8rem;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: var(--zen-essential-tab-icon);
- background-position: center;
- background-size: contain;
- background-repeat: no-repeat;
- border-radius: 4px;
- }
+ & strong {
+ font-weight: 700;
+ }
- --border-radius-medium: 1rem;
+ & .engine-icon {
+ width: 28px;
+ height: 28px;
+ border-radius: 6px;
+ }
- &[visuallyselected] {
- transform: scale(1.06);
- }
+ .zen-welcome-option-radio {
+ position: relative;
+ flex-shrink: 0;
+ width: 22px;
+ height: 22px;
+ box-sizing: border-box;
+ border-radius: 50%;
+ border: 1px solid
+ light-dark(rgba(0, 0, 0, 0.45), rgba(255, 255, 255, 0.5));
- & .tab-background {
- margin: 0 !important;
- }
+ &::after {
+ content: "";
+ position: absolute;
+ inset: 3px;
+ border-radius: 50%;
+ background: currentColor;
+ opacity: 0;
+ }
+ }
- & .tab-background::after {
- filter: blur(30px) brightness(1.2);
- }
- }
+ /* stylelint-disable-next-line stylelint-plugin-mozilla/no-has-selector */
+ &:has(:checked) {
+ scale: 1.03;
+ border-color: var(--zen-welcome-accent);
+ color: var(--zen-welcome-accent);
+
+ .zen-welcome-option-radio {
+ border-color: currentColor;
+
+ &::after {
+ opacity: 1;
}
}
}
}
+
+ #zen-welcome-workspace-colors-anchor {
+ width: 1px;
+ height: 1px;
+ }
+
+ .zen-welcome-mock-browser {
+ width: 92%;
+ height: 100%;
+ border-top-left-radius: 2.4rem;
+ background: light-dark(#e3e3e5, #232326);
+ box-shadow: 0 8px 40px rgba(0, 0, 0, 0.12);
+ overflow: hidden;
+
+ .zen-welcome-mock-browser-sidebar {
+ display: flex;
+ flex-direction: column;
+ gap: 1.6rem;
+ width: 80%;
+ height: 100%;
+ padding: 2.6rem 2.4rem;
+ box-sizing: border-box;
+ background: var(--zen-welcome-surface-bg);
+ }
+
+ .zen-welcome-mock-browser-dots {
+ display: flex;
+ gap: 1rem;
+ margin-bottom: 0.6rem;
+
+ & > div {
+ width: 22px;
+ height: 22px;
+ border-radius: 50%;
+ background: var(--zen-welcome-tile-bg);
+ }
+ }
+
+ #zen-welcome-essentials {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1.5rem;
+ }
+
+ .zen-welcome-essential {
+ appearance: none;
+ aspect-ratio: 1;
+ padding: 0;
+ border-radius: 2rem;
+ border: 3px solid transparent;
+ background: var(--zen-welcome-tile-bg) var(--zen-welcome-app-icon)
+ center / 35% no-repeat;
+ cursor: pointer;
+ will-change: transform;
+ transition: scale 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
+
+ &:hover,
+ &[selected] {
+ scale: 1.04;
+ }
+
+ &[selected] {
+ background-color: color-mix(
+ in srgb,
+ var(--zen-welcome-app-color) 22%,
+ var(--zen-welcome-surface-bg)
+ );
+ border-color: color-mix(
+ in srgb,
+ var(--zen-welcome-app-color) 60%,
+ transparent
+ );
+ }
+ }
+
+ .zen-welcome-mock-tab {
+ flex-shrink: 0;
+ height: 4.5rem;
+ border-radius: 1.6rem;
+ background: var(--zen-welcome-tile-bg);
+ }
+ }
}