Add customizable theme color (#582)

This commit is contained in:
Flaminel
2026-04-26 01:28:07 +03:00
committed by GitHub
parent db2e3e71db
commit 9f48d3565a
57 changed files with 774 additions and 135 deletions

View File

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="M433.8 75.1C386.3 26.7 323.2 0 256 0S125.7 26.7 78.2 75.1C30.8 123.4 4.7 187.7 4.7 256s26.1 132.6 73.5 180.9C125.7 485.3 188.8 512 256 512s130.3-26.7 177.8-75.1c47.4-48.3 73.5-112.6 73.5-180.9s-26.1-132.6-73.5-180.9m-76.9 98.1c-7.4-13.4-21.5-19.8-37.8-17-5.6.9-10.9-2.8-11.9-8.4-.9-5.6 2.8-10.9 8.4-11.9 25-4.2 47.7 6.3 59.3 27.4 11.4 20.8 8.4 45.8-7.7 63.7-2 2.3-4.8 3.4-7.7 3.4-2.5 0-4.9-.9-6.9-2.6-4.2-3.8-4.6-10.3-.8-14.5 10.3-11.3 12.2-27 5.1-40.1M113.1 365.1c-28.2 0-51.1-22.2-51.1-49.6 0-14.7 6.6-27.9 17.1-37 16.2 27.7 33.8 56.4 50.1 84.1-5.2 1.6-10.5 2.5-16.1 2.5m121.8 54-21.2 12.2c-7.7 4.5-17.6 1.8-22.1-5.9l-30.1-52.1 49.2-28.4 30.1 52.1c4.5 7.8 1.8 17.7-5.9 22.1M150 352.4c-16.8-29-35.8-59.7-52.6-88.7 28.8-15.5 85.8-44.5 118.5-108.4 7.2-14.1 10.9-28.8 15.7-40.9 1.5-3.9 4.7-6.9 8.7-8 1.4-.4 3.1-.6 4.9-.4 4.4.4 8.6 2.8 11 7l103.3 177.7c.2.3.3.6.5.9 4.8 9.8-3.9 21-14.7 19.3-15.3-2.4-31.9-6.7-51.3-6.8-69.4-.5-116.4 34.5-144 48.3m284.4-164.2c-1.9 22.8-9.9 38-22.3 55.2-2.1 2.9-5.4 4.5-8.8 4.5-2.2 0-4.4-.7-6.3-2-4.8-3.5-6-10.2-2.5-15.1 10.4-14.6 16.8-26.5 18.3-44.4 1.8-22.3-5.8-42.6-21.4-57-17-15.7-41.8-22.8-66.2-18.8-5.9 1-11.4-3.1-12.4-8.9-1-5.9 3.1-11.4 8.9-12.4 31-5 62.5 4.1 84.4 24.3 20.7 19 30.7 45.6 28.3 74.6"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="M433.7 91a416.5 416.5 0 0 0-105.6-33.2c-4.6 8.2-9.9 19.3-13.5 28.1-39.4-5.9-78.4-5.9-117.1 0-3.7-8.8-9.1-19.9-13.7-28.1-37.1 6.4-72.6 17.7-105.7 33.3-66.8 101-85 199.5-75.9 296.6 44.3 33.1 87.3 53.2 129.6 66.4 10.4-14.4 19.7-29.6 27.7-45.7-15.3-5.8-29.9-13-43.7-21.3 3.7-2.7 7.2-5.6 10.7-8.5 84.2 39.4 175.8 39.4 259 0 3.5 2.9 7.1 5.8 10.7 8.5-13.9 8.3-28.5 15.5-43.8 21.3 8 16 17.3 31.3 27.7 45.7 42.3-13.2 85.3-33.3 129.6-66.4 10.8-112.5-18-210.1-76-296.7M170.9 328c-25.3 0-46-23.6-46-52.4s20.3-52.4 46-52.4 46.5 23.6 46 52.4c.1 28.8-20.2 52.4-46 52.4m170.2 0c-25.3 0-46-23.6-46-52.4s20.3-52.4 46-52.4 46.5 23.6 46 52.4c0 28.8-20.3 52.4-46 52.4"/></svg>

After

Width:  |  Height:  |  Size: 747 B

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="M291.1 190.6c-9.5-5.2-20.3-8.3-31.7-8.9V20.9L83 322.9h136.7c9.5 5.2 20.3 8.3 31.7 8.9v160.8l16.2-27.7 160.2-274.3zm-39.7-8.9c-39.7 2.1-71.2 34.9-71.2 75.1 0 23.4 10.7 44.4 27.5 58.1H97L251.7 49.2zm7.5 283.5.4-133.4c39.7-2.1 71.2-34.9 71.2-75.1 0-23.4-10.7-44.4-27.5-58.1h110.8zm34.2-282.6c-8.1-4.1-16.7-6.9-25.7-8.2v-71.6c36.7 2.8 70.8 18.4 97.2 44.8 10.5 10.5 19.3 22.3 26.3 35zm-75.4 148.3c8.1 4.1 16.7 6.9 25.7 8.2v71.6c-36.7-2.8-70.8-18.4-97.2-44.8a156 156 0 0 1-26.3-35zm97.5 68.2L408.7 239c.7 5.8 1 11.8 1 17.7 0 41.2-16.1 80-45.2 109.2-14.3 14.4-31.1 25.6-49.3 33.2M512 256c0 141.4-114.6 256-256 256-2.2 0-4.4 0-6.6-.1l23-39.3c23.2-1.8 45.9-7.3 67.3-16.4 25.8-10.9 48.9-26.5 68.8-46.4s35.5-43 46.4-68.8c11.3-26.7 17-55.1 17-84.3s-5.7-57.6-17-84.3c-10.9-25.8-26.5-48.9-46.4-68.8s-43-35.5-68.8-46.4c-23-9.7-47.3-15.3-72.3-16.7V.3C403.5 6.2 512 118.4 512 256M171.1 456.3c23 9.7 47.3 15.3 72.3 16.7v38.7C107.8 505.1 0 393.1 0 256 0 114.6 114.6 0 256 0c2.1 0 4.2 0 6.3.1l-23.8 40.8c-23.2 1.8-45.9 7.3-67.3 16.4-25.8 10.9-48.9 26.5-68.8 46.4s-35.5 43-46.4 68.8c-11.3 26.7-17 55.1-17 84.3s5.7 57.6 17 84.3c10.9 25.8 26.5 48.9 46.4 68.8 19.8 19.8 42.9 35.4 68.7 46.4m24.4-341.9L102 274.5c-.7-5.8-1-11.8-1-17.7 0-41.2 16.1-80 45.2-109.2 14.4-14.4 31.1-25.6 49.3-33.2"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="M443.1 32.7h-365C40.9 32.7 9 62 9 99.2l.4 311.2-9.4 69 127.1-33.8H443c37.2 0 69.1-29.3 69.1-66.5V99.2c0-37.2-31.9-66.5-69-66.5m22 346.3c0 10-9 19.8-22.1 19.6H120.2l-64.6 19.5.7-3.8-.4-315.1c0-10.1 9.1-19.6 22.2-19.6H443c13.1 0 22.1 9.5 22.1 19.6zM110.5 139.7l124.6 67.9V254l-116.4 63.3-8.2 4.5v-50.1l76.6-40.6.5-.2-.5-.2-76.6-40.6zm158.2 152.4h132.4v46H268.7z"/></svg>

After

Width:  |  Height:  |  Size: 460 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="M428.7 67C324.3-28.4 162.4-21.1 67 83.3S-21.1 349.6 83.3 445s266.3 88.1 361.7-16.3S533.1 162.4 428.7 67m-43 115.7c-3.1 13.2-8.9 26.6-17.5 39.9-8.6 13.4-19.4 25.5-32.3 36.3-13 10.8-27.8 19.6-44.6 26.4s-34.6 10.1-53.4 10.1h-2.1L182 415.9h-60.8l119.6-268.7 64.2-8.5-62.5 141.1c11-.8 21.8-4.6 32.3-11.2 10.6-6.6 20.3-14.9 29.2-24.9s16.5-21.1 23-33.4 11.1-24.3 13.9-36.1c1.7-7.3 2.5-14.4 2.3-21.1-.1-6.8-1.9-12.7-5.3-17.7s-8.5-9.2-15.4-12.3-16.3-4.6-28.1-4.6c-13.8 0-27.4 2.3-40.8 6.8s-25.8 11.1-37.2 19.7-21.3 19.3-29.8 32.1-14.5 27.4-18.2 43.7c-1.4 5.4-2.3 9.6-2.5 12.9-.3 3.2-.4 5.9-.2 8 .1 2.1.4 3.7.8 4.9.4 1.1.8 2.3 1.1 3.4q-21.6 0-31.5-8.7c-9.9-8.7-8.2-15.8-4.9-30.2 3.4-14.9 11.1-29.2 23-42.7 12-13.5 26.2-25.4 42.7-35.7s34.5-18.4 54.1-24.5 38.7-9.1 57.3-9.1c16.3 0 30.1 2.3 41.2 7s19.8 10.8 26 18.4 10.1 16.5 11.6 26.6c1.6 10 1.1 20.6-1.4 31.6" style="fill-rule:evenodd;clip-rule:evenodd"/></svg>

After

Width:  |  Height:  |  Size: 992 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="m80.3 80.8 3.9 372.4c-31.4 3.9-54.9-11.8-54.9-43.1l-3.9-309.7c0-98 90.2-121.5 145.1-82.3l278.3 160.7c39.2 27.4 47 78.4 27.4 113.7-3.9-27.4-15.7-43.1-39.2-58.8L123.4 57.2C99.9 41.6 80.3 45.5 80.3 80.8m-23.5 392c23.5 7.8 47 3.9 66.6-7.8l321.5-188.2c19.6 27.4 15.7 54.9-7.8 70.6L166.5 504.2c-39.2 19.6-90.1 0-109.7-31.4M150.9 363 343 253.3 154.8 147.4z"/></svg>

After

Width:  |  Height:  |  Size: 450 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="M255.2.4C114 .4-.4 114.8-.4 256S114 511.6 255.2 511.6 510.8 397.2 510.8 256 396.4.4 255.2.4m156.2 411.8c-20.3 20.3-43.9 36.2-70.2 47.3-27.2 11.5-56.2 17.4-86 17.4s-58.8-5.8-86-17.4c-26.3-11.1-49.9-27.1-70.2-47.3-20.3-20.3-36.2-43.9-47.3-70.2-11.5-27.2-17.4-56.2-17.4-86s5.8-58.8 17.4-86c11.1-26.3 27.1-49.9 47.3-70.2 20.3-20.3 43.9-36.2 70.2-47.3 27.2-11.5 56.2-17.4 86-17.4s58.8 5.8 86 17.4c26.3 11.1 49.9 27.1 70.2 47.3 20.3 20.3 36.2 43.9 47.3 70.2 11.5 27.2 17.4 56.2 17.4 86s-5.8 58.8-17.4 86c-11 26.3-27 49.9-47.3 70.2m-95.9-266.1c-15 4.8-32.2 11.8-46.3 21.6-.2.2-.4.3-.6.4-.3.2-.7.4-2 1.4l-1.2.8c-6.7 4.1-9.8 10.2-9.4 14.2v1.1c0 1.2-.1 3-.1 5.3-.1 4.5-.2 11-.3 19-.2 9.9-.4 22-.5 35.3-.1-11.3-.2-21.6-.3-30.2-.1-7.7-.2-14.1-.2-18.5 0-2.2-.1-4-.1-5.2v-1.8c0-.2 0-.5-.1-.9-.1-.5-.2-.9-.3-1.4-1.3-5-5.6-15.9-13.5-19.4-.1-.1-.2-.2-.4-.3-14.1-9.8-31.3-16.8-46.4-21.6 18.5-10.3 39.3-15.6 60.9-15.6 21.5.2 42.3 5.5 60.8 15.8M449.2 256c0-32-7.7-62.2-21.5-88.8 0-1.9.1-3.3.1-4.1l.1-.1v-1c-.3-24.2-7.2-26.6-15.5-27.1-.7 0-1.4-.1-2-.2h-.4c-1.1.1-2.2.1-3.2.2C371.2 90.5 316.5 62 255.2 62c-61.5 0-116.4 28.7-151.9 73.4-1.3-.1-2.7-.2-4-.2h-.4c-.6.1-1.3.1-2 .2-8.3.5-15.2 2.9-15.5 27.1v1l.1.1c0 1.1.1 3.1.1 6-13 26.1-20.3 55.4-20.3 86.5 0 32.8 8.2 63.7 22.5 90.8.1 8.1.2 15.8.3 22.7 0 2.9 1.5 8.5 7.2 8.9.3.1.8.2 1.6.3 4 .7 8.9 1.7 14.5 2.9C143 423.5 196 450 255.2 450s112.2-26.5 147.8-68.3c5.1-1.1 9.6-2 13.3-2.6.8-.1 1.3-.2 1.6-.3 5.7-.4 7.2-6 7.2-8.9.1-6.3.2-13.3.3-20.6 15.2-27.7 23.8-59.5 23.8-93.3m-383.1 0c0-26.8 5.6-52.2 15.7-75.3v.5q-.45 4.2-.6 9.6v.5c.3 6.4 1.5 84 2.4 144-11.2-24.1-17.5-51-17.5-79.3m185.7 172.9v-9c0-3.6 0-8.2-.1-13.7.3-1.5.4-3.4.1-5.6v-9c0-5.6-.1-13.6-.1-23.9-.1-19.1-.3-44.7-.6-72.3-.2-27.5-.5-53.2-.7-72.3-.1-10.3-.2-18.3-.3-23.9 0-2.9-.1-5.2-.1-6.7 0-.8 0-1.4-.1-1.9v-.5c0-.1 0-.2-.1-.4 0-.2-.1-.4-.1-.5-.8-3.3-4.5-14.1-11.4-16.9-.2-.1-.5-.3-.8-.5-16.2-11.3-54.1-30.4-128.2-35.9C144 93.8 196.5 67 255.2 67c58.4 0 110.7 26.6 145.4 68.4-74.6 5.4-112.6 24.7-128.9 36.1-.3.2-.6.4-.8.5-6.9 2.8-10.6 13.6-11.4 16.9-.1.1-.1.3-.1.5s-.1.3-.1.4v.4c0 .5 0 1.1-.1 2 0 1.6-.1 3.8-.1 6.7-.1 5.6-.2 13.6-.3 23.9-.2 19.1-.5 44.9-.7 72.5s-.5 53.3-.6 72.4c-.1 10.3-.1 18.3-.1 23.9v9c-.3 2-.2 3.7 0 5.2 0 5.8-.1 10.7-.1 14.4v9c-1 6.9 1.6 9.8 4 10.9 1 .5 2.3.8 3.4.8.8 0 1.4-.1 1.8-.4 42-32.5 94.6-49.1 128.2-57-34.6 37.8-84.3 61.6-139.5 61.6s-104.9-23.7-139.5-61.5c33.6 8 85.4 24.6 126.9 56.6.4.3 1 .4 1.8.4 1.1 0 2.3-.3 3.4-.8 2.3-1.2 4.9-4 4-10.9m176.1-237.7v-.6c0-3.4-.2-6.4-.5-9 0-1.2 0-2.3.1-3.4 10.8 23.8 16.8 50.1 16.8 77.9 0 29.4-6.7 57.3-18.8 82.1.9-60.6 2.2-140.6 2.4-147M410 139.7s.1 0 0 0c.7.1 1.4.1 2.1.2 5.4.3 10.5.7 10.8 22.1 0 .5-.1 1.1-.1 2 0 1-.1 2.4-.1 4.1-2.8-3-6.2-3.2-9.2-3.4-.7 0-1.4-.1-2.1-.2h-.2c-81.7 4.4-122.7 24.7-139.8 36.4-.4.3-.8.5-.9.6-2.2 1.9-4.4 4.7-6.3 7.5.1-11.3.3-18.6.3-19.5.7-2.6 4.1-11.5 8.5-13.1l.2-.1c.3-.2.7-.4 1.4-.9 16.5-11.4 56.1-31.3 135.4-35.7m-312.8.6c.7 0 1.4-.1 2.1-.2 79.4 4.4 118.9 24.2 135.4 35.7.7.5 1.1.7 1.4.9l.2.1c4.6 1.7 8.1 11.4 8.5 13.4l.1 1.3c.1 2.5.1 8.1.2 16.1-1.7-2.6-3.9-4.9-6.4-5.9-.2-.1-.5-.4-.9-.6-17.1-11.7-58.1-31.9-139.8-36.3h-.2c-.7.1-1.4.1-2.1.2-3 .2-6.4.4-9.2 3.3 0-1.6-.1-2.9-.1-3.8s0-1.6-.1-2c.4-21.5 5.5-21.8 10.9-22.2"/></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="M144.2 103.3c30.7 30.7 70 38.6 112.4 38.6 43.6 0 82.8-8.4 114.7-40.4 14.7-14.7 44.4-44.3 45.5-45.4C371.1 18.7 317.6 0 256.2 0c-60.8 0-114 18.5-159.8 55.5zM373 258.4c0 42.3 6.7 81.2 38.2 112.7 22.9 22.9 44.7 44.5 45 44.8 37-45.8 55.6-99.1 55.6-159.9 0-58.9-17.4-110.8-52.3-155.9L406.6 153c-30.9 31-33.6 57.9-33.6 105.4m-271.1 113c32.7-32.7 38-70.6 38-113.1 0-41.3-6.8-79.9-36.8-110-20.1-20-47.6-47.2-49.7-49.4-31.8 40.3-49.2 86.4-52.3 138.3-.3.6-.5 1.1-.5 1.7C.3 244.3.2 250 .2 256c0 5.7.2 11.3.4 17 .5 10.2 1.7 20.3 3.4 30.2 7.3 42.1 24.8 80 52.7 113.6.1-.2 23.2-23.4 45.2-45.4m269.6 46c-36.8-36.8-66.1-40.4-114.7-40.4-46.7 0-78.4 4.3-112.6 38.5-20.2 20.3-43.4 43.6-43.8 43.9 2.2 1.7 4.4 3.3 6.6 4.9 43 31.8 92.7 47.7 149.3 47.7q84.75 0 149.4-47.7c2.5-1.7 4.9-3.5 7.3-5.4zM186 269.1c-.5-2.8-.8-5.5-.9-8.4-.1-1.6-.1-3.1-.1-4.7 0-1.7 0-3.2.1-4.7 0-.2 0-.3.1-.5 1-17.4 7.9-32.4 20.5-45.1 13.9-13.8 30.6-20.7 50.2-20.7s36.3 6.9 50.2 20.7c13.8 14 20.7 30.8 20.7 50.3s-6.9 36.2-20.7 50.2c-.5.5-1 1.1-1.5 1.5q-3.45 3.3-7.2 6-18 13.2-41.4 13.2c-23.4 0-29.4-4.4-41.3-13.2-3.1-2.2-6.1-4.7-8.9-7.6-10.8-10.6-17.3-22.9-19.8-37" style="fill-rule:evenodd;clip-rule:evenodd"/><path d="m375.2 143.5-1.6-1.6v-.1L440 77.2l-1.4-1.4-66.4 64.6.7.7-.7-.7h-.1l-1.9-1.9-40 40.6 5 5zm-238.3 2.1 40.6 40.5 5-5-40.6-40.5-1.7 1.7-66.4-66.1-1.4 1.4 66.4 66.1zm234.9 223.9-42.6-42.4-5 5 42.6 42.4 1.8-1.8 65.6 67.8 1.4-1.4-65.5-67.9zm-233.3 2.1 1.9 1.9-64.3 64.4 1.4 1.4 64.4-64.5 1.6 1.6 39.5-41.1-5-4.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256 256-114.6 256-256S397.4 0 256 0m-46.1 291.2c-.7.8-2.8 3.4-2.5 6.7l-4 42.5-21.3-59c2.1-1.3 5-3.2 8.7-5.5 51-32.1 88.1-55.3 111.1-69.4-22.2 21.6-58 54.4-91.6 84.3zm4 66.3 4.5-48.3c6.6 4.4 16 10.8 26.5 18-17.4 17.7-26.4 26.2-31 30.3m163-202.7v.3c0 .9-.1 1.9-.2 3.2-.1.5-.1 1.1-.2 1.7v.1c-1.5 23-45.1 198.8-45.5 200.6-.1.3-1.8 6.5-7 6.7-3.2.1-6.3-1.1-8.5-3.3l-.3-.3c-17.6-15.1-74.7-53.8-94.4-66.9 7.8-7 30.7-27.5 53.5-48.6 57.8-53.3 59.3-58.5 60.1-61.3l.1-.2c.5-2.2-.1-4.4-1.6-5.9-1.7-1.7-4.2-2.3-6.9-1.6l-.5.2c-4.9 1.8-47 27.7-140.7 86.8-4.3 2.7-7.6 4.8-9.7 6.1l-61.7-20.1c-1.7-.8-2.3-1.7-1.9-2.9 0-.1.4-.6 2.5-2 9.8-6.7 157.8-61.1 255-96 2.4-.8 5.9-1.3 7.2-.8l.3.1c.1 0 .2.1.2.2v.2c.1 1.1.3 2.5.2 3.7"/></svg>

After

Width:  |  Height:  |  Size: 846 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="M256 512c-34.6 0-68.1-6.8-99.7-20.1C125.9 479 98.5 460.5 75 437s-42-50.9-54.9-81.4C6.8 324.1 0 290.6 0 256s6.8-68.1 20.1-99.7C33 125.9 51.5 98.5 75 75s50.9-42 81.4-54.9C187.9 6.8 221.4 0 256 0s68.1 6.8 99.7 20.1C386.1 33 413.5 51.5 437 75s42 50.9 54.9 81.4c13.4 31.6 20.1 65.1 20.1 99.7s-6.8 68.1-20.1 99.7C479 386.1 460.5 413.5 437 437s-50.9 42-81.4 54.9c-31.5 13.3-65 20.1-99.6 20.1m0-486.2C129.1 25.8 25.8 129.1 25.8 256S129.1 486.2 256 486.2 486.2 382.9 486.2 256 382.9 25.8 256 25.8M59.4 413.3c-4.4 0-5.9-3.2-4.4-9.5 1.4-6.1 5.5-13.8 12.1-23.1l98-114.8q45.15-52.95 69-86.7c23.85-33.75 24.9-38.6 27.1-47.9 1-4.4.4-7.7-2.1-9.9-2.4-2.2-5.5-3.7-9.2-4.5s-7.3-1.2-10.7-1.2c-20.8 0-53.2 14.3-97.2 43-12.4 8-24.2 16.9-35.4 26.9q-16.8 14.85-32.1 31.8c-20 22.6-31.9 41.6-35.5 57-2.8 11.8.4 19 9.6 21.5 3.4 1.1 7.4 1.7 12.1 1.7 8.8 0 18.2-2.5 28.2-7.4 10-5 20-11.3 30.2-19 7.8-6.1 12.2-9.1 13.2-9.1s1.1 1 .4 2.9c-2.8 5.8-7.7 11.3-14.6 16.5s-14.7 10-23.6 14.3c-8.8 4.3-17.5 7.6-26 9.9s-15.8 3.5-21.9 3.5c-9.8 0-17.7-2.8-23.7-8.5-6-5.6-7.7-14-5.1-25 4-16.8 17.6-37.5 40.7-62 22.2-23.7 47.8-44.8 76.6-63.2 11.9-7.7 25.3-15.1 40.5-22.1 15.1-7 30.1-12.8 44.8-17.3 14.8-4.5 27.6-6.8 38.3-6.8 19.3 0 27.3 7.2 23.9 21.5-4.1 17.4-22.8 47.2-56.1 89.6-23.3 29.2-53.2 63.9-89.6 104.1-30.2 33.3-45.4 50-45.6 50 .7 0 15.8-14.2 45.3-42.5 18.1-17.6 35-33.4 50.7-47.3 15.6-13.9 30-26.1 43-36.6 6.4-5.2 14.2-11.8 23.4-19.6s19.9-17 32.2-27.5c10.6-8.8 20.9-16.8 30.8-24s18.3-10.7 25.1-10.7c7.1 0 11.3 1.9 12.6 5.8-29.9 31.4-60.1 69.8-90.4 115.3-21.6 32.5-34.4 57.1-38.4 73.9-2.3 9.9-1.7 17.1 1.9 21.5q5.4 6.6 16.8 6.6c8.3 0 17.7-2.6 28.1-7.8s21.1-12 32.1-20.2c11-8.3 21.5-16.9 31.5-26 35.7-31.9 66.3-66.5 91.7-103.7 19.9-29.2 31.9-52.5 36-69.8q.6-2.55.9-4.5c.2-1.4.3-2.6.3-3.7-5.1 0-9.9-3-14.4-9.1-2.9-3.3-3.9-6.7-3.1-10.3.8-3.3 2.7-6.1 5.6-8.3 3-2.2 6-3.3 9.2-3.3 4.6 0 7.5 2.3 8.6 7 .5 2.2 1.8 5 3.9 8.3 1.4 2.2 1.9 3.9 1.4 5 .9 2.5 3 3.7 6.5 3.7 3.4 0 7.1-1.7 10.9-5s7.6-5 11.3-5c2 0 2.7.8 2.3 2.5q-1.05 4.5-9 8.7c-2.9 1.9-6.1 3.2-9.7 3.7q-3 .45-6 .6c-3 .15-3.9-.1-5.7-.6.3 11-.1 19.1-1.4 24.4-5.2 22-21.3 50.5-48.1 85.5-13.5 17.6-28.1 34.3-43.8 50s-32.6 30.4-50.8 44.2c-36.3 28.1-65.9 42.1-88.9 42.1-25.2 0-34.7-13.2-28.4-39.7 2.9-12.4 8.6-26.5 17-42.3s17.6-31.6 27.7-47.3l41.8-64.9c-13 9.4-23.5 17-31.7 22.9-8.1 5.9-14.9 10.9-20.5 15.1-5.5 4.1-10.8 8.3-15.8 12.6s-11.1 9.4-18.2 15.5c-7.7 6.9-18.3 16.7-31.6 29.5s-29.1 28.7-47.3 47.7l-56.5 58.7c-3.2 3.3-6.8 6.3-10.6 9.1-3.6 2.2-7.1 3.6-10.2 3.6"/></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -52,6 +52,7 @@ import {
tablerHistory,
tablerGripVertical,
tablerFilter,
tablerPalette,
} from '@ng-icons/tabler-icons';
import { routes } from './app.routes';
@@ -114,6 +115,7 @@ export const appConfig: ApplicationConfig = {
tablerHistory,
tablerGripVertical,
tablerFilter,
tablerPalette,
}),
],
};

View File

@@ -128,6 +128,13 @@ export const routes: Routes = [
'@features/settings/account/account-settings.component'
).then((m) => m.AccountSettingsComponent),
},
{
path: 'appearance',
loadComponent: () =>
import(
'@features/settings/appearance/appearance-settings.component'
).then((m) => m.AppearanceSettingsComponent),
},
],
},
],

View File

@@ -2,19 +2,68 @@ import { Injectable, signal, effect } from '@angular/core';
export type Theme = 'dark' | 'light';
export const ACCENT_PRESETS = [
'default',
'blue',
'green',
'rose',
'amber',
'teal',
] as const;
export type AccentPreset = (typeof ACCENT_PRESETS)[number];
export type Accent = AccentPreset | 'custom';
// Preview swatch colors for each preset. These mirror the --brand-500 stop
// declared in styles/_accents.scss (and styles/_tokens.scss for 'default').
// Keep the two in sync — there is no SCSS-from-TS import path.
export const ACCENT_PRESET_HEX: Record<AccentPreset, string> = {
default: '#8b5cf6',
blue: '#3b82f6',
green: '#10b981',
rose: '#f43f5e',
amber: '#f59e0b',
teal: '#14b8a6',
};
const THEME_KEY = 'cleanuparr-theme';
const PERFORMANCE_MODE_KEY = 'cleanuparr-performance-mode';
const FULL_WIDTH_KEY = 'cleanuparr-full-width';
const ACCENT_KEY = 'cleanuparr-accent';
const CUSTOM_ACCENT_KEY = 'cleanuparr-custom-accent';
const DEFAULT_CUSTOM_ACCENT = '#8b5cf6';
const HEX_COLOR_REGEX = /^#[0-9a-f]{6}$/i;
const BRAND_SHADES = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const;
// Lightness stops per shade, tuned to match the visual weight of the default purple scale.
const LIGHTNESS_STOPS: Record<(typeof BRAND_SHADES)[number], number> = {
50: 97,
100: 93,
200: 86,
300: 75,
400: 62,
500: 50,
600: 42,
700: 34,
800: 27,
900: 20,
950: 12,
};
@Injectable({ providedIn: 'root' })
export class ThemeService {
private readonly root = document.documentElement;
private readonly _theme = signal<Theme>('dark');
private readonly _performanceMode = signal(false);
private readonly _fullWidth = signal(false);
private readonly _accent = signal<Accent>('default');
private readonly _customAccent = signal<string>(DEFAULT_CUSTOM_ACCENT);
readonly theme = this._theme.asReadonly();
readonly performanceMode = this._performanceMode.asReadonly();
readonly fullWidth = this._fullWidth.asReadonly();
readonly accent = this._accent.asReadonly();
readonly customAccent = this._customAccent.asReadonly();
constructor() {
this.restoreFromStorage();
@@ -55,6 +104,38 @@ export class ThemeService {
localStorage.setItem(FULL_WIDTH_KEY, String(value));
}
setAccent(accent: Accent): void {
this._accent.set(accent);
localStorage.setItem(ACCENT_KEY, accent);
}
/**
* Picks the right icon variant for the active theme. Asset filenames must
* follow the `*-light.svg` (designed for dark backgrounds) /
* `*-dark.svg` (designed for light backgrounds) convention.
*/
themedIconSrc(src: string): string {
if (this._theme() === 'dark')
{
return src;
}
return src.replace('-light.svg', '-dark.svg');
}
setCustomAccent(hex: string): void {
const normalized = hex.trim().toLowerCase();
if (!HEX_COLOR_REGEX.test(normalized))
{
return;
}
this._customAccent.set(normalized);
localStorage.setItem(CUSTOM_ACCENT_KEY, normalized);
if (this._accent() !== 'custom')
{
this.setAccent('custom');
}
}
private restoreFromStorage(): void {
const savedTheme = localStorage.getItem(THEME_KEY);
if (savedTheme === 'light' || savedTheme === 'dark') {
@@ -70,6 +151,18 @@ export class ThemeService {
if (savedFullWidth === 'true') {
this._fullWidth.set(true);
}
const savedAccent = localStorage.getItem(ACCENT_KEY);
const migratedAccent = savedAccent === 'purple' ? 'default' : savedAccent;
if (migratedAccent && this.isAccent(migratedAccent)) {
this._accent.set(migratedAccent);
}
const savedCustom = localStorage.getItem(CUSTOM_ACCENT_KEY);
if (savedCustom && HEX_COLOR_REGEX.test(savedCustom))
{
this._customAccent.set(savedCustom);
}
}
private detectSystemPreferences(): void {
@@ -81,15 +174,111 @@ export class ThemeService {
private bindToDom(): void {
effect(() => {
document.documentElement.setAttribute('data-theme', this._theme());
this.root.setAttribute('data-theme', this._theme());
});
effect(() => {
document.documentElement.setAttribute('data-performance-mode', String(this._performanceMode()));
this.root.setAttribute('data-performance-mode', String(this._performanceMode()));
});
effect(() => {
document.documentElement.setAttribute('data-full-width', String(this._fullWidth()));
this.root.setAttribute('data-full-width', String(this._fullWidth()));
});
effect(() => {
const accent = this._accent();
this.root.setAttribute('data-accent', accent);
if (accent === 'custom')
{
this.applyCustomAccent(this._customAccent());
}
else
{
this.clearInlineAccent();
}
});
}
private isAccent(value: string): value is Accent {
return value === 'custom' || (ACCENT_PRESETS as readonly string[]).includes(value);
}
private applyCustomAccent(hex: string): void {
const rgb = hexToRgb(hex);
if (!rgb)
{
return;
}
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
// Keep some chroma even when the user picks a near-gray, otherwise the whole
// brand scale collapses to shades of gray and active states become invisible.
const s = Math.max(hsl.s, 15);
for (const shade of BRAND_SHADES)
{
const l = shade === 500 ? hsl.l : LIGHTNESS_STOPS[shade];
const { r, g, b } = hslToRgb(hsl.h, s, l);
this.root.style.setProperty(`--brand-${shade}`, rgbToHex(r, g, b));
}
this.root.style.setProperty('--accent-rgb', `${rgb.r}, ${rgb.g}, ${rgb.b}`);
}
private clearInlineAccent(): void {
for (const shade of BRAND_SHADES)
{
this.root.style.removeProperty(`--brand-${shade}`);
}
this.root.style.removeProperty('--accent-rgb');
}
}
function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
const match = /^#([0-9a-f]{6})$/i.exec(hex.trim());
if (!match) return null;
const n = parseInt(match[1], 16);
return { r: (n >> 16) & 0xff, g: (n >> 8) & 0xff, b: n & 0xff };
}
function rgbToHex(r: number, g: number, b: number): string {
const clamp = (v: number) => Math.max(0, Math.min(255, Math.round(v)));
const hex = (v: number) => clamp(v).toString(16).padStart(2, '0');
return `#${hex(r)}${hex(g)}${hex(b)}`;
}
function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
const rn = r / 255, gn = g / 255, bn = b / 255;
const max = Math.max(rn, gn, bn);
const min = Math.min(rn, gn, bn);
const l = (max + min) / 2;
let h = 0, s = 0;
if (max !== min) {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case rn: h = (gn - bn) / d + (gn < bn ? 6 : 0); break;
case gn: h = (bn - rn) / d + 2; break;
case bn: h = (rn - gn) / d + 4; break;
}
h *= 60;
}
return { h, s: s * 100, l: l * 100 };
}
function hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {
const sn = s / 100, ln = l / 100;
const c = (1 - Math.abs(2 * ln - 1)) * sn;
const hp = h / 60;
const x = c * (1 - Math.abs((hp % 2) - 1));
let r1 = 0, g1 = 0, b1 = 0;
if (hp >= 0 && hp < 1) [r1, g1, b1] = [c, x, 0];
else if (hp < 2) [r1, g1, b1] = [x, c, 0];
else if (hp < 3) [r1, g1, b1] = [0, c, x];
else if (hp < 4) [r1, g1, b1] = [0, x, c];
else if (hp < 5) [r1, g1, b1] = [x, 0, c];
else [r1, g1, b1] = [c, 0, x];
const m = ln - c / 2;
return { r: (r1 + m) * 255, g: (g1 + m) * 255, b: (b1 + m) * 255 };
}

View File

@@ -130,16 +130,16 @@
font-family: var(--font-family);
font-size: var(--font-size-sm);
font-weight: 500;
color: #ffffff;
background: #7E57C2;
color: var(--color-primary-text);
background: var(--color-primary);
border: 1px solid transparent;
border-radius: var(--radius-lg);
cursor: pointer;
transition: all var(--duration-fast) var(--ease-default);
&:hover:not(:disabled) {
background: #6D28D9;
box-shadow: 0 0 20px rgba(126, 87, 194, 0.4);
background: var(--color-primary-hover);
box-shadow: 0 0 20px rgba(var(--accent-rgb), 0.4);
}
&:active:not(:disabled) {

View File

@@ -47,15 +47,15 @@
transition: all var(--duration-normal) var(--ease-default);
.step-group.active & {
background: rgba(126, 87, 194, 0.15);
background: rgba(var(--accent-rgb), 0.15);
color: var(--color-primary);
box-shadow: 0 0 8px rgba(126, 87, 194, 0.25);
box-shadow: 0 0 8px rgba(var(--accent-rgb), 0.25);
}
.step-group.completed & {
background: var(--color-primary);
color: #ffffff;
box-shadow: 0 0 12px rgba(126, 87, 194, 0.35);
box-shadow: 0 0 12px rgba(var(--accent-rgb), 0.35);
}
}

View File

@@ -30,7 +30,7 @@
&:hover {
background: var(--glass-bg-hover);
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(126, 87, 194, 0.12);
box-shadow: 0 8px 24px rgba(var(--accent-rgb), 0.12);
}
// GitHub — monochrome grey/white
@@ -278,8 +278,8 @@
}
&--important {
background: rgba(126, 87, 194, 0.08);
border-color: rgba(126, 87, 194, 0.25);
background: rgba(var(--accent-rgb), 0.08);
border-color: rgba(var(--accent-rgb), 0.25);
animation: slide-up var(--duration-normal) var(--ease-default), glow-pulse-important 3s ease-in-out infinite;
animation-delay: 0s, var(--duration-normal);
}
@@ -533,9 +533,9 @@
box-shadow: 0 0 8px rgba(34, 197, 94, 0.25);
}
&--primary {
background: rgba(126, 87, 194, 0.15);
background: rgba(var(--accent-rgb), 0.15);
color: var(--color-primary);
box-shadow: 0 0 8px rgba(126, 87, 194, 0.25);
box-shadow: 0 0 8px rgba(var(--accent-rgb), 0.25);
}
&--default {
background: rgba(148, 163, 184, 0.15);
@@ -726,10 +726,10 @@
@keyframes glow-pulse-important {
0%, 100% {
box-shadow: 0 0 12px rgba(126, 87, 194, 0.1), 0 0 24px rgba(126, 87, 194, 0.05);
box-shadow: 0 0 12px rgba(var(--accent-rgb), 0.1), 0 0 24px rgba(var(--accent-rgb), 0.05);
}
50% {
box-shadow: 0 0 20px rgba(126, 87, 194, 0.25), 0 0 40px rgba(126, 87, 194, 0.1);
box-shadow: 0 0 20px rgba(var(--accent-rgb), 0.25), 0 0 40px rgba(var(--accent-rgb), 0.1);
}
}

View File

@@ -42,7 +42,7 @@
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(126, 87, 194, 0.06), transparent);
background: linear-gradient(90deg, transparent, rgba(var(--accent-rgb), 0.06), transparent);
transform: translateX(-100%);
transition: transform var(--duration-normal) var(--ease-default);
pointer-events: none;
@@ -233,8 +233,8 @@
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
margin-bottom: var(--space-3);
background: rgba(126, 87, 194, 0.1);
border: 1px solid rgba(126, 87, 194, 0.2);
background: rgba(var(--accent-rgb), 0.1);
border: 1px solid rgba(var(--accent-rgb), 0.2);
border-radius: var(--radius-md);
font-size: var(--font-size-sm);
color: var(--color-primary);

View File

@@ -44,7 +44,7 @@
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(126, 87, 194, 0.06), transparent);
background: linear-gradient(90deg, transparent, rgba(var(--accent-rgb), 0.06), transparent);
transform: translateX(-100%);
transition: transform var(--duration-normal) var(--ease-default);
pointer-events: none;
@@ -202,8 +202,8 @@
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
margin-bottom: var(--space-3);
background: rgba(126, 87, 194, 0.1);
border: 1px solid rgba(126, 87, 194, 0.2);
background: rgba(var(--accent-rgb), 0.1);
border: 1px solid rgba(var(--accent-rgb), 0.2);
border-radius: var(--radius-md);
font-size: var(--font-size-sm);
color: var(--color-primary);

View File

@@ -58,7 +58,7 @@
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(126, 87, 194, 0.06), transparent);
background: linear-gradient(90deg, transparent, rgba(var(--accent-rgb), 0.06), transparent);
transform: translateX(-100%);
transition: transform var(--duration-normal) var(--ease-default);
pointer-events: none;

View File

@@ -135,7 +135,7 @@
&__progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--color-primary), color-mix(in srgb, var(--color-primary) 75%, #c084fc));
background: linear-gradient(90deg, var(--color-primary), var(--brand-300));
border-radius: var(--radius-full);
transition: width var(--duration-normal) var(--ease-default);
min-width: 0;
@@ -307,7 +307,7 @@
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(126, 87, 194, 0.06), transparent);
background: linear-gradient(90deg, transparent, rgba(var(--accent-rgb), 0.06), transparent);
transform: translateX(-100%);
transition: transform var(--duration-normal) var(--ease-default);
pointer-events: none;

View File

@@ -49,7 +49,7 @@
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(126, 87, 194, 0.06), transparent);
background: linear-gradient(90deg, transparent, rgba(var(--accent-rgb), 0.06), transparent);
transform: translateX(-100%);
transition: transform var(--duration-normal) var(--ease-default);
pointer-events: none;

View File

@@ -0,0 +1,65 @@
<app-page-header
title="Appearance"
subtitle="Customize the look and feel of the interface"
/>
<div class="settings-form">
<app-card header="Theme">
<p class="section-hint">Choose between dark and light color modes.</p>
<div class="theme-options">
<button
type="button"
class="theme-option"
[class.theme-option--active]="theme() === 'dark'"
(click)="selectTheme('dark')"
>
<span class="theme-option__preview theme-option__preview--dark"></span>
<span class="theme-option__label">Dark</span>
</button>
<button
type="button"
class="theme-option"
[class.theme-option--active]="theme() === 'light'"
(click)="selectTheme('light')"
>
<span class="theme-option__preview theme-option__preview--light"></span>
<span class="theme-option__label">Light</span>
</button>
</div>
</app-card>
<app-card header="Accent color">
<p class="section-hint">Pick a preset or choose a custom color. Changes apply instantly and persist across sessions.</p>
<div class="swatch-grid">
@for (swatch of presetSwatches; track swatch.value) {
<button
type="button"
class="swatch"
[class.swatch--active]="accent() === swatch.value"
[attr.aria-label]="swatch.label"
[attr.title]="swatch.label"
(click)="selectAccent(swatch.value)"
>
<span class="swatch__dot" [style.background]="swatch.color"></span>
<span class="swatch__label">{{ swatch.label }}</span>
</button>
}
<label
class="swatch swatch--custom"
[class.swatch--active]="accent() === 'custom'"
title="Custom color"
>
<span class="swatch__dot swatch__dot--custom"></span>
<span class="swatch__label">Custom</span>
<input
type="color"
class="swatch__input"
[value]="customAccent()"
(input)="onCustomColorChange($event)"
aria-label="Pick a custom accent color"
/>
</label>
</div>
</app-card>
</div>

View File

@@ -0,0 +1,132 @@
@use 'settings-layout' as *;
:host { @include settings-page; }
.settings-form { @include settings-form; }
.section-hint {
font-size: var(--font-size-sm);
color: var(--text-secondary);
line-height: 1.5;
margin: 0 0 var(--space-3);
}
.theme-options {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-3);
margin-top: var(--space-2);
@media (max-width: 480px) {
grid-template-columns: 1fr;
}
}
.theme-option {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3);
border-radius: var(--radius-lg);
border: 1px solid var(--glass-border);
background: var(--glass-bg);
color: var(--text-primary);
cursor: pointer;
text-align: left;
transition: border-color var(--duration-fast) var(--ease-default),
background var(--duration-fast) var(--ease-default),
box-shadow var(--duration-fast) var(--ease-default);
&:hover {
border-color: var(--glass-border-hover);
background: var(--glass-bg-hover);
}
&--active {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px var(--color-primary-subtle);
}
&__preview {
width: 36px;
height: 36px;
border-radius: var(--radius-md);
border: 1px solid var(--glass-border);
flex-shrink: 0;
&--dark {
background: linear-gradient(135deg, var(--brand-950) 0%, #0c0614 100%);
}
&--light {
background: linear-gradient(135deg, #ffffff 0%, var(--brand-100) 100%);
}
}
&__label {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
}
}
.swatch-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
gap: var(--space-2);
margin-top: var(--space-2);
}
.swatch {
position: relative;
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-md);
border: 1px solid var(--glass-border);
background: var(--glass-bg);
color: var(--text-primary);
cursor: pointer;
text-align: left;
transition: border-color var(--duration-fast) var(--ease-default),
background var(--duration-fast) var(--ease-default),
box-shadow var(--duration-fast) var(--ease-default);
&:hover {
border-color: var(--glass-border-hover);
background: var(--glass-bg-hover);
}
&--active {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px var(--color-primary-subtle);
}
&__dot {
width: 20px;
height: 20px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.15);
flex-shrink: 0;
&--custom {
background-image: conic-gradient(from 180deg, #f43f5e, #f59e0b, #10b981, #3b82f6, #7e57c2, #f43f5e);
}
}
&__label {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
}
&__input {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
border: 0;
padding: 0;
}
}

View File

@@ -0,0 +1,51 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { PageHeaderComponent } from '@layout/page-header/page-header.component';
import { CardComponent } from '@ui';
import {
ACCENT_PRESETS,
ACCENT_PRESET_HEX,
Accent,
Theme,
ThemeService,
} from '@core/services/theme.service';
interface AccentSwatch {
readonly value: Accent;
readonly label: string;
readonly color: string;
}
@Component({
selector: 'app-appearance-settings',
standalone: true,
imports: [PageHeaderComponent, CardComponent],
templateUrl: './appearance-settings.component.html',
styleUrl: './appearance-settings.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppearanceSettingsComponent {
private readonly themeService = inject(ThemeService);
readonly theme = this.themeService.theme;
readonly accent = this.themeService.accent;
readonly customAccent = this.themeService.customAccent;
readonly presetSwatches: AccentSwatch[] = ACCENT_PRESETS.map((value) => ({
value,
label: value.charAt(0).toUpperCase() + value.slice(1),
color: ACCENT_PRESET_HEX[value],
}));
selectTheme(theme: Theme): void {
this.themeService.setTheme(theme);
}
selectAccent(accent: Accent): void {
this.themeService.setAccent(accent);
}
onCustomColorChange(event: Event): void {
const { value } = event.target as HTMLInputElement;
this.themeService.setCustomAccent(value);
}
}

View File

@@ -84,8 +84,8 @@
@for (provider of availableProviders; track provider.type) {
<button class="provider-card" (click)="onProviderTypeSelected(provider.type)">
<span class="provider-card__icon-wrapper">
<img [src]="provider.iconLightUrl" [alt]="provider.name" class="provider-card__icon provider-card__icon--light" />
<img [src]="provider.iconUrl" [alt]="provider.name" class="provider-card__icon provider-card__icon--normal" />
<img [src]="themeService.themedIconSrc(provider.iconLightUrl)" alt="" aria-hidden="true" class="provider-card__icon provider-card__icon--light" />
<img [src]="provider.iconUrl" alt="" aria-hidden="true" class="provider-card__icon provider-card__icon--normal" />
</span>
<span class="provider-card__name">{{ provider.name }}</span>
<span class="provider-card__description">{{ provider.description }}</span>

View File

@@ -9,6 +9,7 @@ import {
import { NotificationApi } from '@core/api/notification.api';
import { ToastService } from '@core/services/toast.service';
import { ConfirmService } from '@core/services/confirm.service';
import { ThemeService } from '@core/services/theme.service';
import {
NotificationProviderDto,
CreateDiscordProviderRequest,
@@ -114,6 +115,9 @@ export class NotificationsComponent implements OnInit, HasPendingChanges {
private readonly api = inject(NotificationApi);
private readonly toast = inject(ToastService);
private readonly confirmService = inject(ConfirmService);
protected readonly themeService = inject(ThemeService);
readonly theme = this.themeService.theme;
readonly loader = new DeferredLoader();
readonly loadError = signal(false);

View File

@@ -41,7 +41,7 @@
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(126, 87, 194, 0.06), transparent);
background: linear-gradient(90deg, transparent, rgba(var(--accent-rgb), 0.06), transparent);
transform: translateX(-100%);
transition: transform var(--duration-normal) var(--ease-default);
pointer-events: none;

View File

@@ -1,7 +1,7 @@
<div class="auth-layout">
<div class="auth-layout__card">
<div class="auth-layout__brand">
<img src="icons/128.png" alt="Cleanuparr" class="auth-layout__logo" />
<app-logo class="auth-layout__logo" />
<h1>Cleanuparr</h1>
</div>
<router-outlet />

View File

@@ -68,7 +68,7 @@
&__logo {
width: 64px;
height: 64px;
filter: drop-shadow(0 0 8px rgba(126, 87, 194, 0.4));
filter: drop-shadow(0 0 8px rgba(var(--accent-rgb), 0.4));
margin-bottom: var(--space-3);
animation: float-gentle 6s ease-in-out infinite;
}

View File

@@ -1,10 +1,11 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { LogoComponent } from '@ui';
@Component({
selector: 'app-auth-layout',
standalone: true,
imports: [RouterOutlet],
imports: [RouterOutlet, LogoComponent],
templateUrl: './auth-layout.component.html',
styleUrl: './auth-layout.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,

View File

@@ -5,7 +5,7 @@
[class.sidebar--mobile-open]="mobileOpen()"
>
<div class="sidebar__brand">
<img src="icons/128.png" alt="Cleanuparr" class="sidebar__logo-img" />
<app-logo class="sidebar__logo-img" />
<span class="sidebar__logo-text">Cleanuparr</span>
</div>
@@ -69,7 +69,7 @@
(click)="onNavItemClick()"
>
@if (item.iconSrc) {
<img [src]="item.iconSrc" [alt]="item.label" class="sidebar__item-img" />
<img [src]="themeService.themedIconSrc(item.iconSrc)" alt="" aria-hidden="true" class="sidebar__item-img" />
} @else if (item.icon) {
<ng-icon [name]="item.icon" class="sidebar__item-icon" />
}

View File

@@ -28,21 +28,21 @@
}
&__logo-img {
width: 32px;
height: 32px;
width: 40px;
height: 40px;
flex-shrink: 0;
filter: drop-shadow(0 0 8px rgba(126, 87, 194, 0.4));
filter: drop-shadow(0 0 8px rgba(var(--accent-rgb), 0.4));
transition: filter var(--duration-fast) var(--ease-default);
.sidebar__brand:hover & {
filter: drop-shadow(0 0 12px rgba(126, 87, 194, 0.6));
filter: drop-shadow(0 0 12px rgba(var(--accent-rgb), 0.6));
}
}
&__logo-text {
font-size: var(--font-size-xl);
font-weight: 700;
color: #ffffff;
color: var(--sidebar-item-active-text);
letter-spacing: -0.02em;
white-space: nowrap;
overflow: hidden;
@@ -73,7 +73,7 @@
left: 0;
right: 0;
height: 60px;
background: linear-gradient(to bottom, transparent, rgba(12, 6, 20, 0.9));
background: linear-gradient(to bottom, transparent, var(--sidebar-fade));
pointer-events: none;
z-index: 1;
}
@@ -95,14 +95,14 @@
&:hover {
background: var(--sidebar-item-hover);
color: #ffffff;
color: var(--sidebar-item-active-text);
}
&--active {
background: linear-gradient(90deg, rgba(126, 87, 194, 0.3), rgba(126, 87, 194, 0.08));
background: linear-gradient(90deg, rgba(var(--accent-rgb), 0.3), rgba(var(--accent-rgb), 0.08));
color: var(--sidebar-item-active-text);
font-weight: 500;
box-shadow: 0 0 20px rgba(126, 87, 194, 0.15);
box-shadow: 0 0 20px rgba(var(--accent-rgb), 0.15);
position: relative;
// Active indicator bar
@@ -206,7 +206,7 @@
&:hover {
background: var(--sidebar-item-hover);
color: #ffffff;
color: var(--sidebar-item-active-text);
}
}
@@ -229,7 +229,7 @@
&:hover {
background: rgba(239, 68, 68, 0.08);
color: #ffffff;
color: var(--color-error);
}
}
@@ -260,7 +260,7 @@
font-family: var(--font-mono);
&:hover {
color: #ffffff;
color: var(--sidebar-section-label-hover);
}
}

View File

@@ -1,8 +1,10 @@
import { Component, ChangeDetectionStrategy, input, output, signal, inject, computed } from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';
import { NgIcon } from '@ng-icons/core';
import { LogoComponent } from '@ui';
import { AppHubService } from '@core/realtime/app-hub.service';
import { AuthService } from '@core/auth/auth.service';
import { ThemeService } from '@core/services/theme.service';
interface NavItem {
label: string;
@@ -20,7 +22,7 @@ interface ExternalLink {
@Component({
selector: 'app-nav-sidebar',
standalone: true,
imports: [RouterLink, RouterLinkActive, NgIcon],
imports: [RouterLink, RouterLinkActive, NgIcon, LogoComponent],
templateUrl: './nav-sidebar.component.html',
styleUrl: './nav-sidebar.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -28,6 +30,9 @@ interface ExternalLink {
export class NavSidebarComponent {
private readonly hub = inject(AppHubService);
private readonly auth = inject(AuthService);
protected readonly themeService = inject(ThemeService);
readonly theme = this.themeService.theme;
collapsed = input(false);
mobileOpen = input(false);
@@ -74,6 +79,7 @@ export class NavSidebarComponent {
otherSettingsItems: NavItem[] = [
{ label: 'Notifications', icon: 'tablerBellRinging', route: '/settings/notifications' },
{ label: 'Account', icon: 'tablerUser', route: '/settings/account' },
{ label: 'Appearance', icon: 'tablerPalette', route: '/settings/appearance' },
];
onNavItemClick(): void {

View File

@@ -21,7 +21,7 @@
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(126, 87, 194, 0.3), transparent);
background: linear-gradient(90deg, transparent, rgba(var(--accent-rgb), 0.3), transparent);
pointer-events: none;
}
@@ -54,7 +54,7 @@
&:hover {
background: var(--glass-bg);
color: var(--text-primary);
box-shadow: 0 0 12px rgba(126, 87, 194, 0.15);
box-shadow: 0 0 12px rgba(var(--accent-rgb), 0.15);
}
&:focus-visible {

View File

@@ -30,9 +30,9 @@
}
&--primary {
background: linear-gradient(135deg, rgba(126, 87, 194, 0.2), rgba(126, 87, 194, 0.1));
background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.2), rgba(var(--accent-rgb), 0.1));
color: var(--color-primary);
border: 1px solid rgba(126, 87, 194, 0.2);
border: 1px solid rgba(var(--accent-rgb), 0.2);
}
&--success {

View File

@@ -70,13 +70,13 @@
background: var(--color-primary);
color: var(--color-primary-text);
border: 1px solid transparent;
box-shadow: 0 0 20px rgba(126, 87, 194, 0.3),
0 4px 12px rgba(126, 87, 194, 0.2);
box-shadow: 0 0 20px rgba(var(--accent-rgb), 0.3),
0 4px 12px rgba(var(--accent-rgb), 0.2);
&:hover:not(:disabled) {
background: var(--color-primary-hover);
box-shadow: 0 0 30px rgba(126, 87, 194, 0.4),
0 6px 16px rgba(126, 87, 194, 0.3);
box-shadow: 0 0 30px rgba(var(--accent-rgb), 0.4),
0 6px 16px rgba(var(--accent-rgb), 0.3);
}
&:active:not(:disabled) {

View File

@@ -15,8 +15,8 @@
// Hover lift effect with inner glow
&:hover {
transform: translateY(-2px);
box-shadow: var(--glass-shadow), 0 8px 24px rgba(126, 87, 194, 0.08),
inset 0 0 30px rgba(126, 87, 194, 0.03);
box-shadow: var(--glass-shadow), 0 8px 24px rgba(var(--accent-rgb), 0.08),
inset 0 0 30px rgba(var(--accent-rgb), 0.03);
&::before {
opacity: 0.7;

View File

@@ -48,7 +48,7 @@
min-height: 26px;
padding: var(--space-1) var(--space-2);
background: var(--color-primary-subtle);
border: 1px solid rgba(126, 87, 194, 0.2);
border: 1px solid rgba(var(--accent-rgb), 0.2);
border-radius: var(--radius-lg);
font-size: var(--font-size-xs);
color: var(--text-primary);
@@ -83,7 +83,7 @@
height: 26px;
padding: 0;
background: var(--color-primary-subtle);
border: 1px solid rgba(126, 87, 194, 0.2);
border: 1px solid rgba(var(--accent-rgb), 0.2);
border-radius: var(--radius-full);
color: var(--text-primary);
font-size: var(--font-size-sm);

View File

@@ -27,3 +27,4 @@ export { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.componen
export { SizeInputComponent } from './size-input/size-input.component';
export type { SizeUnit } from './size-input/size-input.component';
export { TooltipComponent } from './tooltip/tooltip.component';
export { LogoComponent } from './logo/logo.component';

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 69 KiB

View File

@@ -0,0 +1,11 @@
:host {
display: inline-flex;
color: var(--logo-fg);
line-height: 0;
}
svg {
width: 100%;
height: 100%;
display: block;
}

View File

@@ -0,0 +1,12 @@
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
@Component({
selector: 'app-logo',
standalone: true,
templateUrl: './logo.component.html',
styleUrl: './logo.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LogoComponent {
ariaLabel = input<string>('Cleanuparr');
}

View File

@@ -28,7 +28,7 @@
position: absolute;
inset: -1px;
border-radius: inherit;
background: linear-gradient(135deg, rgba(126, 87, 194, 0.3), transparent 50%, rgba(59, 130, 246, 0.2));
background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.3), transparent 50%, rgba(var(--accent-rgb), 0.2));
z-index: -1;
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask-composite: exclude;

View File

@@ -23,7 +23,7 @@
&:focus-within {
border-color: var(--input-border-focus);
box-shadow: 0 0 0 3px var(--color-primary-subtle),
0 0 12px rgba(126, 87, 194, 0.15);
0 0 12px rgba(var(--accent-rgb), 0.15);
}
}

View File

@@ -23,7 +23,7 @@
&:focus-within {
border-color: var(--input-border-focus);
box-shadow: 0 0 0 3px var(--color-primary-subtle),
0 0 12px rgba(126, 87, 194, 0.15);
0 0 12px rgba(var(--accent-rgb), 0.15);
}
}

View File

@@ -2,6 +2,7 @@
@use 'styles/tokens';
@use 'styles/themes';
@use 'styles/accents';
@use 'styles/reset';
@use 'styles/typography';
@use 'styles/scrollbar';
@@ -17,8 +18,8 @@
background: linear-gradient(
90deg,
var(--glass-bg) 0px,
rgba(126, 87, 194, 0.08) 30px,
rgba(59, 130, 246, 0.06) 50px,
rgba(var(--accent-rgb), 0.08) 30px,
rgba(var(--accent-rgb), 0.06) 50px,
var(--glass-bg-hover) 70px,
var(--glass-bg) 100px
);

View File

@@ -0,0 +1,94 @@
// =============================================================================
// Accent Presets
// Each preset overrides the --brand-* scale and --accent-rgb triple.
// The 'default' preset keeps the UI brand palette (from _tokens.scss) unchanged
// and restores the logo's original hexes. Scales follow the same perceptual ramp
// (Tailwind-style 50..950 lightness).
//
// NOTE: The --brand-500 stop of each preset is mirrored as a TS constant in
// app/core/services/theme.service.ts (`ACCENT_PRESET_HEX`) so the appearance
// settings page can render preview swatches without inspecting the DOM. Keep
// the two in sync when changing a preset's mid-tone.
// =============================================================================
// Default: UI stays on brand-purple defaults; logo uses the original darker
// purple for accent parts, and the body flips with the theme (black on dark,
// white on light) so it blends into the sidebar.
[data-accent='default'] {
--logo-accent: #420077;
}
[data-accent='blue'] {
--brand-50: #eff6ff;
--brand-100: #dbeafe;
--brand-200: #bfdbfe;
--brand-300: #93c5fd;
--brand-400: #60a5fa;
--brand-500: #3b82f6;
--brand-600: #2563eb;
--brand-700: #1d4ed8;
--brand-800: #1e40af;
--brand-900: #1e3a8a;
--brand-950: #172554;
--accent-rgb: 59, 130, 246;
}
[data-accent='green'] {
--brand-50: #ecfdf5;
--brand-100: #d1fae5;
--brand-200: #a7f3d0;
--brand-300: #6ee7b7;
--brand-400: #34d399;
--brand-500: #10b981;
--brand-600: #059669;
--brand-700: #047857;
--brand-800: #065f46;
--brand-900: #064e3b;
--brand-950: #022c22;
--accent-rgb: 16, 185, 129;
}
[data-accent='rose'] {
--brand-50: #fff1f2;
--brand-100: #ffe4e6;
--brand-200: #fecdd3;
--brand-300: #fda4af;
--brand-400: #fb7185;
--brand-500: #f43f5e;
--brand-600: #e11d48;
--brand-700: #be123c;
--brand-800: #9f1239;
--brand-900: #881337;
--brand-950: #4c0519;
--accent-rgb: 244, 63, 94;
}
[data-accent='amber'] {
--brand-50: #fffbeb;
--brand-100: #fef3c7;
--brand-200: #fde68a;
--brand-300: #fcd34d;
--brand-400: #fbbf24;
--brand-500: #f59e0b;
--brand-600: #d97706;
--brand-700: #b45309;
--brand-800: #92400e;
--brand-900: #78350f;
--brand-950: #451a03;
--accent-rgb: 245, 158, 11;
}
[data-accent='teal'] {
--brand-50: #f0fdfa;
--brand-100: #ccfbf1;
--brand-200: #99f6e4;
--brand-300: #5eead4;
--brand-400: #2dd4bf;
--brand-500: #14b8a6;
--brand-600: #0d9488;
--brand-700: #0f766e;
--brand-800: #115e59;
--brand-900: #134e4a;
--brand-950: #042f2e;
--accent-rgb: 20, 184, 166;
}

View File

@@ -73,8 +73,8 @@
// Gentle glow breathe for focused inputs — very slow, very subtle
@keyframes glow-breathe {
0%, 100% { box-shadow: 0 0 0 3px var(--color-primary-subtle), 0 0 12px rgba(126, 87, 194, 0.12); }
50% { box-shadow: 0 0 0 3px var(--color-primary-subtle), 0 0 16px rgba(126, 87, 194, 0.22); }
0%, 100% { box-shadow: 0 0 0 3px var(--color-primary-subtle), 0 0 12px rgba(var(--accent-rgb), 0.12); }
50% { box-shadow: 0 0 0 3px var(--color-primary-subtle), 0 0 16px rgba(var(--accent-rgb), 0.22); }
}
// Delayed content materialise — used inside expanding containers
@@ -106,7 +106,7 @@
// Toast entrance glow
@keyframes toast-glow {
from { box-shadow: 0 0 20px var(--toast-glow-color, rgba(126, 87, 194, 0.3)); }
from { box-shadow: 0 0 20px var(--toast-glow-color, rgba(var(--accent-rgb), 0.3)); }
to { box-shadow: none; }
}

View File

@@ -35,7 +35,7 @@ $_glass-noise: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http:
-webkit-backdrop-filter: blur(var(--glass-blur-lg));
border: 1px solid var(--glass-border);
border-radius: var(--radius-xl);
box-shadow: var(--glass-shadow), 0 0 80px rgba(126, 87, 194, 0.04);
box-shadow: var(--glass-shadow), 0 0 80px rgba(var(--accent-rgb), 0.04);
// Frost noise overlay for realism
background-image: #{$_glass-noise};
@@ -100,7 +100,7 @@ $_glass-noise: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http:
outline: none;
border-color: var(--input-border-focus);
box-shadow: 0 0 0 3px var(--color-primary-subtle),
0 0 12px rgba(126, 87, 194, 0.15);
0 0 12px rgba(var(--accent-rgb), 0.15);
animation: glow-breathe 3s ease-in-out infinite;
}

View File

@@ -31,7 +31,7 @@
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(126, 87, 194, 0.06), transparent);
background: linear-gradient(90deg, transparent, rgba(var(--accent-rgb), 0.06), transparent);
transform: translateX(-100%);
transition: transform var(--duration-normal) var(--ease-default);
pointer-events: none;

View File

@@ -7,9 +7,11 @@
// Dark theme (default)
:root,
[data-theme='dark'] {
// Surfaces
// Surfaces with fallbacks for browsers that can't parse color-mix()
--surface-ground: #0c0614;
--surface-section: #140b22;
--surface-ground: color-mix(in srgb, var(--brand-500) 8%, #000000);
--surface-section: #14091e;
--surface-section: color-mix(in srgb, var(--brand-500) 14%, #000000);
--surface-card: rgba(20, 11, 34, 0.75);
--surface-overlay: rgba(12, 6, 20, 0.92);
--surface-elevated: rgba(30, 18, 50, 0.65);
@@ -34,14 +36,14 @@
--color-primary-hover: var(--brand-400);
--color-primary-active: var(--brand-300);
--color-primary-text: #ffffff;
--color-primary-subtle: rgba(126, 87, 194, 0.15);
--color-primary-subtle: rgba(var(--accent-rgb), 0.15);
// Sidebar (always dark in both themes)
--sidebar-bg: linear-gradient(180deg, #1a0e2e 0%, #0c0614 100%);
// Sidebar
--sidebar-bg: linear-gradient(180deg, var(--brand-950) 0%, var(--surface-ground) 100%);
--sidebar-border: rgba(255, 255, 255, 0.06);
--sidebar-item-text: rgba(255, 255, 255, 0.65);
--sidebar-item-hover: rgba(126, 87, 194, 0.12);
--sidebar-item-active: rgba(126, 87, 194, 0.22);
--sidebar-item-hover: rgba(var(--accent-rgb), 0.12);
--sidebar-item-active: rgba(var(--accent-rgb), 0.22);
--sidebar-item-active-text: #ffffff;
--sidebar-section-label: rgba(255, 255, 255, 0.45);
--sidebar-section-label-hover: rgba(255, 255, 255, 0.65);
@@ -61,8 +63,8 @@
// Scrollbar
--scrollbar-track: transparent;
--scrollbar-thumb: rgba(126, 87, 194, 0.45);
--scrollbar-thumb-hover: rgba(126, 87, 194, 0.70);
--scrollbar-thumb: rgba(var(--accent-rgb), 0.45);
--scrollbar-thumb-hover: rgba(var(--accent-rgb), 0.70);
// Dropdown
--dropdown-bg: rgba(20, 11, 34, 0.97);
@@ -74,6 +76,18 @@
--focus-ring: var(--brand-500);
--focus-ring-offset: var(--surface-ground);
// Logo body color: black on dark theme so it blends into the dark sidebar —
// only the accent-colored parts of the logo read against the background.
--logo-fg: #000000;
// Logo accent color (follows --color-primary; overridden for 'default' preset to
// restore the original darker purple).
--logo-accent: var(--color-primary);
// Sidebar fade gradient (bottom of nav scroll area)
--sidebar-fade: rgba(12, 6, 20, 0.90);
--sidebar-fade: color-mix(in srgb, var(--surface-ground) 90%, transparent);
// Ambient orbs
--orb-opacity: 0.15;
--orb-blur: 120px;
@@ -84,9 +98,11 @@
// Light theme
[data-theme='light'] {
// Surfaces
--surface-ground: #f5f0fa;
--surface-section: #ede5f7;
// Surfaces with fallbacks for browsers that can't parse color-mix()
--surface-ground: #faf7fd;
--surface-ground: color-mix(in srgb, var(--brand-500) 3%, #ffffff);
--surface-section: #f3edf9;
--surface-section: color-mix(in srgb, var(--brand-500) 6%, #ffffff);
--surface-card: rgba(255, 255, 255, 0.70);
--surface-overlay: rgba(245, 240, 250, 0.94);
--surface-elevated: rgba(255, 255, 255, 0.80);
@@ -95,9 +111,9 @@
--glass-bg: rgba(255, 255, 255, 0.50);
--glass-bg-hover: rgba(255, 255, 255, 0.65);
--glass-bg-active: rgba(255, 255, 255, 0.75);
--glass-border: rgba(126, 87, 194, 0.10);
--glass-border-hover: rgba(126, 87, 194, 0.20);
--glass-shadow: 0 8px 32px rgba(126, 87, 194, 0.06);
--glass-border: rgba(var(--accent-rgb), 0.10);
--glass-border-hover: rgba(var(--accent-rgb), 0.20);
--glass-shadow: 0 8px 32px rgba(var(--accent-rgb), 0.06);
// Text
--text-primary: rgba(12, 6, 20, 0.90);
@@ -111,50 +127,62 @@
--color-primary-hover: var(--brand-700);
--color-primary-active: var(--brand-800);
--color-primary-text: #ffffff;
--color-primary-subtle: rgba(126, 87, 194, 0.08);
--color-primary-subtle: rgba(var(--accent-rgb), 0.08);
// Sidebar (stays dark purple in light theme for brand identity)
--sidebar-bg: linear-gradient(180deg, #2d1a4e 0%, #1a0e2e 100%);
--sidebar-border: rgba(255, 255, 255, 0.08);
--sidebar-item-text: rgba(255, 255, 255, 0.65);
--sidebar-item-hover: rgba(255, 255, 255, 0.10);
--sidebar-item-active: rgba(255, 255, 255, 0.18);
--sidebar-item-active-text: #ffffff;
--sidebar-section-label: rgba(255, 255, 255, 0.45);
--sidebar-section-label-hover: rgba(255, 255, 255, 0.65);
// Sidebar (light theme): light accent-tinted background with dark text
--sidebar-bg: linear-gradient(180deg, var(--brand-50) 0%, var(--brand-100) 100%);
--sidebar-border: var(--divider);
--sidebar-item-text: rgba(12, 6, 20, 0.70);
--sidebar-item-hover: rgba(var(--accent-rgb), 0.10);
--sidebar-item-active: rgba(var(--accent-rgb), 0.18);
--sidebar-item-active-text: rgba(12, 6, 20, 0.95);
--sidebar-section-label: rgba(12, 6, 20, 0.50);
--sidebar-section-label-hover: rgba(12, 6, 20, 0.80);
// Toolbar
--toolbar-bg: rgba(255, 255, 255, 0.65);
--toolbar-border: rgba(126, 87, 194, 0.10);
--toolbar-border: rgba(var(--accent-rgb), 0.10);
// Input
--input-bg: rgba(255, 255, 255, 0.60);
--input-bg-hover: rgba(255, 255, 255, 0.75);
--input-border: rgba(126, 87, 194, 0.15);
--input-border-hover: rgba(126, 87, 194, 0.25);
--input-border: rgba(var(--accent-rgb), 0.15);
--input-border-hover: rgba(var(--accent-rgb), 0.25);
--input-border-focus: var(--brand-600);
--input-placeholder: rgba(12, 6, 20, 0.45);
--input-text: var(--text-primary);
// Scrollbar
--scrollbar-track: transparent;
--scrollbar-thumb: rgba(126, 87, 194, 0.50);
--scrollbar-thumb-hover: rgba(126, 87, 194, 0.75);
--scrollbar-thumb: rgba(var(--accent-rgb), 0.50);
--scrollbar-thumb-hover: rgba(var(--accent-rgb), 0.75);
// Dropdown
--dropdown-bg: rgba(255, 255, 255, 0.97);
// Divider
--divider: rgba(126, 87, 194, 0.10);
--divider: rgba(var(--accent-rgb), 0.10);
// Focus ring
--focus-ring: var(--brand-600);
--focus-ring-offset: var(--surface-ground);
// Ambient orbs (stronger for light backgrounds)
// Logo body color: white on light theme so it blends into the light sidebar —
// only the accent-colored parts of the logo read against the background.
--logo-fg: #ffffff;
// Logo accent color (follows --color-primary; overridden for 'default' preset to
// restore the original darker purple).
--logo-accent: var(--color-primary);
// Sidebar fade gradient (bottom of nav scroll area)
--sidebar-fade: rgba(237, 233, 254, 0.90);
--sidebar-fade: color-mix(in srgb, var(--brand-100) 90%, transparent);
// Ambient orbs (stronger for light backgrounds; retint with accent)
--orb-opacity: 0.35;
--orb-blur: 150px;
--orb-primary: radial-gradient(circle, #9333ea, #6d28d9);
--orb-secondary: radial-gradient(circle, #7c3aed, #2563eb);
--orb-primary: radial-gradient(circle, var(--brand-500), var(--brand-700));
--orb-secondary: radial-gradient(circle, var(--brand-400), var(--color-info));
--orb-tertiary: radial-gradient(circle, #0891b2, #6366f1);
}

View File

@@ -20,6 +20,11 @@
--brand-900: #{$brand-900};
--brand-950: #{$brand-950};
// Accent RGB triple - used by translucent surfaces (rgba(var(--accent-rgb), X)).
// Must match $brand-500 in _variables.scss.
// Overridden per preset in _accents.scss and at runtime by ThemeService for custom colors.
--accent-rgb: 139, 92, 246;
// Semantic colors
--color-success: #{$color-success};
--color-success-dim: #{$color-success-dim};

View File

@@ -4,18 +4,18 @@
// These feed into _tokens.scss (CSS custom properties) and _themes.scss.
// =============================================================================
// Brand colors (Cleanuparr purple palette)
$brand-50: #f3e8ff;
$brand-100: #e9d5ff;
$brand-200: #d8b4fe;
$brand-300: #c084fc;
$brand-400: #a855f7;
$brand-500: #7E57C2;
$brand-600: #6D28D9;
$brand-700: #5B21B6;
$brand-800: #4C1D95;
$brand-900: #3B0764;
$brand-950: #1e0038;
// Brand colors (Cleanuparr violet palette — cooler purple, less pink-lavender)
$brand-50: #f5f3ff;
$brand-100: #ede9fe;
$brand-200: #ddd6fe;
$brand-300: #c4b5fd;
$brand-400: #a78bfa;
$brand-500: #8b5cf6;
$brand-600: #7c3aed;
$brand-700: #6d28d9;
$brand-800: #5b21b6;
$brand-900: #4c1d95;
$brand-950: #2e1065;
// Semantic colors
$color-success: #22c55e;

View File

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 69 KiB