Skip to content

Instantly share code, notes, and snippets.

@greggman
Created July 14, 2026 18:01
Show Gist options
  • Select an option

  • Save greggman/62124f8802f7d7849642253ea4a8ac32 to your computer and use it in GitHub Desktop.

Select an option

Save greggman/62124f8802f7d7849642253ea4a8ac32 to your computer and use it in GitHub Desktop.
Browser Color Theme Generator
:root {
--font-ui: 'Outfit', sans-serif;
--font-mockup: 'Inter', sans-serif;
--font-code: 'Fira Code', monospace;
--transition-fast: 0.15s cubic-bezier(0.4, 0, 0.2, 1);
--transition-normal: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
--transition-slow: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
--sidebar-width: 360px;
--border-radius-sm: 8px;
--border-radius-md: 12px;
--border-radius-lg: 16px;
}
.dark-app {
--color-app-bg: #0D0F13;
--color-sidebar-bg: #14171E;
--color-surface: #1B1E26;
--color-surface-elevated: #242A35;
--color-text-primary: #F8F9FA;
--color-text-secondary: #94A3B8;
--color-border: #262C38;
--color-dot: rgba(255, 255, 255, 0.04);
--glass-bg: rgba(27, 30, 38, 0.65);
--glass-border: rgba(255, 255, 255, 0.04);
--color-mock-static-bg: #282C34;
--color-mock-static-border: #3A404B;
--color-mock-static-text: #ABB2BF;
}
.light-app {
--color-app-bg: #F4F6F9;
--color-sidebar-bg: #FFFFFF;
--color-surface: #FAFBFD;
--color-surface-elevated: #FFFFFF;
--color-text-primary: #1E293B;
--color-text-secondary: #64748B;
--color-border: #E2E8F0;
--color-dot: rgba(0, 0, 0, 0.04);
--glass-bg: rgba(255, 255, 255, 0.7);
--glass-border: rgba(0, 0, 0, 0.04);
--color-mock-static-bg: #FFFFFF;
--color-mock-static-border: #DFE1E5;
--color-mock-static-text: #5F6368;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-ui);
background-color: var(--color-app-bg);
color: var(--color-text-primary);
width: 100vw;
height: 100vh;
overflow: hidden;
display: flex;
transition: background-color var(--transition-slow), color var(--transition-slow);
}
#app {
display: flex;
width: 100%;
height: 100%;
}
/* Sidebar Styling */
.sidebar {
width: var(--sidebar-width);
height: 100%;
background-color: var(--color-sidebar-bg);
border-right: 1px solid var(--color-border);
display: flex;
flex-direction: column;
z-index: 10;
flex-shrink: 0;
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.2);
transition: background-color var(--transition-slow), border-color var(--transition-slow);
}
.app-header {
padding: 20px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--color-border);
}
.logo-group {
display: flex;
align-items: center;
gap: 12px;
}
.logo-group h1 {
font-size: 1.2rem;
font-weight: 700;
letter-spacing: -0.5px;
background: linear-gradient(135deg, var(--color-accent, #6366f1), #FF007F);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
transition: all var(--transition-normal);
}
.version-tag {
font-size: 0.65rem;
font-weight: 600;
background-color: var(--color-surface);
color: var(--color-text-secondary);
padding: 4px 8px;
border-radius: 12px;
border: 1px solid var(--color-border);
}
.control-scroll {
flex: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
}
.control-scroll::-webkit-scrollbar {
width: 6px;
}
.control-scroll::-webkit-scrollbar-track {
background: transparent;
}
.control-scroll::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: 3px;
}
.section {
background: var(--glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-radius: var(--border-radius-md);
padding: 16px;
transition: all var(--transition-normal);
}
.section h2 {
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--color-text-secondary);
margin-bottom: 12px;
}
.section-desc {
font-size: 0.75rem;
color: var(--color-text-secondary);
margin-top: 8px;
line-height: 1.4;
}
.preset-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
margin-bottom: 16px;
}
.preset-btn {
height: 34px;
border-radius: var(--border-radius-sm);
border: 2px solid transparent;
cursor: pointer;
transition: transform var(--transition-fast), border-color var(--transition-fast);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.preset-btn:hover {
transform: translateY(-2px) scale(1.05);
}
.preset-btn.active {
border-color: var(--color-text-primary);
box-shadow: 0 0 0 2px var(--color-sidebar-bg), 0 0 0 4px var(--color-accent);
transform: scale(1);
}
.custom-color-row {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 12px;
border-top: 1px solid var(--color-border);
}
.custom-color-row label {
font-size: 0.85rem;
font-weight: 500;
}
.color-picker-wrapper {
display: flex;
align-items: center;
gap: 8px;
background: var(--color-surface-elevated);
padding: 4px 10px;
border-radius: var(--border-radius-sm);
border: 1px solid var(--color-border);
}
.color-picker-wrapper input[type="color"] {
-webkit-appearance: none;
border: none;
width: 24px;
height: 24px;
border-radius: 6px;
cursor: pointer;
background: none;
}
.color-picker-wrapper input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
.color-picker-wrapper input[type="color"]::-webkit-color-swatch {
border: none;
border-radius: 6px;
}
.color-picker-wrapper span {
font-size: 0.8rem;
font-family: var(--font-code);
font-weight: 500;
color: var(--color-text-primary);
}
.segmented-control {
display: flex;
background-color: var(--color-surface-elevated);
border-radius: var(--border-radius-sm);
padding: 3px;
border: 1px solid var(--color-border);
}
.seg-btn {
flex: 1;
background: none;
border: none;
padding: 6px 4px;
font-family: var(--font-ui);
font-size: 0.75rem;
font-weight: 500;
color: var(--color-text-secondary);
border-radius: 6px;
cursor: pointer;
transition: all var(--transition-fast);
}
.seg-btn:hover {
color: var(--color-text-primary);
}
.seg-btn.active {
background-color: var(--color-surface);
color: var(--color-text-primary);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
font-weight: 600;
}
.visualizer-content {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
#colorWheelCanvas {
background: none;
flex-shrink: 0;
}
.readout-card {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.contrast-badge {
background-color: var(--color-accent, #6366f1);
color: #FFFFFF;
padding: 6px 12px;
border-radius: 20px;
font-size: 0.7rem;
font-weight: 600;
text-align: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transition: background-color var(--transition-fast), color var(--transition-fast);
}
.contrast-sub {
font-size: 0.7rem;
color: var(--color-text-secondary);
line-height: 1.4;
}
.export-desc {
font-size: 0.75rem;
color: var(--color-text-secondary);
line-height: 1.4;
margin-bottom: 12px;
}
.btn-primary {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
background: linear-gradient(135deg, var(--color-accent, #6366f1), #FF007F);
color: #FFFFFF;
border: none;
width: 100%;
padding: 12px;
border-radius: var(--border-radius-sm);
font-family: var(--font-ui);
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: transform var(--transition-fast), box-shadow var(--transition-fast);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
}
.btn-primary:active {
transform: translateY(0);
}
.guide-box {
margin-top: 16px;
background-color: var(--color-surface);
border-radius: var(--border-radius-sm);
padding: 12px;
border: 1px solid var(--color-border);
}
.guide-box h4 {
font-size: 0.75rem;
font-weight: 600;
color: var(--color-text-primary);
margin-bottom: 6px;
}
.guide-box ol {
font-size: 0.7rem;
color: var(--color-text-secondary);
padding-left: 16px;
display: flex;
flex-direction: column;
gap: 4px;
}
.guide-box li strong {
color: var(--color-text-primary);
}
/* Main Preview Area Styling */
.preview-area {
flex: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--color-app-bg);
background-image: radial-gradient(var(--color-dot) 1px, transparent 1px);
background-size: 24px 24px;
padding: 40px;
transition: background-color var(--transition-slow);
}
.preview-wrapper {
width: 100%;
max-width: 960px;
display: flex;
flex-direction: column;
gap: 12px;
}
.mockup-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.mockup-header h3 {
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--color-text-secondary);
}
.mockup-controls {
display: flex;
gap: 6px;
}
.mockup-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: var(--color-surface);
border: 1px solid var(--color-border);
}
/* Chrome Mockup Drawing (Strict Native Mappings) */
.chrome-mockup {
width: 100%;
border-radius: var(--border-radius-lg);
overflow: hidden;
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.4);
font-family: var(--font-mockup);
display: flex;
flex-direction: column;
transition: box-shadow var(--transition-normal);
}
.chrome-frame {
display: flex;
flex-direction: column;
padding-top: 8px;
transition: background-color var(--transition-normal);
}
.chrome-tabs-row {
display: flex;
align-items: flex-end;
padding-left: 12px;
height: 36px;
gap: 2px;
}
.chrome-tab {
padding: 6px 14px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
font-size: 0.72rem;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
height: 28px;
cursor: default;
position: relative;
transition: background-color var(--transition-normal), color var(--transition-normal);
}
.chrome-tab.active {
z-index: 2;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
}
.chrome-tab.inactive {
z-index: 1;
opacity: 0.9;
}
.icon-tab {
opacity: 0.8;
flex-shrink: 0;
}
.icon-close {
opacity: 0.5;
margin-left: 4px;
flex-shrink: 0;
}
.chrome-newtab-plus {
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-left: 6px;
margin-bottom: 2px;
opacity: 0.7;
cursor: pointer;
transition: background-color var(--transition-fast), color var(--transition-normal);
}
.chrome-newtab-plus:hover {
background-color: rgba(255, 255, 255, 0.08);
}
.chrome-toolbar {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 14px;
z-index: 3;
position: relative;
transition: background-color var(--transition-normal);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
}
.chrome-nav-buttons {
display: flex;
align-items: center;
gap: 10px;
}
.chrome-nav-buttons svg {
opacity: 0.85;
transition: color var(--transition-normal);
}
.chrome-nav-buttons svg.disabled {
opacity: 0.3;
}
.chrome-omnibox {
flex: 1;
display: flex;
align-items: center;
gap: 8px;
padding: 6px 14px;
border-radius: 30px;
font-size: 0.75rem;
transition: background-color var(--transition-normal), border-color var(--transition-normal), color var(--transition-normal);
}
.chrome-omnibox span {
flex: 1;
letter-spacing: 0.1px;
}
.star-icon {
opacity: 0.7;
}
.chrome-menu-buttons {
opacity: 0.85;
transition: color var(--transition-normal);
}
.chrome-bookmark-bar {
display: flex;
align-items: center;
gap: 16px;
padding: 6px 16px 8px 16px;
font-size: 0.7rem;
font-weight: 500;
transition: background-color var(--transition-normal), color var(--transition-normal);
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.dark-app .chrome-bookmark-bar {
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.chrome-bookmark {
display: flex;
align-items: center;
gap: 6px;
opacity: 0.9;
transition: color var(--transition-normal);
}
/* New Tab Page Area (Strict Mapping) */
.chrome-ntp-area {
flex: 1;
min-height: 420px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 32px;
padding: 40px;
transition: background-color var(--transition-normal);
}
.mock-foogle-logo {
font-size: 4rem;
font-weight: 700;
letter-spacing: -2px;
color: var(--color-mock-static-text);
opacity: 0.4;
user-select: none;
}
.mock-search-bar {
width: 100%;
max-width: 560px;
display: flex;
align-items: center;
gap: 14px;
padding: 12px 22px;
border-radius: 40px;
font-size: 0.82rem;
background-color: var(--color-mock-static-bg);
border: 1px solid var(--color-mock-static-border);
color: var(--color-mock-static-text);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
opacity: 0.7;
user-select: none;
}
.mock-search-bar svg {
color: var(--color-mock-static-text);
opacity: 0.6;
}
.mock-shortcuts {
display: flex;
gap: 32px;
}
.shortcut-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
opacity: 0.65;
}
.shortcut-circle {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.1rem;
font-weight: 600;
background-color: var(--color-mock-static-bg);
border: 1px solid var(--color-mock-static-border);
color: var(--color-mock-static-text);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}
.shortcut-text {
font-size: 0.68rem;
font-weight: 500;
color: var(--color-mock-static-text);
}
.mock-ntp-readout {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
margin-top: 12px;
padding: 12px 24px;
border-radius: var(--border-radius-sm);
background-color: rgba(0, 0, 0, 0.03);
border: 1px dashed rgba(0, 0, 0, 0.08);
transition: background-color var(--transition-normal), border-color var(--transition-normal);
}
.dark-app .mock-ntp-readout {
background-color: rgba(255, 255, 255, 0.02);
border: 1px dashed rgba(255, 255, 255, 0.08);
}
.mock-ntp-text {
font-size: 0.8rem;
font-weight: 500;
transition: color var(--transition-normal);
}
.mock-ntp-link {
font-size: 0.8rem;
font-weight: 600;
text-decoration: none;
transition: color var(--transition-normal);
}
.mock-ntp-link:hover {
text-decoration: underline;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ThemeGen - Browser Theme Generator & Previewer</title>
<link rel="stylesheet" href="index.css">
</head>
<body class="dark-app">
<div id="app">
<!-- Sidebar Control Panel -->
<aside class="sidebar">
<header class="app-header">
<div class="logo-group">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="32" height="32" class="header-logo">
<circle cx="64" cy="64" r="56" fill="url(#headerGrad)" />
<defs>
<linearGradient id="headerGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FF007F" />
<stop offset="100%" stop-color="#00A896" />
</linearGradient>
</defs>
<circle cx="64" cy="64" r="28" fill="#FFFFFF" />
</svg>
<h1>ThemeGen</h1>
</div>
<div class="version-tag">Native Themer</div>
</header>
<div class="control-scroll">
<section class="section preset-section">
<h2>Designer Presets</h2>
<div class="preset-grid" id="presetGrid">
<!-- Populated via JS -->
</div>
<div class="custom-color-row">
<label for="baseColorInput">Custom Base Color</label>
<div class="color-picker-wrapper">
<input type="color" id="baseColorInput" value="#3D5A80">
<span id="baseColorHex">#3D5A80</span>
</div>
</div>
</section>
<section class="section harmony-section">
<h2>Color Theory Harmony</h2>
<div class="segmented-control" id="harmonyControl">
<button data-value="monochromatic" class="seg-btn">Mono</button>
<button data-value="analogous" class="seg-btn">Analogous</button>
<button data-value="complementary" class="seg-btn active">Complementary</button>
<button data-value="triadic" class="seg-btn">Triad</button>
</div>
<p class="section-desc" id="harmonyDesc">Computes pure opposite hues for dynamic and balanced secondary
contrasts.</p>
</section>
<section class="section mode-section">
<h2>UI Mode Target</h2>
<div class="segmented-control" id="modeControl">
<button data-value="light" class="seg-btn">Premium Light</button>
<button data-value="dark" class="seg-btn active">Midnight Dark</button>
</div>
</section>
<section class="section visualizer-section">
<h2>Harmony Analysis</h2>
<div class="visualizer-content">
<canvas id="colorWheelCanvas" width="160" height="160"></canvas>
<div class="readout-card">
<div class="contrast-badge" id="contrastBadge">WCAG AAA Pass</div>
<p class="contrast-sub">Relative luminance calculated to guarantee perfect off-white/charcoal text
readability on your toolbar and frame.</p>
</div>
</div>
</section>
<section class="section export-section">
<h2>Save Theme</h2>
<p class="export-desc">Exports <strong>manifest.json</strong> file
ready to be loaded unpacked into your browser.</p>
<button class="btn-primary" id="btnSave">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
<polyline points="17 21 17 13 7 13 7 21"></polyline>
<polyline points="7 3 7 8 15 8"></polyline>
</svg>
Save manifest.json
</button>
<div class="guide-box">
<h4>How to Install Unpacked Theme:</h4>
<ol>
<li>Put <strong>manifest.json</strong> in a new empty folder.</li>
<li>Go to <strong>chrome://extensions</strong>, enable Developer Mode.</li>
<li>Click <strong>Load Unpacked</strong> and select that folder.</li>
</ol>
</div>
</section>
</div>
</aside>
<!-- Main Live Preview Area -->
<main class="preview-area">
<div class="preview-wrapper">
<div class="mockup-header">
<h3>Live Native Chrome Theme Preview</h3>
<div class="mockup-controls">
<span class="mockup-dot"></span>
<span class="mockup-dot"></span>
<span class="mockup-dot"></span>
</div>
</div>
<!-- Chrome Browser Mockup (Strict Native Mappings) -->
<div class="chrome-mockup">
<div class="chrome-frame">
<div class="chrome-tabs-row">
<!-- Active Tab (Inherits Toolbar background!) -->
<div class="chrome-tab active">
<svg class="icon-tab" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12"
fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
</svg>
<span>New Tab (Active)</span>
<svg class="icon-close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="10" height="10"
fill="none" stroke="currentColor" stroke-width="2.5">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</div>
<!-- Inactive Tab (Inherits Frame background!) -->
<div class="chrome-tab inactive">
<svg class="icon-tab" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12"
fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
</svg>
<span>Foogle (Inactive)</span>
</div>
<div class="chrome-tab inactive">
<svg class="icon-tab" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12"
fill="none" stroke="currentColor" stroke-width="2">
<path
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22">
</path>
</svg>
<span>GitHub (Inactive)</span>
</div>
<div class="chrome-newtab-plus">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none"
stroke="currentColor" stroke-width="2.5">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</div>
</div>
<!-- Toolbar Area (Sets toolbar color!) -->
<div class="chrome-toolbar">
<div class="chrome-nav-buttons">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="none"
stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="none"
stroke="currentColor" stroke-width="2" class="disabled">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="none"
stroke="currentColor" stroke-width="2">
<path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67"></path>
</svg>
</div>
<div class="chrome-omnibox">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none"
stroke="currentColor" stroke-width="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
<span>chrome://newtab</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none"
stroke="currentColor" stroke-width="2" class="star-icon">
<polygon
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2">
</polygon>
</svg>
</div>
<div class="chrome-menu-buttons">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="none"
stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="12" cy="5" r="1"></circle>
<circle cx="12" cy="19" r="1"></circle>
</svg>
</div>
</div>
<!-- Bookmark Bar (Inherits Toolbar background!) -->
<div class="chrome-bookmark-bar">
<div class="chrome-bookmark">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none"
stroke="currentColor" stroke-width="2">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
<span>Work Folder</span>
</div>
<div class="chrome-bookmark">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none"
stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
</svg>
<span>YouTube</span>
</div>
<div class="chrome-bookmark">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none"
stroke="currentColor" stroke-width="2">
<polygon
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2">
</polygon>
</svg>
<span>Favorites</span>
</div>
</div>
<!-- New Tab Page Area (Inherits ntp_background, ntp_text, ntp_link!) -->
<div class="chrome-ntp-area">
<!-- Neutral static Foogle logo representing the actual un-themable Chrome NTP Foogle logo -->
<div class="mock-foogle-logo">Foogle</div>
<!-- Static neutral search bar to avoid misleading the user -->
<div class="mock-search-bar">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="none"
stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span>Search Foogle or type a URL</span>
</div>
<div class="mock-shortcuts">
<div class="shortcut-item">
<div class="shortcut-circle">G</div>
<span class="shortcut-text">Gmail</span>
</div>
<div class="shortcut-item">
<div class="shortcut-circle">YT</div>
<span class="shortcut-text">YouTube</span>
</div>
<div class="shortcut-item">
<div class="shortcut-circle">GH</div>
<span class="shortcut-text">GitHub</span>
</div>
<div class="shortcut-item">
<div class="shortcut-circle">CAL</div>
<span class="shortcut-text">Calendar</span>
</div>
</div>
<!-- Sample text & link to visualize ntp_text and ntp_link -->
<div class="mock-ntp-readout">
<p class="mock-ntp-text">Sample Native NTP Text</p>
<a href="#" class="mock-ntp-link">Sample Native NTP Link</a>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<script src="index.js"></script>
</body>
</html>
/**
* ThemeGen - Unified Chrome Theme Generator & Previewer System
* Implements DEEP and HIGHLY VISIBLE Graphic Design Harmonies.
* Shifting the Hue of the Frame, NTP Background, and Accents dynamically
* based on Monochromatic, Analogous, Complementary, and Triadic calculations.
*/
const ColorUtils = {
hexToRgb(hex) {
hex = hex.replace(/^#/, '');
if (hex.length === 3) {
hex = hex.split('').map(c => c + c).join('');
}
const num = parseInt(hex, 16);
return [(num >> 16) & 255, (num >> 8) & 255, num & 255];
},
rgbToHex(r, g, b) {
return "#" + [r, g, b].map(x => {
const hex = x.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}).join('');
},
rgbToHsl(r, g, b) {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
},
hslToRgb(h, s, l) {
h /= 360; s /= 100; l /= 100;
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
},
getLuminance(r, g, b) {
const a = [r, g, b].map(v => {
v /= 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];
},
getContrastColor(r, g, b, baseHue) {
const l = this.getLuminance(r, g, b);
if (l > 0.38) {
const [tr, tg, tb] = this.hslToRgb(baseHue, 30, 8);
return [tr, tg, tb];
} else {
const [tr, tg, tb] = this.hslToRgb(baseHue, 12, 97);
return [tr, tg, tb];
}
},
getSecondaryTextColor(r, g, b, baseHue) {
const l = this.getLuminance(r, g, b);
if (l > 0.38) {
const [tr, tg, tb] = this.hslToRgb(baseHue, 20, 36);
return [tr, tg, tb];
} else {
const [tr, tg, tb] = this.hslToRgb(baseHue, 15, 78);
return [tr, tg, tb];
}
}
};
const PaletteGenerator = {
generate(baseHex, harmonyMode = 'complementary', themeMode = 'dark') {
const baseRgb = ColorUtils.hexToRgb(baseHex);
const [h, s, l] = ColorUtils.rgbToHsl(...baseRgb);
const primaryHue = h;
let frameHue, ntpBgHue, ntpLinkHue, secondaryHue, tertiaryHue;
// Compute Hues with HIGHLY VISIBLE and meaningful graphic design purposes
switch (harmonyMode) {
case 'monochromatic':
frameHue = primaryHue;
ntpBgHue = primaryHue;
ntpLinkHue = primaryHue;
secondaryHue = primaryHue;
tertiaryHue = primaryHue;
break;
case 'analogous':
frameHue = (primaryHue + 30) % 360;
ntpBgHue = (primaryHue - 30 + 360) % 360;
ntpLinkHue = (primaryHue + 30) % 360;
secondaryHue = (primaryHue + 30) % 360;
tertiaryHue = (primaryHue - 30 + 360) % 360;
break;
case 'complementary':
frameHue = (primaryHue + 180) % 360; // Frame adopts the COMPLEMENT!
ntpBgHue = primaryHue;
ntpLinkHue = (primaryHue + 180) % 360; // Links pop in Complement
secondaryHue = (primaryHue + 180) % 360;
tertiaryHue = primaryHue;
break;
case 'triadic':
frameHue = (primaryHue + 120) % 360; // First Triad hue for Frame
ntpBgHue = (primaryHue + 240) % 360; // Second Triad hue for NTP
ntpLinkHue = (primaryHue + 120) % 360;
secondaryHue = (primaryHue + 120) % 360;
tertiaryHue = (primaryHue + 240) % 360;
break;
default:
frameHue = primaryHue;
ntpBgHue = primaryHue;
ntpLinkHue = (primaryHue + 180) % 360;
secondaryHue = (primaryHue + 180) % 360;
tertiaryHue = primaryHue;
}
const safeSat = Math.max(s, 35); // Ensure there is enough saturation to actually see the hues!
let colors = {};
let frameRgb, frameInactiveRgb, bgRgb, buttonBgRgb;
if (themeMode === 'dark') {
// Dark Mode: Highly colorful Frame and deep NTP background
frameRgb = ColorUtils.hslToRgb(frameHue, Math.min(safeSat, 55), Math.max(Math.min(l - 12, 16), 11));
frameInactiveRgb = ColorUtils.hslToRgb(frameHue, Math.min(safeSat * 0.7, 40), Math.max(Math.min(l - 16, 12), 8));
bgRgb = ColorUtils.hslToRgb(ntpBgHue, Math.min(safeSat * 0.4, 25), 8);
buttonBgRgb = ColorUtils.hslToRgb(frameHue, Math.min(safeSat, 45), 24);
} else {
// Light Mode: Frame adopts a gorgeous pastel/muted variant of the harmony hue
frameRgb = ColorUtils.hslToRgb(frameHue, Math.min(safeSat * 0.5, 35), Math.min(Math.max(l + 12, 85), 90));
frameInactiveRgb = ColorUtils.hslToRgb(frameHue, Math.min(safeSat * 0.3, 25), Math.min(Math.max(l + 16, 90), 93));
bgRgb = ColorUtils.hslToRgb(ntpBgHue, Math.min(safeSat * 0.15, 12), 97);
buttonBgRgb = ColorUtils.hslToRgb(frameHue, Math.min(safeSat * 0.4, 30), 85);
}
const textPrimary = ColorUtils.getContrastColor(...baseRgb, primaryHue);
const textSecondary = ColorUtils.getSecondaryTextColor(...baseRgb, primaryHue);
const accentRgb = ColorUtils.hslToRgb(ntpLinkHue, Math.max(safeSat, 75), themeMode === 'dark' ? 65 : 45);
// If Monochromatic, make Frame a deeper/lighter shade of the EXACT toolbar hue
if (harmonyMode === 'monochromatic') {
if (themeMode === 'dark') {
frameRgb = ColorUtils.hslToRgb(primaryHue, safeSat * 0.9, Math.max(l - 10, 8));
frameInactiveRgb = ColorUtils.hslToRgb(primaryHue, safeSat * 0.7, Math.max(l - 14, 6));
} else {
frameRgb = ColorUtils.hslToRgb(primaryHue, safeSat * 0.4, Math.min(l + 12, 88));
frameInactiveRgb = ColorUtils.hslToRgb(primaryHue, safeSat * 0.2, Math.min(l + 16, 92));
}
}
colors = {
primary: baseHex,
secondary: ColorUtils.rgbToHex(accentRgb[0], accentRgb[1], accentRgb[2]),
tertiary: ColorUtils.rgbToHex(...ColorUtils.hslToRgb(tertiaryHue, Math.max(safeSat, 70), 55)),
background: ColorUtils.rgbToHex(...bgRgb),
surface: baseHex,
surfaceElevated: ColorUtils.rgbToHex(...buttonBgRgb),
textPrimary: ColorUtils.rgbToHex(...textPrimary),
textSecondary: ColorUtils.rgbToHex(...textSecondary),
borderColor: ColorUtils.rgbToHex(...ColorUtils.hslToRgb(primaryHue, safeSat * 0.3, themeMode === 'dark' ? 22 : 84)),
chrome: {
frame: frameRgb,
frame_inactive: frameInactiveRgb,
frame_incognito: [30, 32, 38],
frame_incognito_inactive: [22, 24, 28],
toolbar: baseRgb, // EXACTLY the user's selected base color!
tab_text: textPrimary,
tab_background_text: ColorUtils.getSecondaryTextColor(...frameRgb, primaryHue),
bookmark_text: textPrimary,
ntp_background: bgRgb,
ntp_text: ColorUtils.getContrastColor(...bgRgb, primaryHue),
ntp_link: accentRgb,
button_background: buttonBgRgb
}
};
return {
baseHex: baseHex.toUpperCase(),
harmonyMode,
themeMode,
primaryHue,
secondaryHue,
tertiaryHue,
colors
};
},
generateNativeThemeJson(palette) {
const c = palette.colors.chrome;
const manifest = {
manifest_version: 3,
name: `ThemeGen - ${palette.baseHex} ${palette.themeMode.charAt(0).toUpperCase() + palette.themeMode.slice(1)} Theme`,
version: "1.0.0",
description: `Premium graphic-design principled Chrome theme generated dynamically from ${palette.baseHex} using a ${palette.harmonyMode} harmony.`,
theme: {
colors: {
frame: c.frame,
frame_inactive: c.frame_inactive,
frame_incognito: c.frame_incognito,
frame_incognito_inactive: c.frame_incognito_inactive,
toolbar: c.toolbar,
tab_text: c.tab_text,
tab_background_text: c.tab_background_text,
bookmark_text: c.bookmark_text,
ntp_background: c.ntp_background,
ntp_text: c.ntp_text,
ntp_link: c.ntp_link,
button_background: c.button_background
},
tints: {
buttons: [palette.secondaryHue / 360, 0.3, palette.themeMode === 'dark' ? 0.6 : 0.4]
},
properties: {
ntp_background_alignment: "center",
ntp_background_repeat: "no-repeat"
}
}
};
return JSON.stringify(manifest, null, 2);
}
};
function drawColorWheel(canvas, primaryHue, secondaryHue, tertiaryHue, harmonyMode, themeMode) {
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
const cx = width / 2;
const cy = height / 2;
const radius = Math.min(cx, cy) - 10;
ctx.clearRect(0, 0, width, height);
for (let angle = 0; angle < 360; angle++) {
const startAngle = (angle - 0.5) * Math.PI / 180;
const endAngle = (angle + 1.5) * Math.PI / 180;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, startAngle, endAngle);
ctx.closePath();
ctx.fillStyle = `hsl(${angle}, 85%, 55%)`;
ctx.fill();
}
ctx.beginPath();
ctx.arc(cx, cy, radius * 0.68, 0, 2 * Math.PI);
ctx.fillStyle = themeMode === 'dark' ? '#14171E' : '#FFFFFF';
ctx.fill();
const drawDot = (hue, isPrimary = false) => {
const rad = hue * Math.PI / 180;
const rDist = radius * 0.84;
const px = cx + rDist * Math.cos(rad);
const py = cy + rDist * Math.sin(rad);
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.lineTo(px, py);
ctx.strokeStyle = isPrimary ? (themeMode === 'dark' ? 'rgba(255, 255, 255, 0.4)' : 'rgba(0, 0, 0, 0.25)') : (themeMode === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)');
ctx.lineWidth = isPrimary ? 2 : 1;
ctx.setLineDash([4, 4]);
ctx.stroke();
ctx.setLineDash([]);
ctx.beginPath();
ctx.arc(px, py, isPrimary ? 8 : 6, 0, 2 * Math.PI);
ctx.fillStyle = `hsl(${hue}, 90%, 65%)`;
ctx.strokeStyle = '#FFFFFF';
ctx.lineWidth = 2;
ctx.shadowBlur = 8;
ctx.shadowColor = `hsl(${hue}, 90%, 65%)`;
ctx.fill();
ctx.stroke();
ctx.shadowBlur = 0;
};
ctx.beginPath();
const radP = primaryHue * Math.PI / 180;
const radS = secondaryHue * Math.PI / 180;
const rDist = radius * 0.84;
ctx.moveTo(cx + rDist * Math.cos(radP), cy + rDist * Math.sin(radP));
if (harmonyMode === 'complementary') {
ctx.lineTo(cx + rDist * Math.cos(radS), cy + rDist * Math.sin(radS));
} else if (harmonyMode === 'triadic') {
const radT = tertiaryHue * Math.PI / 180;
ctx.lineTo(cx + rDist * Math.cos(radS), cy + rDist * Math.sin(radS));
ctx.lineTo(cx + rDist * Math.cos(radT), cy + rDist * Math.sin(radT));
ctx.closePath();
} else if (harmonyMode === 'analogous') {
ctx.lineTo(cx + rDist * Math.cos(radS), cy + rDist * Math.sin(radS));
const radT = tertiaryHue * Math.PI / 180;
ctx.moveTo(cx + rDist * Math.cos(radP), cy + rDist * Math.sin(radP));
ctx.lineTo(cx + rDist * Math.cos(radT), cy + rDist * Math.sin(radT));
}
ctx.strokeStyle = themeMode === 'dark' ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.15)';
ctx.lineWidth = 1.5;
ctx.stroke();
if (harmonyMode === 'triadic') {
drawDot(secondaryHue);
drawDot(tertiaryHue);
} else if (harmonyMode === 'analogous') {
drawDot(secondaryHue);
drawDot(tertiaryHue);
} else if (harmonyMode === 'complementary') {
drawDot(secondaryHue);
}
drawDot(primaryHue, true);
}
const presets = [
{ name: 'Sapphire Slate', hex: '#3D5A80' },
{ name: 'Aurora Teal', hex: '#00A896' },
{ name: 'Terracotta Sunset', hex: '#E07A5F' },
{ name: 'Neon Cyberpunk', hex: '#FF007F' },
{ name: 'Forest Matcha', hex: '#81B622' },
{ name: 'Amethyst Royal', hex: '#7209B7' },
{ name: 'Amber Gold', hex: '#F4A261' },
{ name: 'Nordic Snow', hex: '#8E9AAF' }
];
let currentConfig = {
baseHex: '#3D5A80',
harmonyMode: 'complementary',
themeMode: 'dark'
};
const presetGrid = document.getElementById('presetGrid');
const baseColorInput = document.getElementById('baseColorInput');
const baseColorHex = document.getElementById('baseColorHex');
const harmonyControl = document.getElementById('harmonyControl');
const modeControl = document.getElementById('modeControl');
const harmonyDesc = document.getElementById('harmonyDesc');
const canvas = document.getElementById('colorWheelCanvas');
const contrastBadge = document.getElementById('contrastBadge');
const btnSave = document.getElementById('btnSave');
function init() {
presets.forEach(p => {
const btn = document.createElement('button');
btn.className = 'preset-btn';
btn.style.backgroundColor = p.hex;
btn.title = p.name;
btn.setAttribute('data-hex', p.hex);
btn.addEventListener('click', () => {
updateConfig({ baseHex: p.hex });
});
presetGrid.appendChild(btn);
});
baseColorInput.addEventListener('input', (e) => {
const hex = e.target.value.toUpperCase();
baseColorHex.textContent = hex;
updateConfig({ baseHex: hex });
});
setupSegmentedControl('harmonyControl', 'harmonyMode', (val) => {
const descs = {
monochromatic: "Varies saturation and lightness across your selected hue for a minimal, unified aesthetic.",
analogous: "Shifts your Window Frame and NTP background to adjacent (± 30°) hues for a natural, harmonious palette.",
complementary: "Shifts your Window Frame and Accents into pure opposite hues (180°) for a high-contrast dynamic palette.",
triadic: "Forms an equilateral triangle (120° and 240°), splitting your Toolbar, Frame, and NTP into three beautifully balanced hues."
};
harmonyDesc.textContent = descs[val] || "";
});
setupSegmentedControl('modeControl', 'themeMode');
btnSave.addEventListener('click', () => {
const palette = PaletteGenerator.generate(
currentConfig.baseHex,
currentConfig.harmonyMode,
currentConfig.themeMode
);
const jsonStr = PaletteGenerator.generateNativeThemeJson(palette);
const blob = new Blob([jsonStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'manifest.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
applyConfig();
}
function setupSegmentedControl(elementId, configKey, onUpdate) {
const container = document.getElementById(elementId);
const btns = container.querySelectorAll('.seg-btn');
btns.forEach(b => {
b.addEventListener('click', () => {
btns.forEach(x => x.classList.remove('active'));
b.classList.add('active');
const val = b.getAttribute('data-value');
updateConfig({ [configKey]: val });
if (onUpdate) onUpdate(val);
});
});
btns.forEach(b => {
if (b.getAttribute('data-value') === currentConfig[configKey]) {
b.classList.add('active');
} else {
b.classList.remove('active');
}
});
}
function updateConfig(newPartial) {
currentConfig = { ...currentConfig, ...newPartial };
applyConfig();
}
function applyConfig() {
baseColorInput.value = currentConfig.baseHex;
baseColorHex.textContent = currentConfig.baseHex.toUpperCase();
const harmBtns = harmonyControl.querySelectorAll('.seg-btn');
harmBtns.forEach(b => {
b.classList.toggle('active', b.getAttribute('data-value') === currentConfig.harmonyMode);
});
const modeBtns = modeControl.querySelectorAll('.seg-btn');
modeBtns.forEach(b => {
b.classList.toggle('active', b.getAttribute('data-value') === currentConfig.themeMode);
});
// Highlight active preset
const presetBtns = presetGrid.querySelectorAll('.preset-btn');
presetBtns.forEach(b => {
b.classList.toggle('active', b.getAttribute('data-hex').toUpperCase() === currentConfig.baseHex.toUpperCase());
});
// 1. Generate Palette
const palette = PaletteGenerator.generate(
currentConfig.baseHex,
currentConfig.harmonyMode,
currentConfig.themeMode
);
// 2. Set Application CSS Variables
document.body.className = currentConfig.themeMode === 'dark' ? 'dark-app' : 'light-app';
document.body.style.setProperty('--color-accent', palette.colors.primary);
// 3. Update Contrast Readout (Based on EXACT Toolbar Surface)
const toolbarRgbTuple = palette.colors.chrome.toolbar;
const badgeText = ColorUtils.getContrastColor(...toolbarRgbTuple, palette.primaryHue);
contrastBadge.style.backgroundColor = palette.colors.primary;
contrastBadge.style.color = ColorUtils.rgbToHex(...badgeText);
const l = ColorUtils.getLuminance(...toolbarRgbTuple);
if (l > 0.38) {
contrastBadge.textContent = "WCAG AAA Reading Pass";
} else {
contrastBadge.textContent = "WCAG AAA Immersive Pass";
}
// 4. Map Palette EXACTLY and ONLY to Native Chrome Mockup Elements
const c = palette.colors.chrome;
const frameRgb = `rgb(${c.frame.join(',')})`;
const toolbarRgb = `rgb(${c.toolbar.join(',')})`;
const tabTextRgb = `rgb(${c.tab_text.join(',')})`;
const tabBgTextRgb = `rgb(${c.tab_background_text.join(',')})`;
const bookmarkTextRgb = `rgb(${c.bookmark_text.join(',')})`;
const ntpBgRgb = `rgb(${c.ntp_background.join(',')})`;
const ntpTextRgb = `rgb(${c.ntp_text.join(',')})`;
const ntpLinkRgb = `rgb(${c.ntp_link.join(',')})`;
const mockFrame = document.querySelector('.chrome-frame');
const mockToolbar = document.querySelector('.chrome-toolbar');
const mockBookmarkBar = document.querySelector('.chrome-bookmark-bar');
const mockNtpArea = document.querySelector('.chrome-ntp-area');
const mockActiveTab = document.querySelector('.chrome-tab.active');
const mockInactiveTabs = document.querySelectorAll('.chrome-tab.inactive');
const mockOmnibox = document.querySelector('.chrome-omnibox');
const mockPlusIcon = document.querySelector('.chrome-newtab-plus');
const mockNtpText = document.querySelector('.mock-ntp-text');
const mockNtpLink = document.querySelector('.mock-ntp-link');
// Frame color paints the title bar and background of inactive tabs
mockFrame.style.backgroundColor = frameRgb;
// Toolbar color paints the main toolbar, the active tab background, and the bookmark bar
mockToolbar.style.backgroundColor = toolbarRgb;
mockBookmarkBar.style.backgroundColor = toolbarRgb;
mockActiveTab.style.backgroundColor = toolbarRgb;
// ntp_background paints the New Tab Page viewport
mockNtpArea.style.backgroundColor = ntpBgRgb;
// Set tab texts
mockActiveTab.style.color = tabTextRgb;
mockInactiveTabs.forEach(t => {
t.style.backgroundColor = frameRgb;
t.style.color = tabBgTextRgb;
});
// Set Bookmark text
mockBookmarkBar.style.color = bookmarkTextRgb;
// Omnibox adopts a perfectly calculated relative tint
mockOmnibox.style.backgroundColor = currentConfig.themeMode === 'dark' ? 'rgba(255, 255, 255, 0.12)' : 'rgba(0, 0, 0, 0.05)';
mockOmnibox.style.color = tabTextRgb;
mockOmnibox.style.border = 'none';
mockPlusIcon.style.color = tabTextRgb;
// Map ntp_text and ntp_link to their respective static readouts
mockNtpText.style.color = ntpTextRgb;
mockNtpLink.style.color = ntpLinkRgb;
// Toolbar Iconsadopt exact tab_text and bookmark_text mappings
document.querySelectorAll('.chrome-nav-buttons svg, .chrome-menu-buttons svg, .icon-close').forEach(svg => {
svg.style.color = tabTextRgb;
});
document.querySelectorAll('.chrome-bookmark svg').forEach(svg => {
svg.style.color = bookmarkTextRgb;
});
// 5. Draw Canvas Color Wheel
drawColorWheel(
canvas,
palette.primaryHue,
palette.secondaryHue,
palette.tertiaryHue,
currentConfig.harmonyMode,
currentConfig.themeMode
);
}
init();
{"name":"Browser Color Theme Generator ","settings":{},"filenames":["index.html","index.css","index.js"]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment