Merge branch 'main' into main
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @ -1,12 +1,12 @@ | ||||
| <p align="center"> | ||||
|   <picture> | ||||
|     <source media="(prefers-color-scheme: dark)" srcset="images/homepage-dark.png"> | ||||
|     <img src="images/homepage-light.png" width="65%"> | ||||
|     <source media="(prefers-color-scheme: dark)" srcset="images/banner_light@2x.png"> | ||||
|     <img src="images/banner_dark@2x.png" width="65%"> | ||||
|   </picture> | ||||
| </p> | ||||
| 
 | ||||
| <p align="center"> | ||||
|   A modern <em>(fully static, fast)</em>, secure <em>(fully proxied)</em>, highly customizable application dashboard with integrations for more than 25 services and translations for 15 languages. Easily configured via YAML files (or discovery via docker labels). | ||||
|   A modern <em>(fully static, fast)</em>, secure <em>(fully proxied)</em>, highly customizable application dashboard with integrations for more than 25 services and translations for over 15 languages. Easily configured via YAML files (or discovery via docker labels). | ||||
| </p> | ||||
| 
 | ||||
| <p align="center"> | ||||
| @ -38,7 +38,7 @@ | ||||
| - Images built for AMD64 (x86_64), ARM64, ARMv7 and ARMv6 | ||||
|   - Supports all Raspberry Pi's, most SBCs & Apple Silicon | ||||
| - Full i18n support with automatic language detection | ||||
|   - Translantions for Chinese, Dutch, French, German, Hebrew, Hungarian, Norwegian Bokmål, Polish, Portuguese, Portuguese (Brazil), Romainian, Russian, Spanish, Swedish and Yue | ||||
|   - Translantions for Chinese, Dutch, Finnish, French, German, Hebrew, Hungarian, Norwegian Bokmål, Polish, Portuguese, Portuguese (Brazil), Romainian, Russian, Spanish, Swedish and Yue | ||||
|   - Want to help translate? [Join the Weblate project](https://hosted.weblate.org/engage/homepage/) | ||||
| - Service & Web Bookmarks | ||||
| - Docker Integration | ||||
| @ -47,7 +47,7 @@ | ||||
| - Service Integration | ||||
|   - Sonarr, Radarr, Readarr, Prowlarr, Bazarr, Lidarr, Emby, Jellyfin, Tautulli (Plex) | ||||
|   - Ombi, Overseerr, Jellyseerr, Jackett, NZBGet, SABnzbd, ruTorrent, Transmission, qBittorrent | ||||
|   - Portainer, Traefik, Speedtest Tracker, PiHole, AdGuard Home, Nginx Proxy Manager, Gotify, Syncthing Relay Server, Authentic | ||||
|   - Portainer, Traefik, Speedtest Tracker, PiHole, AdGuard Home, Nginx Proxy Manager, Gotify, Syncthing Relay Server, Authentic, Proxmox | ||||
| - Information Providers | ||||
|   - Coin Market Cap, Mastodon | ||||
| - Information & Utility Widgets | ||||
| @ -152,10 +152,11 @@ Huge thanks to the all the contributors who have helped make this project what i | ||||
| - [DevPGSV](https://github.com/benphelps/homepage/commits?author=DevPGSV) - Syncthing Relay Server & Mastodon widgets | ||||
| - [ilusi0n](https://github.com/benphelps/homepage/commits?author=ilusi0n) - Jellyseerr Integration | ||||
| - [ItsJustMeChris](https://github.com/benphelps/homepage/commits?author=ItsJustMeChris) - Coin Market Cap Widget | ||||
| - [JazzFisch](https://github.com/benphelps/homepage/commits?author=JazzFisch) - Readarr, Bazarr, Lidarr, SABnzbd, Transmission, qBittorrent Integrations & countless more improvements | ||||
| - [JazzFisch](https://github.com/benphelps/homepage/commits?author=JazzFisch) - Readarr, Bazarr, Lidarr, SABnzbd, Transmission, qBittorrent, Proxmox Integrations & countless more improvements | ||||
| - [josways](https://github.com/benphelps/homepage/commits?author=josways) - Baidu search provider | ||||
| - [mauricio-kalil](https://github.com/benphelps/homepage/commits?author=mauricio-kalil) - Portuguese (Brazil) | ||||
| - [modem7](https://github.com/benphelps/homepage/commits?author=modem7) - Impvoed Docker Image | ||||
| - [MountainGod2](https://github.com/benphelps/homepage/discussions/243) - Homepage Logo | ||||
| - [quod](https://github.com/benphelps/homepage/commits?author=quod) - Fixed Typos | ||||
| - [schklom](https://github.com/benphelps/homepage/commits?author=schklom) - ARM64, ARMv7 and ARMv6 | ||||
| - [xicopitz](https://github.com/benphelps/homepage/commits?author=xicopitz) - Gotify & Prowlarr Integration | ||||
| @ -186,3 +187,4 @@ Huge thanks to the all the contributors who have helped make this project what i | ||||
| - Sascha Jelinek - German | ||||
| - [ShlomiPorush](https://github.com/benphelps/homepage/commits?author=ShlomiPorush) - Hebrew | ||||
| - [SuperDOS](https://github.com/benphelps/homepage/commits?author=SuperDOS) - Swedish | ||||
| - [kaihu](https://github.com/benphelps/homepage/commits?author=kaihu) - Finnish | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								images/banner_dark@2x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 31 KiB | 
							
								
								
									
										
											BIN
										
									
								
								images/banner_light@2x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 30 KiB | 
| Before Width: | Height: | Size: 15 KiB | 
| Before Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/android-chrome-192x192.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/android-chrome-512x512.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-touch-icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/favicon-16x16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 465 B | 
							
								
								
									
										
											BIN
										
									
								
								public/favicon-32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 595 B | 
| Before Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/homepage.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/mstile-144x144.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/mstile-150x150.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/mstile-310x150.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/mstile-310x310.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/mstile-70x70.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 958 B | 
							
								
								
									
										23
									
								
								public/safari-pinned-tab.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,23 @@ | ||||
| <?xml version="1.0" standalone="no"?> | ||||
| <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" | ||||
|  "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> | ||||
| <svg version="1.0" xmlns="http://www.w3.org/2000/svg" | ||||
|  width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000" | ||||
|  preserveAspectRatio="xMidYMid meet"> | ||||
| <metadata> | ||||
| Created by potrace 1.14, written by Peter Selinger 2001-2017 | ||||
| </metadata> | ||||
| <g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)" | ||||
| fill="#000000" stroke="none"> | ||||
| <path d="M1682 5688 c-141 -24 -253 -147 -287 -316 -5 -24 -10 -3658 -5 -4029 | ||||
| 0 -33 0 -33 168 108 92 77 178 151 192 162 14 12 75 64 136 114 62 51 114 99 | ||||
| 116 105 2 7 4 209 3 449 l-1 436 276 0 c151 0 277 -4 280 -8 3 -4 5 -98 4 | ||||
| -208 0 -111 2 -201 4 -201 3 0 117 95 132 110 3 3 73 61 155 130 168 140 167 | ||||
| 139 250 209 51 43 62 48 75 37 13 -12 524 -441 654 -550 28 -22 68 -57 91 -76 | ||||
| 23 -19 66 -55 95 -80 30 -25 86 -72 125 -105 38 -33 140 -118 225 -190 85 -71 | ||||
| 166 -139 180 -151 14 -13 70 -60 124 -106 55 -46 133 -111 174 -145 41 -35 77 | ||||
| -61 80 -59 4 2 7 607 7 1344 -1 1197 1 1341 15 1344 8 2 163 3 343 3 l328 0 | ||||
| -4 685 c-1 377 -6 695 -9 706 -26 90 -93 186 -161 233 -86 58 30 55 -1924 55 | ||||
| -991 1 -1819 -2 -1841 -6z"/> | ||||
| </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.2 KiB | 
| @ -3,9 +3,11 @@ import List from "components/bookmarks/list"; | ||||
| 
 | ||||
| export default function BookmarksGroup({ group }) { | ||||
|   return ( | ||||
|     <div key={group.name} className="basis-full md:basis-1/2 lg:basis-1/3 xl:basis-1/4 flex-1 p-1"> | ||||
|     <div key={group.name} className="basis-full md:basis-1/2 lg:basis-1/3 xl:basis-1/4 flex-1"> | ||||
|       <h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{group.name}</h2> | ||||
|       <ErrorBoundary><List bookmarks={group.bookmarks} /></ErrorBoundary> | ||||
|       <ErrorBoundary> | ||||
|         <List bookmarks={group.bookmarks} /> | ||||
|       </ErrorBoundary> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|  | ||||
							
								
								
									
										114
									
								
								src/components/favicon.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,114 @@ | ||||
| /* eslint-disable @next/next/no-img-element */ | ||||
| /* eslint-disable jsx-a11y/alt-text */ | ||||
| import { useRef, useEffect, useContext } from "react"; | ||||
| 
 | ||||
| import themes from "utils/styles/themes"; | ||||
| import { ColorContext } from "utils/contexts/color"; | ||||
| 
 | ||||
| export function Svg({ svgRef = null }) { | ||||
|   const { color } = useContext(ColorContext); | ||||
| 
 | ||||
|   const { iconStart, iconEnd } = themes[color]; | ||||
| 
 | ||||
|   return ( | ||||
|     <svg | ||||
|       ref={svgRef} | ||||
|       xmlns="http://www.w3.org/2000/svg" | ||||
|       viewBox="0 0 1024 1024" | ||||
|       style={{ | ||||
|         enableBackground: "new 0 0 1024 1024", | ||||
|       }} | ||||
|       xmlSpace="preserve" | ||||
|       className="w-full h-full" | ||||
|     > | ||||
|       <style> | ||||
|         { | ||||
|           ".st0{display:none}.st3{stroke-linecap:square}.st3,.st4{fill:none;stroke:#fff;stroke-miterlimit:10}.st6{display:inline;fill:#333}.st7{fill:#fff}" | ||||
|         } | ||||
|       </style> | ||||
|       <g id="Icon"> | ||||
|         <path | ||||
|           d="M771.9 191c27.7 0 50.1 26.5 50.1 59.3v186.4l-100.2.3V250.3c0-32.8 22.4-59.3 50.1-59.3z" | ||||
|           style={{ | ||||
|             fill: iconStart, | ||||
|           }} | ||||
|         /> | ||||
|         <linearGradient | ||||
|           id="homepage_favicon_gradient" | ||||
|           gradientUnits="userSpaceOnUse" | ||||
|           x1={200.746} | ||||
|           y1={225.015} | ||||
|           x2={764.986} | ||||
|           y2={789.255} | ||||
|         > | ||||
|           <stop | ||||
|             offset={0} | ||||
|             style={{ | ||||
|               stopColor: iconStart, | ||||
|             }} | ||||
|           /> | ||||
|           <stop | ||||
|             offset={1} | ||||
|             style={{ | ||||
|               stopColor: iconEnd, | ||||
|             }} | ||||
|           /> | ||||
|         </linearGradient> | ||||
|         <path | ||||
|           d="M721.8 250.3c0-32.7 22.4-59.3 50.1-59.3H253.1c-27.7 0-50.1 26.5-50.1 59.3v582.2l90.2-75.7-.1-130.3H375v61.8l88-73.8 258.8 217.9V250.6" | ||||
|           style={{ | ||||
|             fill: "url(#homepage_favicon_gradient})", | ||||
|           }} | ||||
|         /> | ||||
|       </g> | ||||
|     </svg> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export default function Favicon() { | ||||
|   const svgRef = useRef(); | ||||
|   const imgRef = useRef(); | ||||
|   const canvasRef = useRef(); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     const svg = svgRef.current; | ||||
|     const img = imgRef.current; | ||||
|     const canvas = canvasRef.current; | ||||
| 
 | ||||
|     if (!svg || !img || !canvas) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const xml = new XMLSerializer().serializeToString(svg); | ||||
| 
 | ||||
|     const svg64 = Buffer.from(xml).toString("base64"); | ||||
|     const b64Start = "data:image/svg+xml;base64,"; | ||||
| 
 | ||||
|     // prepend a "header" | ||||
|     const image64 = b64Start + svg64; | ||||
| 
 | ||||
|     // set it as the source of the img element | ||||
|     img.onload = () => { | ||||
|       // draw the image onto the canvas | ||||
|       canvas.getContext("2d").drawImage(img, 0, 0); | ||||
|       // canvas.width = 256; | ||||
|       // canvas.height = 256; | ||||
| 
 | ||||
|       const link = window.document.createElement("link"); | ||||
|       link.type = "image/x-icon"; | ||||
|       link.rel = "shortcut icon"; | ||||
|       link.href = canvas.toDataURL("image/x-icon"); | ||||
|       document.getElementsByTagName("head")[0].appendChild(link); | ||||
|     }; | ||||
| 
 | ||||
|     img.src = image64; | ||||
|   }, []); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="hidden"> | ||||
|       <Svg svgRef={svgRef} /> | ||||
|       <img width={64} height={64} ref={imgRef} /> | ||||
|       <canvas width={64} height={64} ref={canvasRef} /> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										56
									
								
								src/components/widgets/logo/logo.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,56 @@ | ||||
| export default function Logo() { | ||||
|   return ( | ||||
|     <div className="w-12 h-12 flex flex-row items-center align-middle mr-3 self-center"> | ||||
|       <svg | ||||
|         xmlns="http://www.w3.org/2000/svg" | ||||
|         viewBox="0 0 1024 1024" | ||||
|         style={{ | ||||
|           enableBackground: "new 0 0 1024 1024", | ||||
|         }} | ||||
|         xmlSpace="preserve" | ||||
|         className="w-full h-full" | ||||
|       > | ||||
|         <style> | ||||
|           { | ||||
|             ".st0{display:none}.st3{stroke-linecap:square}.st3,.st4{fill:none;stroke:#fff;stroke-miterlimit:10}.st6{display:inline;fill:#333}.st7{fill:#fff}" | ||||
|           } | ||||
|         </style> | ||||
|         <g id="Icon"> | ||||
|           <path | ||||
|             d="M771.9 191c27.7 0 50.1 26.5 50.1 59.3v186.4l-100.2.3V250.3c0-32.8 22.4-59.3 50.1-59.3z" | ||||
|             style={{ | ||||
|               fill: "rgba(var(--color-logo-start))", | ||||
|             }} | ||||
|           /> | ||||
|           <linearGradient | ||||
|             id="homepage_logo_gradient" | ||||
|             gradientUnits="userSpaceOnUse" | ||||
|             x1={200.746} | ||||
|             y1={225.015} | ||||
|             x2={764.986} | ||||
|             y2={789.255} | ||||
|           > | ||||
|             <stop | ||||
|               offset={0} | ||||
|               style={{ | ||||
|                 stopColor: "rgba(var(--color-logo-start))", | ||||
|               }} | ||||
|             /> | ||||
|             <stop | ||||
|               offset={1} | ||||
|               style={{ | ||||
|                 stopColor: "rgba(var(--color-logo-stop))", | ||||
|               }} | ||||
|             /> | ||||
|           </linearGradient> | ||||
|           <path | ||||
|             d="M721.8 250.3c0-32.7 22.4-59.3 50.1-59.3H253.1c-27.7 0-50.1 26.5-50.1 59.3v582.2l90.2-75.7-.1-130.3H375v61.8l88-73.8 258.8 217.9V250.6" | ||||
|             style={{ | ||||
|               fill: "url(#homepage_logo_gradient)", | ||||
|             }} | ||||
|           /> | ||||
|         </g> | ||||
|       </svg> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @ -16,9 +16,9 @@ function Widget({ options }) { | ||||
| 
 | ||||
|   if (error || data?.cod === 401 || data?.error) { | ||||
|     return ( | ||||
|       <div className="flex flex-col justify-center first:ml-0 ml-4"> | ||||
|       <div className="flex flex-col justify-center first:ml-auto ml-4 mr-2"> | ||||
|         <div className="flex flex-row items-center justify-end"> | ||||
|           <div className="flex flex-col items-center"> | ||||
|           <div className="hidden sm:flex flex-col items-center"> | ||||
|             <BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" /> | ||||
|             <div className="flex flex-col ml-3 text-left"> | ||||
|               <span className="text-theme-800 dark:text-theme-200 text-sm">{t("widget.api_error")}</span> | ||||
| @ -32,9 +32,9 @@ function Widget({ options }) { | ||||
| 
 | ||||
|   if (!data) { | ||||
|     return ( | ||||
|       <div className="flex flex-col justify-center first:ml-0 ml-4"> | ||||
|       <div className="flex flex-col justify-center first:ml-auto ml-4 mr-2"> | ||||
|         <div className="flex flex-row items-center justify-end"> | ||||
|           <div className="flex flex-col items-center"> | ||||
|           <div className="hidden sm:flex flex-col items-center"> | ||||
|             <WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" /> | ||||
|           </div> | ||||
|           <div className="flex flex-col ml-3 text-left"> | ||||
| @ -49,9 +49,9 @@ function Widget({ options }) { | ||||
|   const unit = options.units === "metric" ? "celsius" : "fahrenheit"; | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex flex-col justify-center first:ml-0 ml-4"> | ||||
|     <div className="flex flex-col justify-center first:ml-auto ml-2 mr-2"> | ||||
|       <div className="flex flex-row items-center justify-end"> | ||||
|         <div className="flex flex-col items-center"> | ||||
|         <div className="hidden sm:flex flex-col items-center"> | ||||
|           <Icon | ||||
|             condition={data.weather[0].id} | ||||
|             timeOfDay={data.dt > data.sys.sunrise && data.dt < data.sys.sundown ? "day" : "night"} | ||||
| @ -102,9 +102,13 @@ export default function OpenWeatherMap({ options }) { | ||||
| 
 | ||||
|   if (!location) { | ||||
|     return ( | ||||
|       <button type="button" onClick={() => requestLocation()} className="flex flex-col justify-center first:ml-0 ml-4"> | ||||
|       <button | ||||
|         type="button" | ||||
|         onClick={() => requestLocation()} | ||||
|         className="flex flex-col justify-center first:ml-auto ml-4 mr-2" | ||||
|       > | ||||
|         <div className="flex flex-row items-center justify-end"> | ||||
|           <div className="flex flex-col items-center"> | ||||
|           <div className="hidden sm:flex flex-col items-center"> | ||||
|             {requesting ? ( | ||||
|               <MdLocationSearching className="w-6 h-6 text-theme-800 dark:text-theme-200 animate-pulse" /> | ||||
|             ) : ( | ||||
|  | ||||
| @ -5,7 +5,7 @@ import Memory from "./memory"; | ||||
| export default function Resources({ options }) { | ||||
|   const { expanded } = options; | ||||
|   return ( | ||||
|     <div className="flex flex-col max-w:full sm:basis-auto self-center m-auto flex-wrap"> | ||||
|     <div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap"> | ||||
|       <div className="flex flex-row self-center flex-wrap justify-between"> | ||||
|         {options.cpu && <Cpu expanded={expanded} />} | ||||
|         {options.memory && <Memory expanded={expanded} />} | ||||
|  | ||||
| @ -56,7 +56,7 @@ export default function Search({ options }) { | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <form className="flex-col relative h-8 my-4 min-w-full md:min-w-fit grow first:ml-0 ml-4" onSubmit={handleSubmit}> | ||||
|     <form className="flex-col relative h-8 my-4 min-w-fit grow first:ml-0 ml-4" onSubmit={handleSubmit}> | ||||
|       <div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-white" /> | ||||
|       <input | ||||
|         type="text" | ||||
|  | ||||
| @ -16,7 +16,7 @@ function Widget({ options }) { | ||||
| 
 | ||||
|   if (error || data?.error) { | ||||
|     return ( | ||||
|       <div className="flex flex-col justify-center first:ml-0 ml-4"> | ||||
|       <div className="flex flex-col justify-center first:ml-0 ml-4 mr-2"> | ||||
|         <div className="flex flex-row items-center justify-end"> | ||||
|           <div className="flex flex-col items-center"> | ||||
|             <BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" /> | ||||
| @ -32,7 +32,7 @@ function Widget({ options }) { | ||||
| 
 | ||||
|   if (!data) { | ||||
|     return ( | ||||
|       <div className="flex flex-col justify-center first:ml-0 ml-4"> | ||||
|       <div className="flex flex-col justify-center first:ml-0 ml-4 mr-2"> | ||||
|         <div className="flex flex-row items-center justify-end"> | ||||
|           <div className="flex flex-col items-center"> | ||||
|             <WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" /> | ||||
| @ -49,7 +49,7 @@ function Widget({ options }) { | ||||
|   const unit = options.units === "metric" ? "celsius" : "fahrenheit"; | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex flex-col justify-center first:ml-0 ml-4"> | ||||
|     <div className="flex flex-col justify-center first:ml-0 ml-4 mr-2"> | ||||
|       <div className="flex flex-row items-center justify-end"> | ||||
|         <div className="flex flex-col items-center"> | ||||
|           <Icon condition={data.current.condition.code} timeOfDay={data.current.is_day ? "day" : "night"} /> | ||||
| @ -103,7 +103,11 @@ export default function WeatherApi({ options }) { | ||||
| 
 | ||||
|   if (!location) { | ||||
|     return ( | ||||
|       <button type="button" onClick={() => requestLocation()} className="flex flex-col justify-center first:ml-0 ml-4"> | ||||
|       <button | ||||
|         type="button" | ||||
|         onClick={() => requestLocation()} | ||||
|         className="flex flex-col justify-center first:ml-0 ml-4 mr-2" | ||||
|       > | ||||
|         <div className="flex flex-row items-center justify-end"> | ||||
|           <div className="flex flex-col items-center"> | ||||
|             {requesting ? ( | ||||
|  | ||||
| @ -9,6 +9,7 @@ const widgetMappings = { | ||||
|   search: dynamic(() => import("components/widgets/search/search")), | ||||
|   greeting: dynamic(() => import("components/widgets/greeting/greeting")), | ||||
|   datetime: dynamic(() => import("components/widgets/datetime/datetime")), | ||||
|   logo: dynamic(() => import("components/widgets/logo/logo"), { ssr: false }), | ||||
|   unifi_console: dynamic(() => import("components/widgets/unifi_console/unifi_console")), | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -8,6 +8,11 @@ export default function Document() { | ||||
|           name="description" | ||||
|           content="A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations." | ||||
|         /> | ||||
|         <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=4" /> | ||||
|         <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=4" /> | ||||
|         <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=4" /> | ||||
|         <link rel="manifest" href="/site.webmanifest?v=4" /> | ||||
|         <link rel="mask-icon" href="/safari-pinned-tab.svg?v=4" color="#1e9cd7" /> | ||||
|       </Head> | ||||
|       <body> | ||||
|         <Main /> | ||||
|  | ||||
							
								
								
									
										14
									
								
								src/pages/api/theme.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,14 @@ | ||||
| import checkAndCopyConfig, { getSettings } from "utils/config/config"; | ||||
| 
 | ||||
| export default function handler({ res }) { | ||||
|   checkAndCopyConfig("settings.yaml"); | ||||
|   const settings = getSettings(); | ||||
| 
 | ||||
|   const color = settings.color || "slate"; | ||||
|   const theme = settings.theme || "dark"; | ||||
| 
 | ||||
|   return res.status(200).json({ | ||||
|     color, | ||||
|     theme, | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/pages/browserconfig.xml.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,29 @@ | ||||
| import { getSettings } from "utils/config/config"; | ||||
| import themes from "utils/styles/themes"; | ||||
| 
 | ||||
| export async function getServerSideProps({ res }) { | ||||
|   const settings = getSettings(); | ||||
| 
 | ||||
|   const color = settings.color || "slate"; | ||||
|   const theme = settings.theme || "dark"; | ||||
| 
 | ||||
|   const xml = `<?xml version="1.0" encoding="utf-8"?> | ||||
| <browserconfig> | ||||
|     <msapplication> | ||||
|         <tile> | ||||
|             <square150x150logo src="/mstile-150x150.png?v=2"/> | ||||
|             <TileColor>${themes[color][theme]}</TileColor> | ||||
|         </tile> | ||||
|     </msapplication> | ||||
| </browserconfig>`; | ||||
| 
 | ||||
|   res.setHeader("Content-Type", "text/xml"); | ||||
|   res.write(xml); | ||||
|   res.end(); | ||||
| 
 | ||||
|   return { | ||||
|     props: {}, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default function BrowserConfig() {} | ||||
| @ -20,6 +20,7 @@ import { ThemeContext } from "utils/contexts/theme"; | ||||
| import { SettingsContext } from "utils/contexts/settings"; | ||||
| import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response"; | ||||
| import ErrorBoundary from "components/errorboundry"; | ||||
| import themes from "utils/styles/themes"; | ||||
| 
 | ||||
| const ThemeToggle = dynamic(() => import("components/toggles/theme"), { | ||||
|   ssr: false, | ||||
| @ -152,6 +153,13 @@ function Index({ initialSettings, fallback }) { | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| const headerStyles = { | ||||
|   boxed: | ||||
|     "m-4 mb-0 sm:m-8 sm:mb-0 rounded-md shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 dark:bg-white/5 p-3", | ||||
|   underlined: "m-4 mb-0 sm:m-8 sm:mb-1 border-b-2 pb-4 border-theme-800 dark:border-theme-200/50", | ||||
|   clean: "m-4 mb-0 sm:m-8 sm:mb-0", | ||||
| }; | ||||
| 
 | ||||
| function Home({ initialSettings }) { | ||||
|   const { i18n } = useTranslation(); | ||||
|   const { theme, setTheme } = useContext(ThemeContext); | ||||
| @ -185,10 +193,24 @@ function Home({ initialSettings }) { | ||||
|       <Head> | ||||
|         <title>{initialSettings.title || "Homepage"}</title> | ||||
|         {initialSettings.base && <base href={initialSettings.base} />} | ||||
|         {initialSettings.favicon && <link rel="icon" href={initialSettings.favicon} />} | ||||
|         {initialSettings.favicon ? ( | ||||
|           <link rel="icon" href={initialSettings.favicon} /> | ||||
|         ) : ( | ||||
|           <link rel="shortcut icon" href="/homepage.ico" /> | ||||
|         )} | ||||
|         <meta | ||||
|           name="msapplication-TileColor" | ||||
|           content={themes[initialSettings.color || "slate"][initialSettings.theme || "dark"]} | ||||
|         /> | ||||
|         <meta name="theme-color" content={themes[initialSettings.color || "slate"][initialSettings.theme || "dark"]} /> | ||||
|       </Head> | ||||
|       <div className="relative container m-auto flex flex-col justify-between z-10"> | ||||
|         <div className="flex flex-row flex-wrap m-8 pb-4 mt-10 border-b-2 border-theme-800 dark:border-theme-200 justify-between"> | ||||
|         <div | ||||
|           className={classNames( | ||||
|             "flex flex-row flex-wrap  justify-between", | ||||
|             headerStyles[initialSettings.headerStyle || "underlined"] | ||||
|           )} | ||||
|         > | ||||
|           {widgets && ( | ||||
|             <> | ||||
|               {widgets | ||||
| @ -197,7 +219,7 @@ function Home({ initialSettings }) { | ||||
|                   <Widget key={i} widget={widget} /> | ||||
|                 ))} | ||||
| 
 | ||||
|               <div className="ml-4 flex flex-wrap basis-full grow sm:basis-auto justify-between md:justify-end mt-2 md:mt-0"> | ||||
|               <div className="m-auto sm:ml-2 flex flex-wrap grow sm:basis-auto justify-between md:justify-end"> | ||||
|                 {widgets | ||||
|                   .filter((widget) => rightAlignedWidgets.includes(widget.type)) | ||||
|                   .map((widget, i) => ( | ||||
| @ -209,7 +231,7 @@ function Home({ initialSettings }) { | ||||
|         </div> | ||||
| 
 | ||||
|         {services && ( | ||||
|           <div className="flex flex-wrap p-8 items-start"> | ||||
|           <div className="flex flex-wrap p-4 sm:p-8 sm:pt-4 items-start pb-2"> | ||||
|             {services.map((group) => ( | ||||
|               <ServicesGroup key={group.name} services={group} layout={initialSettings.layout?.[group.name]} /> | ||||
|             ))} | ||||
| @ -217,7 +239,7 @@ function Home({ initialSettings }) { | ||||
|         )} | ||||
| 
 | ||||
|         {bookmarks && ( | ||||
|           <div className="grow flex flex-wrap pt-0 p-8"> | ||||
|           <div className="grow flex flex-wrap pt-0 p-4 sm:p-8"> | ||||
|             {bookmarks.map((group) => ( | ||||
|               <BookmarksGroup key={group.name} group={group} /> | ||||
|             ))} | ||||
|  | ||||
							
								
								
									
										42
									
								
								src/pages/site.webmanifest.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,42 @@ | ||||
| import checkAndCopyConfig, { getSettings } from "utils/config/config"; | ||||
| import themes from "utils/styles/themes"; | ||||
| 
 | ||||
| export async function getServerSideProps({ res }) { | ||||
|   checkAndCopyConfig("settings.yaml"); | ||||
|   const settings = getSettings(); | ||||
| 
 | ||||
|   const color = settings.color || "slate"; | ||||
|   const theme = settings.theme || "dark"; | ||||
| 
 | ||||
|   const manifest = { | ||||
|     name: "Homepage", | ||||
|     short_name: "Homepage", | ||||
|     icons: [ | ||||
|       { | ||||
|         src: "/android-chrome-192x192.png?v=2", | ||||
|         sizes: "192x192", | ||||
|         type: "image/png", | ||||
|       }, | ||||
|       { | ||||
|         src: "/android-chrome-512x512.png?v=2", | ||||
|         sizes: "512x512", | ||||
|         type: "image/png", | ||||
|       }, | ||||
|     ], | ||||
|     theme_color: themes[color][theme], | ||||
|     background_color: themes[color][theme], | ||||
|     display: "standalone", | ||||
|   }; | ||||
| 
 | ||||
|   res.setHeader("Content-Type", "application/manifest+json"); | ||||
|   res.write(JSON.stringify(manifest)); | ||||
|   res.end(); | ||||
| 
 | ||||
|   return { | ||||
|     props: {}, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default function Webmanifest() { | ||||
|   return null; | ||||
| } | ||||
| @ -39,7 +39,6 @@ body { | ||||
| 
 | ||||
| #page_container { | ||||
|   overflow: auto; | ||||
|   overflow: overlay; | ||||
|   scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -9,6 +9,9 @@ | ||||
|   --color-700: 40 40 40; | ||||
|   --color-800: 255 255 255; | ||||
|   --color-900: 255 255 255; | ||||
| 
 | ||||
|   --color-logo-start: 128 128 128 / 20%; | ||||
|   --color-logo-stop: 128 128 128 / 40%; | ||||
| } | ||||
| 
 | ||||
| .theme-slate { | ||||
| @ -22,6 +25,9 @@ | ||||
|   --color-700: 51 65 85; | ||||
|   --color-800: 30 41 59; | ||||
|   --color-900: 15 23 42; | ||||
| 
 | ||||
|   --color-logo-start: 148 163 184; | ||||
|   --color-logo-stop: 51 65 85; | ||||
| } | ||||
| 
 | ||||
| .theme-gray { | ||||
| @ -35,6 +41,9 @@ | ||||
|   --color-700: 55 65 81; | ||||
|   --color-800: 31 41 55; | ||||
|   --color-900: 17 24 39; | ||||
| 
 | ||||
|   --color-logo-start: 156 163 175; | ||||
|   --color-logo-stop: 55 65 81; | ||||
| } | ||||
| 
 | ||||
| .theme-zinc { | ||||
| @ -48,6 +57,9 @@ | ||||
|   --color-700: 63 63 70; | ||||
|   --color-800: 39 39 42; | ||||
|   --color-900: 24 24 27; | ||||
| 
 | ||||
|   --color-logo-start: 161 161 170; | ||||
|   --color-logo-stop: 63 63 70; | ||||
| } | ||||
| 
 | ||||
| .theme-neutral { | ||||
| @ -61,6 +73,9 @@ | ||||
|   --color-700: 64 64 64; | ||||
|   --color-800: 38 38 38; | ||||
|   --color-900: 23 23 23; | ||||
| 
 | ||||
|   --color-logo-start: 163 163 163; | ||||
|   --color-logo-stop: 64 64 64; | ||||
| } | ||||
| 
 | ||||
| .theme-stone { | ||||
| @ -74,6 +89,9 @@ | ||||
|   --color-700: 68 64 60; | ||||
|   --color-800: 41 37 36; | ||||
|   --color-900: 28 25 23; | ||||
| 
 | ||||
|   --color-logo-start: 168 162 158; | ||||
|   --color-logo-stop: 68 64 60; | ||||
| } | ||||
| 
 | ||||
| .theme-red { | ||||
| @ -87,6 +105,9 @@ | ||||
|   --color-700: 185 28 28; | ||||
|   --color-800: 153 27 27; | ||||
|   --color-900: 127 29 29; | ||||
| 
 | ||||
|   --color-logo-start: 248 113 113; | ||||
|   --color-logo-stop: 185 28 28; | ||||
| } | ||||
| 
 | ||||
| .theme-orange { | ||||
| @ -100,6 +121,9 @@ | ||||
|   --color-700: 194 65 12; | ||||
|   --color-800: 154 52 18; | ||||
|   --color-900: 124 45 18; | ||||
| 
 | ||||
|   --color-logo-start: 251 146 60; | ||||
|   --color-logo-stop: 194 65 12; | ||||
| } | ||||
| 
 | ||||
| .theme-amber { | ||||
| @ -113,6 +137,9 @@ | ||||
|   --color-700: 180 83 9; | ||||
|   --color-800: 146 64 14; | ||||
|   --color-900: 120 53 15; | ||||
| 
 | ||||
|   --color-logo-start: 251 191 36; | ||||
|   --color-logo-stop: 180 83 9; | ||||
| } | ||||
| 
 | ||||
| .theme-yellow { | ||||
| @ -126,6 +153,9 @@ | ||||
|   --color-700: 161 98 7; | ||||
|   --color-800: 133 77 14; | ||||
|   --color-900: 113 63 18; | ||||
| 
 | ||||
|   --color-logo-start: 250 204 21; | ||||
|   --color-logo-stop: 161 98 7; | ||||
| } | ||||
| 
 | ||||
| .theme-lime { | ||||
| @ -139,6 +169,9 @@ | ||||
|   --color-700: 77 124 15; | ||||
|   --color-800: 63 98 18; | ||||
|   --color-900: 54 83 20; | ||||
| 
 | ||||
|   --color-logo-start: 163 230 53; | ||||
|   --color-logo-stop: 77 124 15; | ||||
| } | ||||
| 
 | ||||
| .theme-green { | ||||
| @ -152,6 +185,9 @@ | ||||
|   --color-700: 21 128 61; | ||||
|   --color-800: 22 101 52; | ||||
|   --color-900: 20 83 45; | ||||
| 
 | ||||
|   --color-logo-start: 74 222 128; | ||||
|   --color-logo-stop: 21 128 61; | ||||
| } | ||||
| 
 | ||||
| .theme-emerald { | ||||
| @ -165,6 +201,9 @@ | ||||
|   --color-700: 4 120 87; | ||||
|   --color-800: 6 95 70; | ||||
|   --color-900: 6 78 59; | ||||
| 
 | ||||
|   --color-logo-start: 52 211 153; | ||||
|   --color-logo-stop: 4 120 87; | ||||
| } | ||||
| 
 | ||||
| .theme-teal { | ||||
| @ -178,6 +217,9 @@ | ||||
|   --color-700: 15 118 110; | ||||
|   --color-800: 17 94 89; | ||||
|   --color-900: 19 78 74; | ||||
| 
 | ||||
|   --color-logo-start: 45 212 191; | ||||
|   --color-logo-stop: 15 118 110; | ||||
| } | ||||
| 
 | ||||
| .theme-cyan { | ||||
| @ -191,6 +233,9 @@ | ||||
|   --color-700: 14 116 144; | ||||
|   --color-800: 21 94 117; | ||||
|   --color-900: 22 78 99; | ||||
| 
 | ||||
|   --color-logo-start: 34 211 238; | ||||
|   --color-logo-stop: 14 116 144; | ||||
| } | ||||
| 
 | ||||
| .theme-sky { | ||||
| @ -204,6 +249,9 @@ | ||||
|   --color-700: 3 105 161; | ||||
|   --color-800: 7 89 133; | ||||
|   --color-900: 12 74 110; | ||||
| 
 | ||||
|   --color-logo-start: 56 189 248; | ||||
|   --color-logo-stop: 3 105 161; | ||||
| } | ||||
| 
 | ||||
| .theme-blue { | ||||
| @ -217,6 +265,9 @@ | ||||
|   --color-700: 29 78 216; | ||||
|   --color-800: 30 64 175; | ||||
|   --color-900: 30 58 138; | ||||
| 
 | ||||
|   --color-logo-start: 96 165 250; | ||||
|   --color-logo-stop: 29 78 216; | ||||
| } | ||||
| 
 | ||||
| .theme-indigo { | ||||
| @ -230,6 +281,9 @@ | ||||
|   --color-700: 67 56 202; | ||||
|   --color-800: 55 48 163; | ||||
|   --color-900: 49 46 129; | ||||
| 
 | ||||
|   --color-logo-start: 129 140 248; | ||||
|   --color-logo-stop: 67 56 202; | ||||
| } | ||||
| 
 | ||||
| .theme-violet { | ||||
| @ -243,6 +297,9 @@ | ||||
|   --color-700: 109 40 217; | ||||
|   --color-800: 91 33 182; | ||||
|   --color-900: 76 29 149; | ||||
| 
 | ||||
|   --color-logo-start: 167 139 250; | ||||
|   --color-logo-stop: 109 40 217; | ||||
| } | ||||
| 
 | ||||
| .theme-purple { | ||||
| @ -256,6 +313,9 @@ | ||||
|   --color-700: 126 34 206; | ||||
|   --color-800: 107 33 168; | ||||
|   --color-900: 88 28 135; | ||||
| 
 | ||||
|   --color-logo-start: 192 132 252; | ||||
|   --color-logo-stop: 126 34 206; | ||||
| } | ||||
| 
 | ||||
| .theme-fuchsia { | ||||
| @ -269,6 +329,9 @@ | ||||
|   --color-700: 162 28 175; | ||||
|   --color-800: 134 25 143; | ||||
|   --color-900: 112 26 117; | ||||
| 
 | ||||
|   --color-logo-start: 232 121 249; | ||||
|   --color-logo-stop: 162 28 175; | ||||
| } | ||||
| 
 | ||||
| .theme-pink { | ||||
| @ -282,6 +345,9 @@ | ||||
|   --color-700: 190 24 93; | ||||
|   --color-800: 157 23 77; | ||||
|   --color-900: 131 24 67; | ||||
| 
 | ||||
|   --color-logo-start: 244 114 182; | ||||
|   --color-logo-stop: 190 24 93; | ||||
| } | ||||
| 
 | ||||
| .theme-rose { | ||||
| @ -295,4 +361,7 @@ | ||||
|   --color-700: 190 18 60; | ||||
|   --color-800: 159 18 57; | ||||
|   --color-900: 136 19 55; | ||||
| 
 | ||||
|   --color-logo-start: 251 113 133; | ||||
|   --color-logo-stop: 190 18 60; | ||||
| } | ||||
|  | ||||
| @ -59,7 +59,7 @@ export async function servicesFromDocker() { | ||||
|         let constructedService = null; | ||||
| 
 | ||||
|         Object.keys(container.Labels).forEach((label) => { | ||||
|           if (label.startsWith("homepage")) { | ||||
|           if (label.startsWith("homepage.")) { | ||||
|             if (!constructedService) { | ||||
|               constructedService = { | ||||
|                 container: container.Names[0].replace(/^\//, ""), | ||||
|  | ||||
							
								
								
									
										142
									
								
								src/utils/styles/themes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,142 @@ | ||||
| const themes = { | ||||
|   white: { | ||||
|     light: "#ffffff", | ||||
|     iconStart: "#ffffff", | ||||
|     iconEnd: "#282828", | ||||
|     dark: "#ffffff", | ||||
|   }, | ||||
|   slate: { | ||||
|     light: "#f8fafc", | ||||
|     iconStart: "#94a3b8", | ||||
|     iconEnd: "#334155", | ||||
|     dark: "#1e293b", | ||||
|   }, | ||||
|   gray: { | ||||
|     light: "#f9fafb", | ||||
|     iconStart: "#9ca3af", | ||||
|     iconEnd: "#374151", | ||||
|     dark: "#1f2937", | ||||
|   }, | ||||
|   zinc: { | ||||
|     light: "#fafafa", | ||||
|     iconStart: "#a1a1aa", | ||||
|     iconEnd: "#3f3f46", | ||||
|     dark: "#27272a", | ||||
|   }, | ||||
|   neutral: { | ||||
|     light: "#fafafa", | ||||
|     iconStart: "#a3a3a3", | ||||
|     iconEnd: "#404040", | ||||
|     dark: "#262626", | ||||
|   }, | ||||
|   stone: { | ||||
|     light: "#fafaf9", | ||||
|     iconStart: "#a8a29e", | ||||
|     iconEnd: "#44403c", | ||||
|     dark: "#292524", | ||||
|   }, | ||||
|   red: { | ||||
|     light: "#fef2f2", | ||||
|     iconStart: "#f87171", | ||||
|     iconEnd: "#b91c1c", | ||||
|     dark: "#991b1b", | ||||
|   }, | ||||
|   orange: { | ||||
|     light: "#fff7ed", | ||||
|     iconStart: "#fb923c", | ||||
|     iconEnd: "#c2410c", | ||||
|     dark: "#9a3412", | ||||
|   }, | ||||
|   amber: { | ||||
|     light: "#fffbeb", | ||||
|     iconStart: "#fbbf24", | ||||
|     iconEnd: "#b45309", | ||||
|     dark: "#92400e", | ||||
|   }, | ||||
|   yellow: { | ||||
|     light: "#fefce8", | ||||
|     iconStart: "#facc15", | ||||
|     iconEnd: "#a16207", | ||||
|     dark: "#854d0e", | ||||
|   }, | ||||
|   lime: { | ||||
|     light: "#f7fee7", | ||||
|     iconStart: "#a3e635", | ||||
|     iconEnd: "#4d7c0f", | ||||
|     dark: "#3f6212", | ||||
|   }, | ||||
|   green: { | ||||
|     light: "#f0fdf4", | ||||
|     iconStart: "#4ade80", | ||||
|     iconEnd: "#15803d", | ||||
|     dark: "#166534", | ||||
|   }, | ||||
|   emerald: { | ||||
|     light: "#ecfdf5", | ||||
|     iconStart: "#34d399", | ||||
|     iconEnd: "#047857", | ||||
|     dark: "#065f46", | ||||
|   }, | ||||
|   teal: { | ||||
|     light: "#f0fdfa", | ||||
|     iconStart: "#2dd4bf", | ||||
|     iconEnd: "#0f766e", | ||||
|     dark: "#115e59", | ||||
|   }, | ||||
|   cyan: { | ||||
|     light: "#ecfeff", | ||||
|     iconStart: "#22d3ee", | ||||
|     iconEnd: "#0e7490", | ||||
|     dark: "#155e75", | ||||
|   }, | ||||
|   sky: { | ||||
|     light: "#f0f9ff", | ||||
|     iconStart: "#38bdf8", | ||||
|     iconEnd: "#0369a1", | ||||
|     dark: "#075985", | ||||
|   }, | ||||
|   blue: { | ||||
|     light: "#eff6ff", | ||||
|     iconStart: "#60a5fa", | ||||
|     iconEnd: "#1d4ed8", | ||||
|     dark: "#1e40af", | ||||
|   }, | ||||
|   indigo: { | ||||
|     light: "#eef2ff", | ||||
|     iconStart: "#818cf8", | ||||
|     iconEnd: "#4338ca", | ||||
|     dark: "#3730a3", | ||||
|   }, | ||||
|   violet: { | ||||
|     light: "#f5f3ff", | ||||
|     iconStart: "#a78bfa", | ||||
|     iconEnd: "#6d28d9", | ||||
|     dark: "#5b21b6", | ||||
|   }, | ||||
|   purple: { | ||||
|     light: "#faf5ff", | ||||
|     iconStart: "#c084fc", | ||||
|     iconEnd: "#7e22ce", | ||||
|     dark: "#6b21a8", | ||||
|   }, | ||||
|   fuchsia: { | ||||
|     light: "#fdf4ff", | ||||
|     iconStart: "#e879f9", | ||||
|     iconEnd: "#a21caf", | ||||
|     dark: "#86198f", | ||||
|   }, | ||||
|   pink: { | ||||
|     light: "#fdf2f8", | ||||
|     iconStart: "#f472b6", | ||||
|     iconEnd: "#be185d", | ||||
|     dark: "#9d174d", | ||||
|   }, | ||||
|   rose: { | ||||
|     light: "#fff1f2", | ||||
|     iconStart: "#fb7185", | ||||
|     iconEnd: "#be123c", | ||||
|     dark: "#9f1239", | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| export default themes; | ||||
 shamoon
						shamoon