diff --git a/src/browser/base/content/zen-panels/gradient-generator.inc b/src/browser/base/content/zen-panels/gradient-generator.inc
index 95f77f240..d4d3b1449 100644
--- a/src/browser/base/content/zen-panels/gradient-generator.inc
+++ b/src/browser/base/content/zen-panels/gradient-generator.inc
@@ -100,7 +100,7 @@
-
+
diff --git a/src/zen/common/styles/zen-browser-ui.css b/src/zen/common/styles/zen-browser-ui.css
index 202ed322e..26af382a4 100644
--- a/src/zen/common/styles/zen-browser-ui.css
+++ b/src/zen/common/styles/zen-browser-ui.css
@@ -111,9 +111,7 @@
}
@media (-moz-platform: macos) {
- #zen-main-app-wrapper,
- #zen-appcontent-wrapper,
- #zen-sidebar-splitter {
+ #zen-main-app-wrapper {
appearance: -moz-sidebar !important;
}
}
@@ -286,3 +284,17 @@
border-radius: 2px;
margin: 10px 2px 0 0;
}
+
+#zen-sidebar-splitter {
+ border-radius: 14px;
+ transition:
+ background 0.2s ease-in-out,
+ opacity 0.2s ease-in-out;
+
+ &:hover {
+ background: var(--zen-primary-color);
+ transition: background 0.2s ease-in-out;
+ transition-delay: 0.2s;
+ opacity: 1;
+ }
+}
diff --git a/src/zen/common/styles/zen-theme.css b/src/zen/common/styles/zen-theme.css
index ef2a0493a..14b387a28 100644
--- a/src/zen/common/styles/zen-theme.css
+++ b/src/zen/common/styles/zen-theme.css
@@ -117,7 +117,7 @@
--zen-button-padding: 0.6rem 1.2rem;
--zen-toolbar-element-bg: light-dark(
- color-mix(in oklch, var(--toolbox-textcolor) 9%, transparent),
+ color-mix(in oklch, var(--toolbox-textcolor) 8%, transparent),
color-mix(in oklch, var(--toolbox-textcolor) 15%, transparent)
);
diff --git a/src/zen/tabs/zen-tabs/vertical-tabs.css b/src/zen/tabs/zen-tabs/vertical-tabs.css
index 7102aa811..7324f33c1 100644
--- a/src/zen/tabs/zen-tabs/vertical-tabs.css
+++ b/src/zen/tabs/zen-tabs/vertical-tabs.css
@@ -253,6 +253,8 @@
overflow-x: clip; /* Clip horizontal overflow */
overflow-clip-margin: var(--zen-toolbox-padding); /* Add margin to clipping area */
+ --focus-outline-color: transparent;
+
@media (-moz-platform: macos) {
font-size: 1.1rem; /* Slightly larger font on macOS */
}
@@ -1489,6 +1491,7 @@
background: transparent;
border: none;
padding: 0;
+ outline: none !important;
}
/* ==========================================================================
@@ -1513,3 +1516,8 @@
width: 100%;
}
}
+
+#tabs-newtab-button:not([in-urlbar='true']) label,
+.zen-current-workspace-indicator-name {
+ opacity: 0.7;
+}
diff --git a/src/zen/workspaces/ZenGradientGenerator.mjs b/src/zen/workspaces/ZenGradientGenerator.mjs
index fea233334..ecb5ff661 100644
--- a/src/zen/workspaces/ZenGradientGenerator.mjs
+++ b/src/zen/workspaces/ZenGradientGenerator.mjs
@@ -40,9 +40,11 @@
return points;
}
- const MAX_OPACITY = 0.8;
+ const MAX_OPACITY = 0.9;
const MIN_OPACITY = 0.3;
+ const EXPLICIT_LIGHTNESS_TYPE = 'explicit-lightness';
+
class nsZenThemePicker extends ZenMultiWindowFeature {
static MAX_DOTS = 3;
@@ -195,12 +197,14 @@
ID: 0,
position: { x, y },
isPrimary: true,
+ type: EXPLICIT_LIGHTNESS_TYPE,
},
];
for (let i = 1; i < numDots; i++) {
dots.push({
ID: i,
position: { x: 0, y: 0 },
+ type: EXPLICIT_LIGHTNESS_TYPE,
});
}
this.useAlgo = algo;
@@ -434,13 +438,16 @@
return { x, y };
}
- getColorFromPosition(x, y) {
+ getColorFromPosition(x, y, type = undefined) {
// Return a color as hsl based on the position in the gradient
const gradient = this.panel.querySelector('.zen-theme-picker-gradient');
const rect = gradient.getBoundingClientRect();
const padding = 20; // each side
- rect.width += padding * 2;
- rect.height += padding * 2;
+ const dotHalfSize = 36 / 2; // half the size of the dot
+ x += dotHalfSize;
+ y += dotHalfSize;
+ rect.width += padding * 2; // Adjust width and height for padding
+ rect.height += padding * 2; // Adjust width and height for padding
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const radius = (rect.width - padding) / 2;
@@ -453,6 +460,11 @@
const normalizedDistance = 1 - Math.min(distance / radius, 1); // Normalize distance to [0, 1]
const hue = (angle / 360) * 360; // Normalize angle to [0, 360)
const saturation = normalizedDistance * 100; // Scale distance to [0, 100]
+ if (type !== EXPLICIT_LIGHTNESS_TYPE) {
+ // Set the current lightness to how far we are from the center of the circle
+ // For example, moving the dot outside will have higher lightness, while moving it inside will have lower lightness
+ this.#currentLightness = Math.round((1 - normalizedDistance) * 100);
+ }
const lightness = this.#currentLightness; // Fixed lightness for simplicity
const [r, g, b] = this.hslToRgb(hue / 360, saturation / 100, lightness / 100);
return [
@@ -498,11 +510,14 @@
dot.style.setProperty('--zen-theme-picker-dot-color', `rgb(${r}, ${g}, ${b})`);
dot.setAttribute('data-position', this.getJSONPos(x, y));
+ dot.setAttribute('data-type', color.type);
this.dots.push({
ID: id,
element: dot,
position: { x: null, y: null }, // at some point possition should instead be stored as percentege just so that the size of the color picker does not matter.
+ type: color.type,
+ lightness: color.lightness,
});
}
if (!fromWorkspace) {
@@ -566,14 +581,18 @@
}
}
- spawnDot(relativePosition, primary = false) {
+ spawnDot(dotData, primary = false) {
const dotPad = this.panel.querySelector('.zen-theme-picker-gradient');
+ const relativePosition = {
+ x: dotData.x,
+ y: dotData.y,
+ };
const dot = document.createElement('div');
dot.classList.add('zen-theme-picker-dot');
- dot.style.left = `${relativePosition.x}px`;
- dot.style.top = `${relativePosition.y}px`;
+ dot.style.left = `${dotData.x}px`;
+ dot.style.top = `${dotData.y}px`;
dotPad.appendChild(dot);
@@ -590,18 +609,24 @@
}
}
- const colorFromPos = this.getColorFromPosition(relativePosition.x, relativePosition.y);
+ const colorFromPos = this.getColorFromPosition(
+ relativePosition.x,
+ relativePosition.y,
+ dotData.type
+ );
dot.style.setProperty(
'--zen-theme-picker-dot-color',
`rgb(${colorFromPos[0]}, ${colorFromPos[1]}, ${colorFromPos[2]})`
);
dot.setAttribute('data-position', this.getJSONPos(relativePosition.x, relativePosition.y));
+ dot.setAttribute('data-type', dotData.type);
this.dots.push({
ID: id,
element: dot,
position: { x: relativePosition.x, y: relativePosition.y },
lightness: this.#currentLightness,
+ type: dotData.type,
});
}
@@ -685,9 +710,14 @@
let distance = getDistanceFromCenter(primaryDot.position, centerPosition);
const radius = (rect.width - padding) / 2;
if (distance > radius) distance = radius;
-
if (this.dots.length > 0) {
- updatedDots = [{ ID: 0, position: primaryDot.position }];
+ updatedDots = [
+ {
+ ID: 0,
+ position: primaryDot.position,
+ type: primaryDot.type,
+ },
+ ];
}
harmonyAngles.angles.forEach((angleOffset, index) => {
@@ -699,7 +729,11 @@
y: centerPosition.y + distance * Math.sin(radian),
};
- updatedDots.push({ ID: index + 1, position: newPosition });
+ updatedDots.push({
+ ID: index + 1,
+ position: newPosition,
+ type: primaryDot.type,
+ });
});
return updatedDots;
@@ -707,38 +741,24 @@
handleColorPositions(colorPositions, ignoreLegacy = false) {
colorPositions.sort((a, b) => a.ID - b.ID);
- const existingPrimaryDot = this.dots.find((d) => d.ID === 0);
if (this.isLegacyVersion && !ignoreLegacy) {
this.isLegacyVersion = false;
Services.prefs.setIntPref('zen.theme.gradient-legacy-version', 1);
}
- if (existingPrimaryDot) {
- existingPrimaryDot.element.style.zIndex = 999;
- const colorFromPos = this.getColorFromPosition(
- existingPrimaryDot.position.x,
- existingPrimaryDot.position.y
- );
- existingPrimaryDot.element.style.setProperty(
- '--zen-theme-picker-dot-color',
- `rgb(${colorFromPos[0]}, ${colorFromPos[1]}, ${colorFromPos[2]})`
- );
- existingPrimaryDot.element.setAttribute(
- 'data-position',
- this.getJSONPos(existingPrimaryDot.position.x, existingPrimaryDot.position.y)
- );
- }
-
colorPositions.forEach((dotPosition) => {
const existingDot = this.dots.find((dot) => dot.ID === dotPosition.ID);
if (existingDot) {
+ existingDot.type = dotPosition.type;
existingDot.position = dotPosition.position;
const colorFromPos = this.getColorFromPosition(
dotPosition.position.x,
- dotPosition.position.y
+ dotPosition.position.y,
+ dotPosition.type
);
+ existingDot.lightness = this.#currentLightness;
existingDot.element.style.setProperty(
'--zen-theme-picker-dot-color',
`rgb(${colorFromPos[0]}, ${colorFromPos[1]}, ${colorFromPos[2]})`
@@ -747,6 +767,7 @@
'data-position',
this.getJSONPos(dotPosition.position.x, dotPosition.position.y)
);
+ existingDot.element.setAttribute('data-type', dotPosition.type);
if (!this.dragging) {
gZenUIManager.motion.animate(
@@ -766,7 +787,10 @@
existingDot.element.style.top = `${dotPosition.position.y}px`;
}
} else {
- this.spawnDot(dotPosition.position);
+ this.spawnDot({
+ type: dotPosition.type,
+ ...dotPosition.position,
+ });
}
});
}
@@ -849,7 +873,6 @@
const relativeY = pixelY - rect.top;
if (!clickedDot && this.dots.length < 1) {
- this.#currentLightness = 50;
this.spawnDot({ x: relativeX, y: relativeY }, this.dots.length === 0);
this.updateCurrentWorkspace(true);
@@ -983,23 +1006,7 @@
}
themedColors(colors) {
- const colorToBlend = this.isDarkMode ? [255, 255, 255] : [0, 0, 0]; // Default to white for dark mode, black otherwise
- const opacity = this.currentOpacity;
- // Convert opacity into a percentage where the lowest is 60% and the highest is 100%
- // The more transparent, the more white the color will be blended with. In order words,
- // make the transparency relative to these 2 ends.
- // e.g. 0% opacity becomes 60% blend, 100% opacity becomes 100% blend
- let blendPercentage = Math.max(30, 30 + opacity * 70);
- if (this.isLegacyVersion) {
- blendPercentage = 100; // Legacy version always blends to 100%
- }
- return colors.map((color) => ({
- c: color.isCustom ? color.c : this.blendColors(color.c, colorToBlend, blendPercentage),
- isCustom: color.isCustom,
- algorithm: color.algorithm,
- lightness: color.lightness,
- position: color.position,
- }));
+ return [...colors];
}
onOpacityChange(event) {
@@ -1028,6 +1035,20 @@
).matches;
}
+ blendWithWhiteOverlay(baseColor, opacity) {
+ const blendColor = [255, 255, 255];
+ const blendAlpha = 0.2;
+ const baseAlpha = baseColor[3] !== undefined ? baseColor[3] : 1;
+ const blended = [];
+
+ for (let i = 0; i < 3; i++) {
+ blended[i] = Math.round(blendColor[i] * (1 - opacity) + baseColor[i] * opacity);
+ }
+
+ const blendedAlpha = +(blendAlpha * (1 - opacity) + baseAlpha * opacity).toFixed(3);
+ return `rgba(${blended[0]}, ${blended[1]}, ${blended[2]}, ${blendedAlpha})`;
+ }
+
getSingleRGBColor(color, forToolbar = false) {
if (color.isCustom) {
return color.c;
@@ -1043,7 +1064,7 @@
} else {
color = color.c;
}
- return `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${opacity})`;
+ return this.blendWithWhiteOverlay(color, opacity);
}
luminance([r, g, b]) {
@@ -1067,31 +1088,10 @@
];
}
- findOptimalBlend(dominantColor, blendTarget, minContrast = 4.5) {
- let low = 0;
- let high = 100;
- let bestMatch = null;
-
- for (let i = 0; i < 10; i++) {
- const mid = (low + high) / 2;
- const blended = this.blendColors(dominantColor, blendTarget, mid);
- const contrast = this.contrastRatio(blended, blendTarget);
-
- if (contrast >= minContrast) {
- bestMatch = blended;
- high = mid;
- } else {
- low = mid;
- }
- }
-
- return bestMatch || this.blendColors(dominantColor, blendTarget, 10); // fallback
- }
-
getGradient(colors, forToolbar = false) {
const themedColors = this.themedColors(colors);
this.useAlgo = themedColors[0]?.algorithm ?? '';
- this.#currentLightness = themedColors[0]?.lightness ?? 70;
+ this.#currentLightness = themedColors[0]?.lightness ?? 50;
const rotation = -45; // TODO: Detect rotation based on the accent color
if (themedColors.length === 0) {
@@ -1118,8 +1118,10 @@
if (!forToolbar) {
return [
`linear-gradient(${rotation}deg, ${this.getSingleRGBColor(themedColors[1], forToolbar)} 0%, transparent 100%)`,
- `linear-gradient(${rotation + 180}deg, ${this.getSingleRGBColor(themedColors[0], forToolbar)} 0%, transparent 100%)`,
- ].join(', ');
+ `linear-gradient(${rotation + 180}deg, ${this.getSingleRGBColor(themedColors[0], forToolbar)} 0%, transparent 80%)`,
+ ]
+ .reverse()
+ .join(', ');
}
return `linear-gradient(${rotation}deg, ${this.getSingleRGBColor(themedColors[1], forToolbar)} 0%, ${this.getSingleRGBColor(themedColors[0], forToolbar)} 100%)`;
} else if (themedColors.length === 3) {
@@ -1140,9 +1142,7 @@
}
shouldBeDarkMode(accentColor) {
- let minimalLum = 0.6;
if (!this.canBeTransparent) {
- // Blend the color with the toolbar background
const toolbarBg = this.getToolbarModifiedBaseRaw();
accentColor = this.blendColors(
toolbarBg.slice(0, 3),
@@ -1150,9 +1150,23 @@
(1 - this.currentOpacity) * 100
);
}
- const lum = this.luminance(accentColor);
- // Return true if background is dark enough that white text is preferred
- return lum < minimalLum;
+
+ const bg = accentColor;
+
+ // Get text colors (with alpha)
+ let darkText = this.getToolbarColor(true); // e.g. [r, g, b, a]
+ let lightText = this.getToolbarColor(false); // e.g. [r, g, b, a]
+
+ lightText[3] -= 0.4; // Reduce alpha for light text
+
+ // Composite text color over background
+ darkText = this.blendColors(bg, darkText.slice(0, 3), (1 - darkText[3]) * 100);
+ lightText = this.blendColors(bg, lightText.slice(0, 3), (1 - lightText[3]) * 100);
+
+ const darkContrast = this.contrastRatio(bg, darkText);
+ const lightContrast = this.contrastRatio(bg, lightText);
+
+ return darkContrast > lightContrast;
}
static getTheme(colors = [], opacity = 0.5, texture = 0) {
@@ -1285,6 +1299,10 @@
return color;
}
+ getToolbarColor(isDarkMode = false) {
+ return isDarkMode ? [255, 255, 255, 0.8] : [0, 0, 0, 0.8]; // Default toolbar
+ }
+
async onWorkspaceChange(workspace, skipUpdate = false, theme = null) {
const uuid = workspace.uuid;
// Use theme from workspace object or passed theme
@@ -1465,13 +1483,18 @@
// Check for the primary color
isDarkMode = browser.gZenThemePicker.shouldBeDarkMode(dominantColor);
browser.document.documentElement.setAttribute('zen-should-be-dark-mode', isDarkMode);
+ browser.gZenThemePicker.panel.removeAttribute('invalidate-controls');
} else {
browser.document.documentElement.removeAttribute('zen-should-be-dark-mode');
+ if (!this.isLegacyVersion) {
+ browser.gZenThemePicker.panel.setAttribute('invalidate-controls', 'true');
+ }
}
// Set `--toolbox-textcolor` to have a contrast with the primary color
+ const textColor = this.getToolbarColor(isDarkMode);
document.documentElement.style.setProperty(
'--toolbox-textcolor',
- isDarkMode ? 'rgba(255, 255, 255, 0.8)' : 'rgba(0, 0, 0, 0.8)'
+ `rgba(${textColor[0]}, ${textColor[1]}, ${textColor[2]}, ${textColor[3]})`
);
}
@@ -1549,6 +1572,7 @@
const algorithm = this.useAlgo;
const position =
dot.getAttribute('data-position') && JSON.parse(dot.getAttribute('data-position'));
+ const type = dot.getAttribute('data-type');
return {
c: isCustom ? color : color.match(/\d+/g).map(Number),
isCustom,
@@ -1556,6 +1580,7 @@
isPrimary,
lightness: this.#currentLightness,
position,
+ type,
};
});
const gradient = nsZenThemePicker.getTheme(colors, this.currentOpacity, this.currentTexture);
diff --git a/src/zen/workspaces/zen-gradient-generator.css b/src/zen/workspaces/zen-gradient-generator.css
index 1270cd7b0..24864981b 100644
--- a/src/zen/workspaces/zen-gradient-generator.css
+++ b/src/zen/workspaces/zen-gradient-generator.css
@@ -261,6 +261,7 @@
animation: zen-theme-picker-dot-animation 0.5s;
transform: translate(-50%, -50%);
pointer-events: none;
+ transform-origin: center center;
&:first-of-type {
width: 36px;
@@ -269,10 +270,10 @@
z-index: 2;
pointer-events: all;
transition: transform 0.2s;
+ z-index: 999;
&:hover {
transform: scale(1.05) translate(-50%, -50%);
}
- transform-origin: center center;
}
&[dragging='true'] {
@@ -435,7 +436,7 @@
margin: 0 !important;
}
-:root:not([zen-should-be-dark-mode]) {
+#PanelUI-zen-gradient-generator[invalidate-controls='true'] {
#PanelUI-zen-gradient-generator-opacity {
display: none !important;
}