47559c

feat: 12 pastel accent colors with full-surface propagation (v1.5.0)

time
2026-04-23 21:05 UTC · 6 months ago
machine
koh2
parent
d94d9e
14 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/vault.ts+2 −1src/content/inject.ts+37 −24src/content/save.ts+11 −3src/popup/App.tsx+21 −5src/popup/components/Ring.tsx+1 −1src/popup/styles/popup.css+102 −70src/popup/styles/vars.css+5 −4src/popup/views/Add.tsx+15 −15src/popup/views/Settings.tsx+24 −4src/popup/views/contracts.test.tsx+4 −0src/shared/types.ts+17 −0
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "1.4.56",4+  "version": "1.5.0",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "1.4.56",4+  "version": "1.5.0",55   "description": "Zero-knowledge password manager",66   "action": {77     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "1.4.56",3+  "version": "1.5.0",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/background/vault.ts+2 −1
@@ -1,6 +1,7 @@11 import { deriveKey, generateSalt } from "../crypto/argon2";22 import { extractDomain } from "../shared/domain";33 import type {4+  AccentColorName,45   LegacyVaultEntry,56   StoredVault,67   VaultEntry,@@ -45,7 +46,7 @@4546 // ---------------------------------------------------------------------------4647 4748 export interface VaultSettings {48-  accentColor?: string;49+  accentColor?: AccentColorName;4950   theme?: "dark" | "light";5051   autoLockMinutes?: number;5152   clipboardClearSeconds?: number;

view file

src/content/inject.ts+37 −24
@@ -1,3 +1,11 @@1+import { ACCENT_COLORS } from "../shared/types";2+3+let accentHex = "#E8956D";4+chrome.runtime.sendMessage({ type: "GET_SETTINGS" }, (resp: { ok: boolean; data?: { accentColor?: string } } | undefined) => {5+  const name = resp?.data?.accentColor ?? "peach";6+  accentHex = ACCENT_COLORS.find(c => c.name === name)?.hex ?? "#E8956D";7+});8+19 export interface SelectorEntry {210   id: string;311   title: string;@@ -39,11 +47,11 @@3947 }4048 4149 function plusSvgStr(): string {42-  return `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="rgba(217,104,48,0.8)" stroke-width="1.2" stroke-linecap="round"><path d="M6 1.5v9M1.5 6h9"/></svg>`;50+  return `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"><path d="M6 1.5v9M1.5 6h9"/></svg>`;4351 }4452 4553 function peachIconSvg(): string {46-  return `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 64 64" fill="none" aria-hidden="true"><path d="M32 54C16 54 8 44 8 31C8 18 17 10 27 10C28 7 30 5.5 32 5.5C34 5.5 36 7 37 10C47 10 56 18 56 31C56 44 48 54 32 54Z" fill="#D96830"/><path d="M37 8.5C40.5 3.5 48 5 46 10.5C42 11 37 8.5 37 8.5Z" fill="#7B9060"/></svg>`;54+  return `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 64 64" fill="none" aria-hidden="true"><path d="M32 54C16 54 8 44 8 31C8 18 17 10 27 10C28 7 30 5.5 32 5.5C34 5.5 36 7 37 10C47 10 56 18 56 31C56 44 48 54 32 54Z" fill="${accentHex}"/><path d="M37 8.5C40.5 3.5 48 5 46 10.5C42 11 37 8.5 37 8.5Z" fill="#7B9060"/></svg>`;4755 }4856 4957 function genSvgStr(): string {@@ -77,6 +85,7 @@7785 ): void {7886   const host = document.createElement("div");7987   host.style.cssText = "position:fixed;bottom:22px;left:50%;transform:translateX(-50%);z-index:2147483647;pointer-events:auto;";88+  host.style.setProperty("--peach-accent", accentHex);8089   const shadow = host.attachShadow({ mode: "closed" });8190 8291   const style = document.createElement("style");@@ -92,9 +101,9 @@92101     .txt { font-size:11px;color:rgba(240,232,216,0.55);font-family:"IBM Plex Mono",monospace; }93102     .yes {94103       padding:5px 12px;border-radius:6px;border:none;cursor:pointer;font-size:11px;95-      background:#D96830;color:#fff;transition:background 100ms;font-family:Inter,sans-serif;104+      background:var(--peach-accent);color:#fff;transition:background 100ms;font-family:Inter,sans-serif;96105     }97-    .yes:hover { background:#c55e28; }106+    .yes:hover { background:color-mix(in srgb, var(--peach-accent) 82%, #000); }98107     .no {99108       padding:5px 10px;border-radius:6px;border:none;cursor:pointer;font-size:11px;100109       background:rgba(240,232,216,0.07);color:rgba(240,232,216,0.45);font-family:Inter,sans-serif;@@ -203,8 +212,8 @@203212     opacity: 0;204213   `;205214   button.addEventListener("mouseenter", () => {206-    button.style.background = "rgba(217,104,48,0.12)";207-    button.style.boxShadow = "0 0 0 1px rgba(217,104,48,0.18), 0 0 14px rgba(217,104,48,0.12)";215+    button.style.background = `color-mix(in srgb, ${accentHex} 12%, transparent)`;216+    button.style.boxShadow = `0 0 0 1px color-mix(in srgb, ${accentHex} 18%, transparent), 0 0 14px color-mix(in srgb, ${accentHex} 12%, transparent)`;208217     button.style.transform = "scale(1.08)";209218   });210219   button.addEventListener("mouseleave", () => {@@ -274,7 +283,7 @@274283     color: rgba(240,232,216,0.28); font-family: "IBM Plex Mono", monospace;275284   }276285   .domain-name {277-    font-size: 10px; color: rgba(217,104,48,0.6);286+    font-size: 10px; color: color-mix(in srgb, var(--peach-accent) 60%, transparent);278287     font-family: "IBM Plex Mono", monospace;279288   }280289 @@ -291,8 +300,8 @@291300     transition: border-color 120ms, box-shadow 120ms;292301   }293302   .search:focus {294-    border-color: rgba(217,104,48,0.4);295-    box-shadow: 0 0 0 2px rgba(217,104,48,0.10);303+    border-color: color-mix(in srgb, var(--peach-accent) 40%, transparent);304+    box-shadow: 0 0 0 2px color-mix(in srgb, var(--peach-accent) 10%, transparent);296305   }297306   .search::placeholder { color: rgba(240,232,216,0.2); }298307 @@ -308,10 +317,10 @@308317   }309318   .entry:last-child { border-bottom: none; }310319   .entry:hover { background: rgba(240,232,216,0.04); }311-  .entry.focused { background: rgba(217,104,48,0.08); }320+  .entry.focused { background: color-mix(in srgb, var(--peach-accent) 8%, transparent); }312321   .entry.focused::before {313322     content: ""; position: absolute; left: 0; top: 3px; bottom: 3px;314-    width: 2px; border-radius: 2px; background: rgba(217,104,48,0.55);323+    width: 2px; border-radius: 2px; background: color-mix(in srgb, var(--peach-accent) 55%, transparent);315324   }316325 317326   .entry-icon {@@ -322,7 +331,9 @@322331   }323332   .entry-icon img { width: 15px; height: 15px; border-radius: 2px; display: block; }324333   .entry-icon.is-new {325-    background: rgba(217,104,48,0.08); border-color: rgba(217,104,48,0.15);334+    background: color-mix(in srgb, var(--peach-accent) 8%, transparent);335+    border-color: color-mix(in srgb, var(--peach-accent) 15%, transparent);336+    color: color-mix(in srgb, var(--peach-accent) 80%, transparent);326337   }327338 328339   .entry-info { flex: 1; min-width: 0; }@@ -331,7 +342,7 @@331342     white-space: nowrap; overflow: hidden; text-overflow: ellipsis;332343     line-height: 1.3;333344   }334-  .entry-title.is-new { color: rgba(217,104,48,0.9); font-size: 12px; }345+  .entry-title.is-new { color: color-mix(in srgb, var(--peach-accent) 90%, transparent); font-size: 12px; }335346   .entry-sub {336347     font-size: 10px; color: rgba(240,232,216,0.32);337348     font-family: "IBM Plex Mono", monospace;@@ -341,8 +352,8 @@341352 342353   .totp-badge {343354     font-size: 8px; letter-spacing: 0.04em; padding: 2px 6px;344-    border-radius: 4px; background: rgba(217,104,48,0.10);345-    color: rgba(217,104,48,0.75); font-family: "IBM Plex Mono", monospace;355+    border-radius: 4px; background: color-mix(in srgb, var(--peach-accent) 10%, transparent);356+    color: color-mix(in srgb, var(--peach-accent) 75%, transparent); font-family: "IBM Plex Mono", monospace;346357     flex-shrink: 0; margin-left: 8px;347358   }348359 @@ -385,30 +396,31 @@385396     font-size: 11px; font-family: "IBM Plex Mono", monospace;386397     outline: none; width: 100%; transition: border-color 120ms, box-shadow 120ms;387398   }388-  .f-input:focus { border-color: rgba(217,104,48,0.4); box-shadow: 0 0 0 2px rgba(217,104,48,0.10); }399+  .f-input:focus { border-color: color-mix(in srgb, var(--peach-accent) 40%, transparent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--peach-accent) 10%, transparent); }389400   .f-input::placeholder { color: rgba(240,232,216,0.18); }390401   .f-input.url-f { font-size: 10px; color: rgba(240,232,216,0.5); letter-spacing: 0.01em; }391402   .f-row { display: flex; gap: 6px; }392403   .f-row .f-input { flex: 1; min-width: 0; }393404   .gen-btn {394405     padding: 0 10px; border-radius: 7px; border: none;395-    background: rgba(217,104,48,0.10); color: rgba(217,104,48,0.8);406+    background: color-mix(in srgb, var(--peach-accent) 10%, transparent);407+    color: color-mix(in srgb, var(--peach-accent) 80%, transparent);396408     font-size: 10px; font-family: "IBM Plex Mono", monospace;397409     cursor: pointer; white-space: nowrap; flex-shrink: 0;398410     display: flex; align-items: center; gap: 4px;399411     transition: background 100ms;400412   }401-  .gen-btn:hover { background: rgba(217,104,48,0.18); }413+  .gen-btn:hover { background: color-mix(in srgb, var(--peach-accent) 18%, transparent); }402414   .save-btn {403415     width: 100%; padding: 8px; border-radius: 8px; border: none;404-    background: #D96830; color: #fff; font-size: 12px;416+    background: var(--peach-accent); color: #fff; font-size: 12px;405417     font-family: Inter, sans-serif; cursor: pointer; font-weight: 500;406418     transition: background 120ms; letter-spacing: 0.01em;407419   }408-  .save-btn:hover { background: #c55e28; }420+  .save-btn:hover { background: color-mix(in srgb, var(--peach-accent) 82%, #000); }409421   .save-btn:disabled { opacity: 0.4; cursor: not-allowed; }410422   .url-hint {411-    font-size: 9px; color: rgba(217,104,48,0.45);423+    font-size: 9px; color: color-mix(in srgb, var(--peach-accent) 45%, transparent);412424     font-family: "IBM Plex Mono", monospace; font-style: italic;413425   }414426 @@ -450,15 +462,15 @@450462     font-size: 11px; font-family: "IBM Plex Mono", monospace;451463     outline: none; transition: border-color 120ms, box-shadow 120ms;452464   }453-  .locked-input:focus { border-color: rgba(217,104,48,0.4); box-shadow: 0 0 0 2px rgba(217,104,48,0.10); }465+  .locked-input:focus { border-color: color-mix(in srgb, var(--peach-accent) 40%, transparent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--peach-accent) 10%, transparent); }454466   .locked-input::placeholder { color: rgba(240,232,216,0.2); }455467   .locked-btn {456468     width: 100%; padding: 8px; border-radius: 8px; border: none;457-    background: #D96830; color: #fff; font-size: 12px;469+    background: var(--peach-accent); color: #fff; font-size: 12px;458470     font-family: Inter, sans-serif; cursor: pointer; font-weight: 500;459471     transition: background 120ms; letter-spacing: 0.01em;460472   }461-  .locked-btn:hover { background: #c55e28; }473+  .locked-btn:hover { background: color-mix(in srgb, var(--peach-accent) 82%, #000); }462474   .locked-btn:disabled { opacity: 0.4; cursor: not-allowed; }463475   .locked-error {464476     font-size: 10px; color: rgba(220,80,60,0.85);@@ -474,6 +486,7 @@474486 ): () => void {475487   const host = document.createElement("div");476488   host.style.cssText = "position: absolute; z-index: 2147483647;";489+  host.style.setProperty("--peach-accent", accentHex);477490   const shadow = host.attachShadow({ mode: "closed" });478491 479492   const style = shadow.appendChild(document.createElement("style"));

view file

src/content/save.ts+11 −3
@@ -1,6 +1,13 @@11 import { extractDomain } from "../shared/domain";2+import { ACCENT_COLORS } from "../shared/types";23 import type { VaultEntry } from "../shared/types";34 5+let accentHex = "#E8956D";6+chrome.runtime.sendMessage({ type: "GET_SETTINGS" }, (resp: { ok: boolean; data?: { accentColor?: string } } | undefined) => {7+  const name = resp?.data?.accentColor ?? "peach";8+  accentHex = ACCENT_COLORS.find(c => c.name === name)?.hex ?? "#E8956D";9+});10+411 let currentBanner: (() => void) | null = null;512 613 interface DetectedCredential {@@ -64,6 +71,7 @@6471     position: fixed; top: 16px; right: 16px; z-index: 2147483647;6572     font-family: Inter, sans-serif;6673   `;74+  host.style.setProperty("--peach-accent", accentHex);6775   const shadow = host.attachShadow({ mode: "closed" });6876 6977   const isUpdate = existingEntry !== null;@@ -77,8 +85,8 @@7785     :host { all: initial; }7886     .banner {7987       background:80-        radial-gradient(120% 80% at 0% 0%, rgba(217,104,48,0.10) 0%, transparent 65%),81-        radial-gradient(85% 100% at 100% 100%, rgba(217,104,48,0.06) 0%, transparent 70%),88+        radial-gradient(120% 80% at 0% 0%, color-mix(in srgb, var(--peach-accent) 10%, transparent) 0%, transparent 65%),89+        radial-gradient(85% 100% at 100% 100%, color-mix(in srgb, var(--peach-accent) 6%, transparent) 0%, transparent 70%),8290         #0C0A06;8391       border: 1px solid rgba(240,232,216,0.10);8492       border-radius: 14px;@@ -108,7 +116,7 @@108116     }109117     .btn-save {110118       flex: 1;111-      background: #D96830;119+      background: var(--peach-accent);112120       color: rgba(0,0,0,0.75);113121       border: none;114122       border-radius: 10px;

view file

src/popup/App.tsx+21 −5
@@ -1,5 +1,6 @@11 import { createEffect, createMemo, createSignal, lazy, onCleanup, onMount, Show, Suspense } from "solid-js";2-import type { PasskeyEntry, PendingPasskeyRequest, VaultEntry } from "../shared/types";2+import { ACCENT_COLORS } from "../shared/types";3+import type { AccentColorName, PasskeyEntry, PendingPasskeyRequest, VaultEntry } from "../shared/types";34 import { LockView } from "./views/Lock";45 import { VaultView, type SortOrder } from "./views/Vault";56 import { EntryView } from "./views/Entry";@@ -56,7 +57,7 @@5657       {5758         offset: 0.3,5859         boxShadow:59-          "0 2px 8px rgba(0,0,0,0.5), 0 14px 48px rgba(0,0,0,0.6), 0 0 0 1.5px rgba(217,104,48,0.58), 0 0 22px rgba(217,104,48,0.18)",60+          "0 2px 8px rgba(0,0,0,0.5), 0 14px 48px rgba(0,0,0,0.6), 0 0 0 1.5px color-mix(in srgb, var(--peach-accent) 58%, transparent), 0 0 22px color-mix(in srgb, var(--peach-accent) 18%, transparent)",6061       },6162       { offset: 1, boxShadow: "var(--copy-shadow)" },6263     ],@@ -159,7 +160,7 @@159160   const [entries, setEntries] = createSignal<VaultEntry[]>([]);160161   const [selectedEntry, setSelectedEntry] = createSignal<VaultEntry | null>(null);161162   const [editEntry, setEditEntry] = createSignal<VaultEntry | undefined>(undefined);162-  const [accentColor, setAccentColor] = createSignal("#D96830");163+  const [accentColor, setAccentColor] = createSignal<AccentColorName>("peach");163164   const [theme, setTheme] = createSignal<"dark" | "light">("dark");164165   const [lockOnClose, setLockOnClose] = createSignal(true);165166   const [autoLockMinutes, setAutoLockMinutes] = createSignal(5);@@ -174,8 +175,13 @@174175 175176   let contentPhaseTimer: number | undefined;176177 178+  function applyAccent(name: AccentColorName) {179+    const color = ACCENT_COLORS.find(c => c.name === name) ?? ACCENT_COLORS[0]!;180+    document.documentElement.style.setProperty("--peach-accent", color.hex);181+  }182+177183   createEffect(() => {178-    document.documentElement.style.setProperty("--ac", accentColor());184+    applyAccent(accentColor());179185   });180186 181187   createEffect(() => {@@ -189,6 +195,12 @@189195     return entries().findIndex((entry) => entry.id === currentId);190196   });191197 198+  async function applyAccentColor(name: AccentColorName) {199+    applyAccent(name);200+    setAccentColor(name);201+    await sendMessage({ type: "UPDATE_SETTINGS", payload: { accentColor: name } });202+  }203+192204   async function applyTheme(t: "dark" | "light") {193205     setTheme(t);194206     await sendMessage({ type: "UPDATE_SETTINGS", payload: { theme: t } });@@ -401,7 +413,9 @@401413   async function restoreSettings() {402414     const res = await sendMessage({ type: "GET_SETTINGS" });403415     const s = (res as { ok: boolean; data?: any }).data ?? {};404-    if (s.accentColor) setAccentColor(s.accentColor);416+    if (s.accentColor && ACCENT_COLORS.some(c => c.name === s.accentColor)) {417+      setAccentColor(s.accentColor as AccentColorName);418+    }405419     setTheme(s.theme === "light" ? "light" : "dark");406420     setLockOnClose(s.lockOnClose !== false);407421     setAutoLockMinutes(typeof s.autoLockMinutes === "number" ? s.autoLockMinutes : 5);@@ -798,11 +812,13 @@798812                   autoLockMinutes={autoLockMinutes()}799813                   trashRetentionDays={trashRetentionDays()}800814                   theme={theme()}815+                  accentColor={accentColor()}801816                   onBreachMonitoringChange={applyBreachMonitoring}802817                   onLockOnCloseChange={applyLockOnClose}803818                   onAutoLockMinutesChange={applyAutoLockMinutes}804819                   onTrashRetentionChange={applyTrashRetention}805820                   onThemeChange={applyTheme}821+                  onAccentChange={applyAccentColor}806822                   onBack={handleBack}807823                   onImport={() => setView("import")}808824                   onExport={() => setView("export")}

view file

src/popup/components/Ring.tsx+1 −1
@@ -73,7 +73,7 @@7373         stroke="var(--ac)"7474         stroke-width="1"7575         style={{76-          filter: "drop-shadow(0 0 5px rgba(217,104,48,0.8))",76+          filter: "drop-shadow(0 0 5px color-mix(in srgb, var(--peach-accent) 80%, transparent))",7777           transform: "rotate(-90deg)",7878           "transform-origin": "50% 50%",7979           opacity: "0",

view file

src/popup/styles/popup.css+102 −70
@@ -79,8 +79,8 @@7979 /* Shell */8080 .peach-shell, .peach-screen {8181   background:82-    radial-gradient(120% 80% at 0% 0%, rgba(217,104,48,0.08) 0%, transparent 65%),83-    radial-gradient(85% 100% at 100% 100%, rgba(217,104,48,0.06) 0%, transparent 70%),82+    radial-gradient(120% 80% at 0% 0%, var(--shell-accent-a) 0%, transparent 65%),83+    radial-gradient(85% 100% at 100% 100%, var(--shell-accent-b) 0%, transparent 70%),8484     var(--peach-bg);8585   border: 1px solid var(--peach-t3);8686   box-shadow:@@ -698,7 +698,7 @@698698   border-top: 1px solid var(--peach-t3);699699   background: var(--peach-s1);700700   min-height: 54px; position: relative;701-  box-shadow: inset 0 6px 18px rgba(217, 104, 48, 0);701+  box-shadow: inset 0 6px 18px transparent;702702   transition: box-shadow 700ms ease;703703 }704704 .peach-search-wrap::before {@@ -791,7 +791,7 @@791791 792792 .peach-btn:focus-visible {793793   outline: none;794-  box-shadow: 0 0 0 2px var(--peach-bg), 0 0 0 4px rgba(217, 104, 48, 0.5);794+  box-shadow: 0 0 0 2px var(--peach-bg), 0 0 0 4px color-mix(in srgb, var(--peach-accent) 50%, transparent);795795 }796796 797797 /* Lock button */@@ -813,15 +813,15 @@813813 .peach-copy-icon-svg { display: block; }814814 .peach-copy-sheen {815815   position: absolute; inset: 0;816-  background: linear-gradient(90deg, rgba(217,104,48,0) 0%, rgba(217,104,48,0.18) 30%, rgba(217,104,48,0.08) 100%);816+  background: linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--peach-accent) 18%, transparent) 30%, color-mix(in srgb, var(--peach-accent) 8%, transparent) 100%);817817   opacity: 0; transform: scaleX(0.15); transform-origin: left center;818818 }819819 .peach-copy-btn.is-copied {820820   animation: peach-copy-confirm var(--peach-dur-150) var(--peach-curve-settle) forwards;821821   box-shadow:822-    0 0 0 1px rgba(217,104,48,0.22),823-    0 0 18px rgba(217,104,48,0.2),824-    inset 0 0 20px rgba(217,104,48,0.12);822+    0 0 0 1px color-mix(in srgb, var(--peach-accent) 22%, transparent),823+    0 0 18px color-mix(in srgb, var(--peach-accent) 20%, transparent),824+    inset 0 0 20px color-mix(in srgb, var(--peach-accent) 12%, transparent);825825 }826826 .peach-copy-btn.is-copied .peach-copy-icon {827827   animation: peach-copy-icon-settle var(--peach-dur-150) var(--peach-curve-settle) both;@@ -917,8 +917,8 @@917917   display: flex;918918   flex-direction: column;919919   background:920-    radial-gradient(120% 80% at 0% 0%, rgba(217,104,48,0.08) 0%, transparent 65%),921-    radial-gradient(85% 100% at 100% 100%, rgba(217,104,48,0.06) 0%, transparent 70%),920+    radial-gradient(120% 80% at 0% 0%, var(--shell-accent-a) 0%, transparent 65%),921+    radial-gradient(85% 100% at 100% 100%, var(--shell-accent-b) 0%, transparent 70%),922922     var(--peach-bg);923923   z-index: 2;924924   overflow: hidden;@@ -1420,10 +1420,10 @@14201420   background: rgba(240,232,216,0.02);14211421 }14221422 .peach-vault-list.is-selecting .peach-entry-row.is-selected {1423-  background: rgba(217,104,48,0.06);1423+  background: color-mix(in srgb, var(--peach-accent) 6%, transparent);14241424 }14251425 .peach-vault-list.is-selecting .peach-entry-row.is-selected:hover {1426-  background: rgba(217,104,48,0.1);1426+  background: color-mix(in srgb, var(--peach-accent) 10%, transparent);14271427 }14281428 14291429 /* Select indicator circles in icon slot */@@ -1502,7 +1502,7 @@15021502 }15031503 .peach-export-card--primary {15041504   background:1505-    linear-gradient(180deg, rgba(217,104,48,0.08), rgba(217,104,48,0.02));1505+    linear-gradient(180deg, color-mix(in srgb, var(--peach-accent) 8%, transparent), color-mix(in srgb, var(--peach-accent) 2%, transparent));15061506 }15071507 .peach-export-card-head {15081508   display: flex;@@ -1527,8 +1527,8 @@15271527   align-items: center;15281528   padding: 4px 8px;15291529   border-radius: 999px;1530-  border: 1px solid rgba(217,104,48,0.22);1531-  background: rgba(217,104,48,0.08);1530+  border: 1px solid color-mix(in srgb, var(--peach-accent) 22%, transparent);1531+  background: color-mix(in srgb, var(--peach-accent) 8%, transparent);15321532   color: var(--peach-accent);15331533   font-family: "IBM Plex Mono", monospace;15341534   font-size: 8px;@@ -1602,8 +1602,8 @@16021602   cursor: pointer;16031603 }16041604 .peach-toggle.is-on {1605-  background: rgba(217,104,48,0.22);1606-  border-color: rgba(217,104,48,0.4);1605+  background: color-mix(in srgb, var(--peach-accent) 22%, transparent);1606+  border-color: color-mix(in srgb, var(--peach-accent) 40%, transparent);16071607 }16081608 .peach-toggle-knob {16091609   width: 10px; height: 10px;@@ -1908,7 +1908,7 @@19081908 .peach-new-type-btn.is-active {19091909   color: var(--peach-t0);19101910   border-color: var(--peach-accent);1911-  background: rgba(217, 104, 48, 0.08);1911+  background: color-mix(in srgb, var(--peach-accent) 8%, transparent);19121912 }19131913 .peach-new-type-btn:not(.is-active):hover {19141914   color: var(--peach-t1);@@ -2168,8 +2168,8 @@21682168 .peach-regen-panel {21692169   position: relative;21702170   z-index: 1;2171-  background: rgba(217, 104, 48, 0.08);2172-  border: 1px solid rgba(217, 104, 48, 0.25);2171+  background: color-mix(in srgb, var(--peach-accent) 8%, transparent);2172+  border: 1px solid color-mix(in srgb, var(--peach-accent) 25%, transparent);21732173   border-radius: 8px;21742174   padding: 12px;21752175   margin-bottom: 8px;@@ -2187,7 +2187,7 @@21872187   font-weight: 600;21882188   letter-spacing: 0.06em;21892189   text-transform: uppercase;2190-  color: rgba(217, 104, 48, 0.9);2190+  color: color-mix(in srgb, var(--peach-accent) 90%, transparent);21912191 }21922192 21932193 .peach-regen-output {@@ -2241,7 +2241,7 @@22412241   display: flex;22422242   flex-direction: column;22432243   background:2244-    radial-gradient(120% 80% at 0% 0%, rgba(217,104,48,0.07) 0%, transparent 65%),2244+    radial-gradient(120% 80% at 0% 0%, color-mix(in srgb, var(--peach-accent) 7%, transparent) 0%, transparent 65%),22452245     var(--peach-bg);22462246   animation: overlay-in 180ms cubic-bezier(0.4, 0, 0.2, 1) both;22472247 }@@ -2280,7 +2280,7 @@22802280 22812281 .peach-note-editor-save-btn {22822282   background: transparent;2283-  border: 1px solid rgba(217, 104, 48, 0.4);2283+  border: 1px solid color-mix(in srgb, var(--peach-accent) 40%, transparent);22842284   border-radius: 8px;22852285   color: var(--peach-accent);22862286   font-family: "IBM Plex Mono", monospace;@@ -2292,8 +2292,8 @@22922292   transition: background 120ms ease, border-color 120ms ease;22932293 }22942294 .peach-note-editor-save-btn:hover:not(:disabled) {2295-  background: rgba(217, 104, 48, 0.1);2296-  border-color: rgba(217, 104, 48, 0.65);2295+  background: color-mix(in srgb, var(--peach-accent) 10%, transparent);2296+  border-color: color-mix(in srgb, var(--peach-accent) 65%, transparent);22972297 }22982298 .peach-note-editor-save-btn:disabled { opacity: 0.4; cursor: not-allowed; }22992299 @@ -2361,9 +2361,6 @@23612361 23622362 /* Inline variant (Add.tsx, Entry.tsx note) — toolbar hoisted to parent row */23632363 .peach-note-editor-inline {2364-  border: 1px solid var(--peach-t3);2365-  border-radius: 8px;2366-  overflow: hidden;23672364   display: flex;23682365   flex-direction: column;23692366   flex: 1;@@ -2371,12 +2368,23 @@23712368 }23722369 .peach-note-editor-body--inline {23732370   flex: 1;2374-  min-height: 200px;2371+  min-height: 0;23752372   font-size: 15px;2376-  padding: 10px 14px 14px;2377-  background: var(--peach-t4);2373+  padding: 10px 20px 14px;23782374 }23792375 2376+/* Full-width toolbar row inside the body (Add.tsx note type).2377+   Negative horizontal margins cancel body padding so the border spans edge-to-edge. */2378+.peach-note-toolbar-body {2379+  display: flex;2380+  align-items: center;2381+  gap: 3px;2382+  padding: 5px 20px 4px;2383+  margin: 2px -20px 0;2384+  border-bottom: 1px solid var(--peach-t3);2385+  flex-shrink: 0;2386+}2387+23802388 /* Right-aligned inline toolbar sitting inside .peach-new-type-row (Add.tsx)23812389    or .peach-entry-note-toolbar-row (Entry.tsx). Reuses .peach-note-tool-btn23822390    and .peach-note-tool-divider button-level styles. */@@ -2413,7 +2421,7 @@24132421   margin: 4px 0 0;24142422 }24152423 .peach-entry-body--new .peach-note-body {2416-  margin: 4px 12px 0;2424+  margin: 8px -20px 0;24172425 }24182426 24192427 /* Add.tsx body is a grid by default; swap to a flex column when a note is@@ -2598,10 +2606,10 @@25982606 }25992607 @keyframes peach-copy-confirm {26002608   from { color: var(--peach-t2); border-color: var(--peach-t3); background-color: transparent; box-shadow: none; }2601-  to   { color: var(--peach-accent); border-color: rgba(217,104,48,0.42); background-color: rgba(217,104,48,0.09); box-shadow: 0 0 18px rgba(217,104,48,0.22), inset 0 0 18px rgba(217,104,48,0.12); }2609+  to   { color: var(--peach-accent); border-color: color-mix(in srgb, var(--peach-accent) 42%, transparent); background-color: color-mix(in srgb, var(--peach-accent) 9%, transparent); box-shadow: 0 0 18px color-mix(in srgb, var(--peach-accent) 22%, transparent), inset 0 0 18px color-mix(in srgb, var(--peach-accent) 12%, transparent); }26022610 }26032611 @keyframes peach-copy-retreat {2604-  from { color: var(--peach-accent); border-color: rgba(217,104,48,0.42); background-color: rgba(217,104,48,0.09); box-shadow: 0 0 18px rgba(217,104,48,0.22), inset 0 0 18px rgba(217,104,48,0.12); }2612+  from { color: var(--peach-accent); border-color: color-mix(in srgb, var(--peach-accent) 42%, transparent); background-color: color-mix(in srgb, var(--peach-accent) 9%, transparent); box-shadow: 0 0 18px color-mix(in srgb, var(--peach-accent) 22%, transparent), inset 0 0 18px color-mix(in srgb, var(--peach-accent) 12%, transparent); }26052613   to   { color: var(--peach-t2); border-color: var(--peach-t3); background-color: transparent; box-shadow: none; }26062614 }26072615 @keyframes peach-copy-flood {@@ -2719,8 +2727,8 @@27192727   transition: all 150ms ease;27202728 }27212729 .peach-pwgen-pill.is-on {2722-  border-color: rgba(217, 104, 48, 0.5);2723-  background: rgba(217, 104, 48, 0.1);2730+  border-color: color-mix(in srgb, var(--peach-accent) 50%, transparent);2731+  background: color-mix(in srgb, var(--peach-accent) 10%, transparent);27242732   color: var(--peach-accent);27252733 }27262734 .peach-pwgen-pill:not(.is-on):hover {@@ -2806,7 +2814,7 @@28062814   height: 160px;28072815   border-radius: 999px;28082816   background:2809-    radial-gradient(circle at 50% 50%, rgba(217, 104, 48, 0.18) 0%, rgba(217, 104, 48, 0.06) 42%, transparent 72%);2817+    radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--peach-accent) 18%, transparent) 0%, color-mix(in srgb, var(--peach-accent) 6%, transparent) 42%, transparent 72%);28102818   filter: blur(6px);28112819   opacity: 0.7;28122820   pointer-events: none;@@ -2880,7 +2888,7 @@28802888   height: 100%;28812889 }28822890 .peach-lock-hero-mark path:first-child {2883-  fill: rgba(217, 104, 48, 0.92);2891+  fill: color-mix(in srgb, var(--peach-accent) 92%, transparent);28842892 }28852893 .peach-lock-hero-mark path:nth-child(2),28862894 .peach-lock-hero-mark path:nth-child(3) {@@ -2898,8 +2906,8 @@28982906   width: fit-content;28992907   padding: 4px 9px;29002908   border-radius: 999px;2901-  border: 1px solid rgba(217, 104, 48, 0.22);2902-  background: rgba(217, 104, 48, 0.08);2909+  border: 1px solid color-mix(in srgb, var(--peach-accent) 22%, transparent);2910+  background: color-mix(in srgb, var(--peach-accent) 8%, transparent);29032911   color: var(--peach-t0);29042912   font-family: "IBM Plex Mono", monospace;29052913   font-size: 8px;@@ -2990,7 +2998,7 @@29902998 }29912999 29923000 .peach-lock-option:hover {2993-  border-color: rgba(217, 104, 48, 0.28);3001+  border-color: color-mix(in srgb, var(--peach-accent) 28%, transparent);29943002   background: rgba(240, 232, 216, 0.04);29953003   transform: translateY(-1px);29963004 }@@ -3056,8 +3064,8 @@30563064   background: rgba(240, 232, 216, 0.03);30573065 }30583066 .peach-snapshot-row.is-selected {3059-  border-color: rgba(217, 104, 48, 0.4);3060-  background: rgba(217, 104, 48, 0.07);3067+  border-color: color-mix(in srgb, var(--peach-accent) 40%, transparent);3068+  background: color-mix(in srgb, var(--peach-accent) 7%, transparent);30613069 }30623070 .peach-snapshot-time {30633071   font-size: 11.5px;@@ -3114,11 +3122,11 @@31143122 }31153123 31163124 .peach-card-active {3117-  border-color: rgba(217, 104, 48, 0.35);3125+  border-color: color-mix(in srgb, var(--peach-accent) 35%, transparent);31183126   box-shadow:31193127     inset 0 1px 0 rgba(240, 232, 216, 0.05),31203128     0 16px 38px rgba(0, 0, 0, 0.18),3121-    0 0 0 1px rgba(217, 104, 48, 0.08);3129+    0 0 0 1px color-mix(in srgb, var(--peach-accent) 8%, transparent);31223130 }31233131 31243132 .peach-card-disabled {@@ -3160,7 +3168,7 @@31603168 31613169 .peach-lock-submit {31623170   width: 100%;3163-  box-shadow: 0 14px 36px rgba(217, 104, 48, 0.22);3171+  box-shadow: 0 14px 36px color-mix(in srgb, var(--peach-accent) 22%, transparent);31643172 }31653173 31663174 .peach-lock-secondary {@@ -3381,9 +3389,9 @@33813389 .peach-restore-file::file-selector-button {33823390   margin-right: 10px;33833391   padding: 7px 10px;3384-  border: 1px solid rgba(217, 104, 48, 0.28);3392+  border: 1px solid color-mix(in srgb, var(--peach-accent) 28%, transparent);33853393   border-radius: 999px;3386-  background: rgba(217, 104, 48, 0.08);3394+  background: color-mix(in srgb, var(--peach-accent) 8%, transparent);33873395   color: var(--peach-t0);33883396   font-size: 10px;33893397   letter-spacing: 0.08em;@@ -3477,7 +3485,7 @@34773485   font-size: 10px;34783486   letter-spacing: 0.08em;34793487   text-transform: uppercase;3480-  background: rgba(217, 104, 48, 0.14);3488+  background: color-mix(in srgb, var(--peach-accent) 14%, transparent);34813489   color: var(--peach-accent);34823490 }34833491 @@ -3858,8 +3866,8 @@38583866   gap: 4px;38593867   padding: 3px 8px;38603868   border-radius: 999px;3861-  border: 1px solid rgba(217, 104, 48, 0.28);3862-  background: rgba(217, 104, 48, 0.08);3869+  border: 1px solid color-mix(in srgb, var(--peach-accent) 28%, transparent);3870+  background: color-mix(in srgb, var(--peach-accent) 8%, transparent);38633871   color: rgba(240, 232, 216, 0.85);38643872   font-family: "IBM Plex Mono", monospace;38653873   font-size: 10px;@@ -3906,8 +3914,8 @@39063914 }39073915 39083916 .peach-tag-suggestion:hover {3909-  border-color: rgba(217, 104, 48, 0.4);3910-  background: rgba(217, 104, 48, 0.08);3917+  border-color: color-mix(in srgb, var(--peach-accent) 40%, transparent);3918+  background: color-mix(in srgb, var(--peach-accent) 8%, transparent);39113919 }39123920 39133921 /* Tag filter strip */@@ -3934,13 +3942,13 @@39343942 }39353943 39363944 .peach-tag-filter-chip:hover {3937-  border-color: rgba(217, 104, 48, 0.35);3945+  border-color: color-mix(in srgb, var(--peach-accent) 35%, transparent);39383946   color: var(--peach-t1);39393947 }39403948 39413949 .peach-tag-filter-chip.is-selected {3942-  border-color: rgba(217, 104, 48, 0.5);3943-  background: rgba(217, 104, 48, 0.12);3950+  border-color: color-mix(in srgb, var(--peach-accent) 50%, transparent);3951+  background: color-mix(in srgb, var(--peach-accent) 12%, transparent);39443952   color: rgba(240, 232, 216, 0.9);39453953 }39463954 @@ -3976,13 +3984,13 @@39763984 }39773985 39783986 .peach-sort-btn:hover {3979-  border-color: rgba(217, 104, 48, 0.3);3987+  border-color: color-mix(in srgb, var(--peach-accent) 30%, transparent);39803988   color: var(--peach-t1);39813989 }39823990 39833991 .peach-sort-btn.is-active {3984-  border-color: rgba(217, 104, 48, 0.5);3985-  background: rgba(217, 104, 48, 0.12);3992+  border-color: color-mix(in srgb, var(--peach-accent) 50%, transparent);3993+  background: color-mix(in srgb, var(--peach-accent) 12%, transparent);39863994   color: rgba(240, 232, 216, 0.9);39873995 }39883996 @@ -3990,7 +3998,7 @@39903998   margin-left: auto;39913999   font-family: "IBM Plex Mono", monospace;39924000   font-size: 9px;3993-  color: rgba(217, 104, 48, 0.6);4001+  color: color-mix(in srgb, var(--peach-accent) 60%, transparent);39944002   white-space: nowrap;39954003   overflow: hidden;39964004   text-overflow: ellipsis;@@ -4003,20 +4011,20 @@40034011   width: 6px;40044012   height: 6px;40054013   border-radius: 50%;4006-  background: rgba(217, 104, 48, 0.75);4014+  background: color-mix(in srgb, var(--peach-accent) 75%, transparent);40074015   margin-left: 5px;40084016   vertical-align: middle;40094017   flex-shrink: 0;4010-  box-shadow: 0 0 4px rgba(217, 104, 48, 0.4);4018+  box-shadow: 0 0 4px color-mix(in srgb, var(--peach-accent) 40%, transparent);40114019 }40124020 40134021 .peach-entry-row.is-tab-match {4014-  background: rgba(217, 104, 48, 0.04);4015-  border-bottom-color: rgba(217, 104, 48, 0.08);4022+  background: color-mix(in srgb, var(--peach-accent) 4%, transparent);4023+  border-bottom-color: color-mix(in srgb, var(--peach-accent) 8%, transparent);40164024 }40174025 40184026 .peach-entry-row.is-tab-match:hover {4019-  background: rgba(217, 104, 48, 0.08);4027+  background: color-mix(in srgb, var(--peach-accent) 8%, transparent);40204028 }40214029 40224030 /* Entry list stagger entrance */@@ -4101,8 +4109,8 @@41014109 }41024110 41034111 .peach-duress-status.is-active {4104-  border-color: rgba(217, 104, 48, 0.45);4105-  background: rgba(217, 104, 48, 0.1);4112+  border-color: color-mix(in srgb, var(--peach-accent) 45%, transparent);4113+  background: color-mix(in srgb, var(--peach-accent) 10%, transparent);41064114   color: rgba(240, 232, 216, 0.85);41074115 }41084116 @@ -4156,8 +4164,8 @@41564164   display: flex;41574165   flex-direction: column;41584166   background:4159-    radial-gradient(120% 80% at 0% 0%, rgba(217,104,48,0.08) 0%, transparent 65%),4160-    radial-gradient(85% 100% at 100% 100%, rgba(217,104,48,0.06) 0%, transparent 70%),4167+    radial-gradient(120% 80% at 0% 0%, var(--shell-accent-a) 0%, transparent 65%),4168+    radial-gradient(85% 100% at 100% 100%, var(--shell-accent-b) 0%, transparent 70%),41614169     var(--peach-bg);41624170   z-index: 2;41634171   overflow: hidden;@@ -4201,7 +4209,7 @@42014209   flex-shrink: 0;42024210 }42034211 .peach-duress-status-pill.is-active {4204-  background: rgba(217, 104, 48, 0.15);4212+  background: color-mix(in srgb, var(--peach-accent) 15%, transparent);42054213   color: var(--peach-accent);42064214 }42074215 @@ -4364,4 +4372,28 @@43644372 }43654373 .peach-sort-trash-btn.is-active {43664374   color: var(--peach-accent);4375+}4376+4377+/* Accent color picker */4378+.peach-settings-row--accent { align-items: flex-start; padding-top: 10px; }4379+.peach-accent-picker {4380+  display: flex;4381+  flex-wrap: wrap;4382+  gap: 7px;4383+  padding: 2px 0;4384+}4385+.peach-accent-swatch {4386+  width: 22px;4387+  height: 22px;4388+  border-radius: 50%;4389+  border: 2px solid transparent;4390+  cursor: pointer;4391+  padding: 0;4392+  transition: transform 100ms;4393+  outline-offset: 2px;4394+}4395+.peach-accent-swatch:hover { transform: scale(1.15); }4396+.peach-accent-swatch.is-active {4397+  border-color: var(--peach-t0);4398+  transform: scale(1.1);43674399 }

view file

src/popup/styles/vars.css+5 −4
@@ -83,13 +83,13 @@8383   --t2: rgba(240, 232, 216, 0.20);8484   --t3: rgba(240, 232, 216, 0.07);8585   --t4: rgba(240, 232, 216, 0.04);86-  --ac: #D96830;86+  --ac: #E8956D;8787   --surface-card: rgba(240, 232, 216, 0.02);8888   --surface-elevated: rgba(19, 17, 9, 0.88);8989   --shell-top-solid: #000000;9090   --shell-top-fade: rgba(12, 10, 6, 0.95);91-  --shell-accent-a: rgba(217, 104, 48, 0.08);92-  --shell-accent-b: rgba(217, 104, 48, 0.06);91+  --shell-accent-a: color-mix(in srgb, var(--peach-accent) 8%, transparent);92+  --shell-accent-b: color-mix(in srgb, var(--peach-accent) 6%, transparent);9393   --breach: rgba(220, 80, 60, 0.7);9494   --health: rgba(100, 210, 100, 0.75);9595   --totp-tag: rgba(120, 175, 255, 0.75);@@ -112,6 +112,7 @@112112   --peach-t2: var(--t2);113113   --peach-t3: var(--t3);114114   --peach-accent: var(--ac);115+  --peach-accent-dim: color-mix(in srgb, var(--peach-accent) 25%, transparent);115116   --peach-breach: var(--breach);116117   --peach-health-ok: var(--health);117118   --peach-totp-tag: var(--totp-tag);@@ -148,7 +149,7 @@148149   --surface-elevated: rgba(185, 170, 152, 0.96);149150   --shell-top-solid: rgba(210, 196, 178, 0.96);150151   --shell-top-fade: rgba(198, 180, 160, 0.96);151-  --shell-accent-a: rgba(217, 104, 48, 0.12);152+  --shell-accent-a: color-mix(in srgb, var(--peach-accent) 12%, transparent);152153   --shell-accent-b: rgba(123, 144, 96, 0.10);153154   --copy-shadow: 0 2px 8px rgba(80, 56, 32, 0.22), 0 14px 48px rgba(80, 56, 32, 0.20), 0 0 0 1px rgba(28,25,21,0.06);154155 }

view file

src/popup/views/Add.tsx+15 −15
@@ -180,23 +180,23 @@180180       {/* ── Body — mirrors Entry.tsx field-card structure ── */}181181       <div class="peach-entry-body peach-entry-body--new">182182 183-        {/* Type toggle + inline note formatting toolbar */}184-        <Show when={!props.entry || selectedType() === "note"}>183+        {/* Type toggle */}184+        <Show when={!props.entry}>185185           <div class="peach-new-type-row">186-            <Show when={!props.entry}>187-              <button188-                class={`peach-new-type-btn ${selectedType() === "login" ? "is-active" : ""}`}189-                onClick={() => setSelectedType("login")}190-              >login</button>191-              <button192-                class={`peach-new-type-btn ${selectedType() === "note" ? "is-active" : ""}`}193-                onClick={() => setSelectedType("note")}194-              >note</button>195-            </Show>196-            <Show when={selectedType() === "note"}>197-              <Toolbar exec={noteExec} class="peach-note-toolbar-inline" />198-            </Show>186+            <button187+              class={`peach-new-type-btn ${selectedType() === "login" ? "is-active" : ""}`}188+              onClick={() => setSelectedType("login")}189+            >login</button>190+            <button191+              class={`peach-new-type-btn ${selectedType() === "note" ? "is-active" : ""}`}192+              onClick={() => setSelectedType("note")}193+            >note</button>199194           </div>195+        </Show>196+197+        {/* Note formatting toolbar — own row so it never overflows */}198+        <Show when={selectedType() === "note"}>199+          <Toolbar exec={noteExec} class="peach-note-toolbar-body" />200200         </Show>201201 202202         {/* Title */}

view file

src/popup/views/Settings.tsx+24 −4
@@ -1,6 +1,7 @@1-import { createSignal, onMount, Show } from "solid-js";1+import { createSignal, For, onMount, Show } from "solid-js";22 import { sendMessage } from "../App";3-import type { PasskeyProviderState } from "../../shared/types";3+import { ACCENT_COLORS } from "../../shared/types";4+import type { AccentColorName, PasskeyProviderState } from "../../shared/types";45 import type { PairedDevice, TrustedNetwork, SyncStatus } from "../../shared/sync-types";56 67 interface LicenseInfo {@@ -16,11 +17,13 @@1617   autoLockMinutes: number;1718   trashRetentionDays: number;1819   theme: "dark" | "light";20+  accentColor: AccentColorName;1921   onBreachMonitoringChange: (enabled: boolean) => void | Promise<void>;2022   onLockOnCloseChange: (enabled: boolean) => void | Promise<void>;2123   onAutoLockMinutesChange: (minutes: number) => void | Promise<void>;2224   onTrashRetentionChange: (days: number) => void | Promise<void>;2325   onThemeChange: (theme: "dark" | "light") => void;26+  onAccentChange: (name: AccentColorName) => void | Promise<void>;2427   onBack: () => void;2528   onImport: () => void;2629   onExport: () => void;@@ -364,7 +367,7 @@364367             </div>365368             <div class="peach-sync-pro-gate-overlay">366369               <span class="peach-sync-pro-badge">Pro</span>367-              <a href="https://peachpasswords.com/#pricing" target="_blank" rel="noopener noreferrer" class="peach-link" style="color:#D96830;font-size:13px;font-weight:500">370+              <a href="https://peachpasswords.com/#pricing" target="_blank" rel="noopener noreferrer" class="peach-link" style="color:var(--peach-accent);font-size:13px;font-weight:500">368371                 Upgrade to Pro →369372               </a>370373             </div>@@ -780,6 +783,23 @@780783               </div>781784             </div>782785           </Show>786+          <div class="peach-settings-row peach-settings-row--meta peach-settings-row--accent">787+            <span>Accent</span>788+            <div class="peach-accent-picker">789+              <For each={ACCENT_COLORS}>790+                {(color) => (791+                  <button792+                    class="peach-accent-swatch"793+                    classList={{ "is-active": props.accentColor === color.name }}794+                    style={{ background: color.hex }}795+                    onClick={() => void props.onAccentChange(color.name)}796+                    aria-label={color.name}797+                    title={color.name}798+                  />799+                )}800+              </For>801+            </div>802+          </div>783803           <div class="peach-settings-row peach-settings-row--meta">784804             <span>Theme</span>785805             <button@@ -888,7 +908,7 @@888908                   target="_blank"889909                   rel="noopener noreferrer"890910                   class="peach-link"891-                  style="color:#D96830"911+                  style="color:var(--peach-accent)"892912                 >893913                   Purchase →894914                 </a>

view file

src/popup/views/contracts.test.tsx+4 −0
@@ -288,11 +288,13 @@288288         autoLockMinutes={5}289289         trashRetentionDays={30}290290         theme="dark"291+        accentColor="peach"291292         onBreachMonitoringChange={vi.fn()}292293         onLockOnCloseChange={vi.fn()}293294         onAutoLockMinutesChange={vi.fn()}294295         onTrashRetentionChange={vi.fn()}295296         onThemeChange={vi.fn()}297+        onAccentChange={vi.fn()}296298         onBack={vi.fn()}297299         onImport={vi.fn()}298300         onExport={vi.fn()}@@ -335,11 +337,13 @@335337         autoLockMinutes={5}336338         trashRetentionDays={30}337339         theme="dark"340+        accentColor="peach"338341         onBreachMonitoringChange={vi.fn()}339342         onLockOnCloseChange={vi.fn()}340343         onAutoLockMinutesChange={onAutoLockMinutesChange}341344         onTrashRetentionChange={vi.fn()}342345         onThemeChange={onThemeChange}346+        onAccentChange={vi.fn()}343347         onBack={vi.fn()}344348         onImport={vi.fn()}345349         onExport={vi.fn()}

view file

src/shared/types.ts+17 −0
@@ -158,6 +158,23 @@158158   | "VAULT_LOCKED"    // background → content/popup: vault just locked159159   | "VAULT_UNLOCKED"; // background → content/popup: vault just unlocked160160 161+export const ACCENT_COLORS = [162+  { name: "peach",    hex: "#E8956D" },163+  { name: "rose",     hex: "#E8909A" },164+  { name: "plum",     hex: "#C49AC8" },165+  { name: "lavender", hex: "#9FA8D4" },166+  { name: "sky",      hex: "#85BAD4" },167+  { name: "mint",     hex: "#7EC8A4" },168+  { name: "sage",     hex: "#9AB87A" },169+  { name: "butter",   hex: "#D4B96A" },170+  { name: "sand",     hex: "#C8A882" },171+  { name: "clay",     hex: "#C4826A" },172+  { name: "slate",    hex: "#8A9EAA" },173+  { name: "stone",    hex: "#A89E92" },174+] as const;175+176+export type AccentColorName = typeof ACCENT_COLORS[number]["name"];177+161178 export interface PasswordPolicy {162179   minLength?: number;163180   maxLength?: number;

view file