瀏覽代碼

feat(web) dark mode (#867)

Alex 2 年之前
父節點
當前提交
f94176a910
共有 39 個文件被更改,包括 364 次插入188 次删除
  1. 3 3
      web/src/app.css
  2. 2 2
      web/src/app.html
  3. 10 8
      web/src/lib/components/admin-page/jobs/job-tile.svelte
  4. 9 5
      web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte
  5. 8 6
      web/src/lib/components/admin-page/server-stats/stats-card.svelte
  6. 9 5
      web/src/lib/components/admin-page/user-management.svelte
  7. 6 8
      web/src/lib/components/album-page/album-card.svelte
  8. 13 9
      web/src/lib/components/album-page/album-viewer.svelte
  9. 6 6
      web/src/lib/components/album-page/asset-selection.svelte
  10. 3 4
      web/src/lib/components/album-page/share-info-modal.svelte
  11. 5 5
      web/src/lib/components/album-page/user-selection-modal.svelte
  12. 1 1
      web/src/lib/components/asset-viewer/asset-viewer.svelte
  13. 7 7
      web/src/lib/components/asset-viewer/detail-panel.svelte
  14. 10 4
      web/src/lib/components/forms/admin-registration-form.svelte
  15. 10 4
      web/src/lib/components/forms/change-password-form.svelte
  16. 10 4
      web/src/lib/components/forms/create-user-form.svelte
  17. 11 6
      web/src/lib/components/forms/edit-user-form.svelte
  18. 6 4
      web/src/lib/components/forms/login-form.svelte
  19. 3 1
      web/src/lib/components/photos-page/asset-date-group.svelte
  20. 1 1
      web/src/lib/components/shared-components/base-modal.svelte
  21. 2 3
      web/src/lib/components/shared-components/circle-icon-button.svelte
  22. 1 1
      web/src/lib/components/shared-components/context-menu/context-menu.svelte
  23. 1 1
      web/src/lib/components/shared-components/context-menu/menu-option.svelte
  24. 4 5
      web/src/lib/components/shared-components/control-app-bar.svelte
  25. 1 1
      web/src/lib/components/shared-components/immich-thumbnail.svelte
  26. 26 16
      web/src/lib/components/shared-components/navigation-bar.svelte
  27. 3 3
      web/src/lib/components/shared-components/scrollbar/scrollbar.svelte
  28. 6 3
      web/src/lib/components/shared-components/side-bar/side-bar-button.svelte
  29. 2 3
      web/src/lib/components/shared-components/side-bar/side-bar.svelte
  30. 12 12
      web/src/lib/components/shared-components/status-box.svelte
  31. 77 0
      web/src/lib/components/shared-components/theme-button.svelte
  32. 7 4
      web/src/lib/components/sharing-page/shared-album-list-tile.svelte
  33. 37 20
      web/src/routes/+layout.svelte
  34. 6 2
      web/src/routes/+page.svelte
  35. 5 3
      web/src/routes/admin/+page.svelte
  36. 15 7
      web/src/routes/albums/+page.svelte
  37. 6 2
      web/src/routes/photos/+page.svelte
  38. 11 6
      web/src/routes/sharing/+page.svelte
  39. 9 3
      web/tailwind.config.cjs

+ 3 - 3
web/src/app.css

@@ -59,15 +59,15 @@ input:focus-visible {
 
 @layer utilities {
 	.immich-form-input {
-		@apply bg-slate-100 p-2 rounded-md focus:border-immich-primary text-sm;
+		@apply bg-slate-100 p-2 rounded-md dark:text-immich-dark-bg focus:border-immich-primary text-sm;
 	}
 
 	.immich-form-label {
-		@apply font-medium text-sm text-gray-500;
+		@apply font-medium text-sm text-gray-500 dark:text-gray-300;
 	}
 
 	.immich-btn-primary {
-		@apply bg-immich-primary text-gray-100 border rounded-xl py-2 px-4 transition-all duration-150 hover:bg-immich-primary hover:shadow-lg text-sm font-medium;
+		@apply bg-immich-primary dark:bg-immich-dark-primary dark:text-immich-dark-gray text-gray-100 border dark:border-immich-dark-gray rounded-xl py-2 px-4 transition-all duration-150 hover:bg-immich-primary dark:hover:bg-immich-dark-primary/90 hover:shadow-lg text-sm font-medium;
 	}
 
 	.immich-text-button {

+ 2 - 2
web/src/app.html

@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="en" class="dark">
 	<head>
 		<meta charset="utf-8" />
 		<link rel="icon" href="%sveltekit.assets%/favicon.png" />
@@ -7,7 +7,7 @@
 		%sveltekit.head%
 	</head>
 
-	<body>
+	<body class="bg-immich-bg dark:bg-immich-dark-bg">
 		<div>%sveltekit.body%</div>
 	</body>
 </html>

+ 10 - 8
web/src/lib/components/admin-page/jobs/job-tile.svelte

@@ -11,17 +11,17 @@
 	const dispatch = createEventDispatcher();
 </script>
 
-<div class="flex border-b pb-5">
+<div class="flex border-b pb-5 dark:border-b-immich-dark-gray">
 	<div class="w-[70%]">
-		<h1 class="text-immich-primary text-sm">{title.toUpperCase()}</h1>
-		<p class="text-sm mt-1">{subtitle}</p>
-		<p class="text-sm">
+		<h1 class="text-immich-primary dark:text-immich-dark-primary text-sm">{title.toUpperCase()}</h1>
+		<p class="text-sm mt-1 dark:text-immich-dark-fg">{subtitle}</p>
+		<p class="text-sm dark:text-immich-dark-fg">
 			<slot />
 		</p>
 		<table class="text-left w-full mt-5">
 			<!-- table header -->
 			<thead
-				class="border rounded-md mb-2 bg-immich-primary/10 flex text-immich-primary w-full h-12"
+				class="border rounded-md mb-2 dark:bg-immich-dark-gray dark:border-immich-dark-gray bg-immich-primary/10 flex text-immich-primary dark:text-immich-dark-primary w-full h-12"
 			>
 				<tr class="flex w-full place-items-center">
 					<th class="text-center w-1/3 font-medium text-sm">Status</th>
@@ -29,8 +29,10 @@
 					<th class="text-center w-1/3 font-medium text-sm">Waiting</th>
 				</tr>
 			</thead>
-			<tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border bg-white">
-				<tr class="text-center flex place-items-center w-full h-[40px]">
+			<tbody
+				class="overflow-y-auto rounded-md w-full max-h-[320px] block border bg-white dark:border-immich-dark-gray dark:bg-[#e5e5e5] dark:text-immich-dark-bg"
+			>
+				<tr class="text-center flex place-items-center w-full h-[60px]">
 					<td class="text-sm px-2 w-1/3 text-ellipsis">{jobStatus ? 'Active' : 'Idle'}</td>
 					<td class="text-sm px-2 w-1/3 text-ellipsis">{activeJobCount}</td>
 					<td class="text-sm px-2 w-1/3 text-ellipsis">{waitingJobCount}</td>
@@ -41,7 +43,7 @@
 	<div class="w-[30%] flex place-items-center place-content-end">
 		<button
 			on:click={() => dispatch('click')}
-			class="px-6 py-3 text-sm bg-immich-primary font-medium rounded-2xl hover:bg-immich-primary/50 transition-all hover:cursor-pointer disabled:cursor-not-allowed shadow-sm text-immich-bg"
+			class="px-6 py-3 text-sm bg-immich-primary dark:bg-immich-dark-primary font-medium rounded-2xl hover:bg-immich-primary/50 transition-all hover:cursor-pointer disabled:cursor-not-allowed shadow-sm text-immich-bg dark:text-immich-dark-gray"
 			disabled={jobStatus}
 		>
 			{#if jobStatus}

+ 9 - 5
web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte

@@ -22,7 +22,7 @@
 
 <div class="flex flex-col gap-5">
 	<div>
-		<p class="text-sm">TOTAL USAGE</p>
+		<p class="text-sm dark:text-immich-dark-fg">TOTAL USAGE</p>
 
 		<div class="flex mt-5 justify-between">
 			<StatsCard logo={CameraIris} title={'PHOTOS'} value={stats.photos.toString()} />
@@ -33,9 +33,11 @@
 	</div>
 
 	<div>
-		<p class="text-sm">USER USAGE DETAIL</p>
+		<p class="text-sm dark:text-immich-dark-fg">USER USAGE DETAIL</p>
 		<table class="text-left w-full mt-5">
-			<thead class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12">
+			<thead
+				class="border rounded-md mb-4 bg-gray-50 dark:bg-immich-dark-gray dark:border-immich-dark-gray flex text-immich-primary dark:text-immich-dark-primary  w-full h-12"
+			>
 				<tr class="flex w-full place-items-center">
 					<th class="text-center w-1/5 font-medium text-sm">User</th>
 					<th class="text-center w-1/5 font-medium text-sm">Photos</th>
@@ -44,11 +46,13 @@
 					<th class="text-center w-1/5 font-medium text-sm">Size</th>
 				</tr>
 			</thead>
-			<tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border">
+			<tbody
+				class="overflow-y-auto rounded-md w-full max-h-[320px] block border dark:border-immich-dark-gray dark:text-immich-dark-bg"
+			>
 				{#each stats.usageByUser as user, i}
 					<tr
 						class={`text-center flex place-items-center w-full h-[50px] ${
-							i % 2 == 0 ? 'bg-immich-gray' : 'bg-immich-bg'
+							i % 2 == 0 ? 'bg-immich-gray dark:bg-[#e5e5e5]' : 'bg-immich-bg dark:bg-[#eeeeee]'
 						}`}
 					>
 						<td class="text-sm px-2 w-1/5 text-ellipsis">{getFullName(user.userId)}</td>

+ 8 - 6
web/src/lib/components/admin-page/server-stats/stats-card.svelte

@@ -17,15 +17,17 @@
 	};
 </script>
 
-<div class="w-[180px] h-[140px] bg-immich-gray rounded-3xl p-5 flex flex-col justify-between">
-	<div class="flex place-items-center gap-4">
-		<svelte:component this={logo} size="40" color={'#4250af'} />
-		<p class="text-immich-primary">{title}</p>
+<div
+	class="w-[180px] h-[140px] bg-immich-gray dark:bg-immich-dark-gray rounded-3xl p-5 flex flex-col justify-between"
+>
+	<div class="flex place-items-center gap-4 text-immich-primary dark:text-immich-dark-primary">
+		<svelte:component this={logo} size="40" />
+		<p>{title}</p>
 	</div>
 
 	<div class="relative text-center font-mono font-semibold text-2xl">
-		<span class="text-[#DCDADA]">{zeros()}</span><span class="text-immich-primary"
-			>{parseInt(value)}</span
+		<span class="text-[#DCDADA] dark:text-[#525252]">{zeros()}</span><span
+			class="text-immich-primary dark:text-immich-dark-primary">{parseInt(value)}</span
 		>
 		{#if unit}
 			<span class="absolute -top-5 right-2 text-base font-light text-gray-400">{unit}</span>

+ 9 - 5
web/src/lib/components/admin-page/user-management.svelte

@@ -9,7 +9,9 @@
 </script>
 
 <table class="text-left w-full my-5">
-	<thead class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12 ">
+	<thead
+		class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12 dark:bg-immich-dark-gray dark:text-immich-dark-primary dark:border-immich-dark-gray"
+	>
 		<tr class="flex w-full place-items-center">
 			<th class="text-center w-1/4 font-medium text-sm">Email</th>
 			<th class="text-center w-1/4 font-medium text-sm">First name</th>
@@ -17,11 +19,13 @@
 			<th class="text-center w-1/4 font-medium text-sm">Edit</th>
 		</tr>
 	</thead>
-	<tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border">
+	<tbody
+		class="overflow-y-auto rounded-md w-full max-h-[320px] block border dark:border-immich-dark-gray"
+	>
 		{#each allUsers as user, i}
 			<tr
-				class={`text-center flex place-items-center w-full h-[80px] ${
-					i % 2 == 0 ? 'bg-gray-100' : 'bg-immich-bg'
+				class={`text-center flex place-items-center w-full h-[80px] dark:text-immich-dark-bg ${
+					i % 2 == 0 ? 'bg-immich-gray dark:bg-[#e5e5e5]' : 'bg-immich-bg dark:bg-[#eeeeee]'
 				}`}
 			>
 				<td class="text-sm px-4 w-1/4 text-ellipsis">{user.email}</td>
@@ -32,7 +36,7 @@
 						on:click={() => {
 							dispatch('edit-user', { user });
 						}}
-						class="bg-immich-primary text-gray-100 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
+						class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700  rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
 						><PencilOutline size="20" /></button
 					></td
 				>

+ 6 - 8
web/src/lib/components/album-page/album-card.svelte

@@ -60,12 +60,7 @@
 		on:click|stopPropagation|preventDefault={showAlbumContextMenu}
 		data-testid="context-button-parent"
 	>
-		<CircleIconButton
-			logo={DotsVertical}
-			size={'20'}
-			hoverColor={'rgba(95,99,104, 0.5)'}
-			logoColor={'#fdf8ec'}
-		/>
+		<CircleIconButton logo={DotsVertical} size={'20'} hoverColor={'rgba(95,99,104, 0.5)'} />
 	</div>
 
 	<div class={`h-[275px] w-[275px] z-[-1]`}>
@@ -78,11 +73,14 @@
 	</div>
 
 	<div class="mt-4">
-		<p class="text-sm font-medium text-gray-800" data-testid="album-name">
+		<p
+			class="text-sm font-medium text-gray-800 dark:text-immich-dark-primary"
+			data-testid="album-name"
+		>
 			{album.albumName}
 		</p>
 
-		<span class="text-xs flex gap-2" data-testid="album-details">
+		<span class="text-xs flex gap-2 dark:text-immich-dark-fg" data-testid="album-details">
 			<p>{album.assetCount} items</p>
 
 			{#if album.shared}

+ 13 - 9
web/src/lib/components/album-page/album-viewer.svelte

@@ -331,7 +331,7 @@
 	};
 </script>
 
-<section class="bg-immich-bg">
+<section class="bg-immich-bg dark:bg-immich-dark-bg">
 	<!-- Multiselection mode app bar -->
 	{#if isMultiSelectionMode}
 		<ControlAppBar
@@ -340,7 +340,9 @@
 			tailwindClasses={'bg-white shadow-md'}
 		>
 			<svelte:fragment slot="leading">
-				<p class="font-medium text-immich-primary">Selected {multiSelectAsset.size}</p>
+				<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
+					Selected {multiSelectAsset.size}
+				</p>
 			</svelte:fragment>
 			<svelte:fragment slot="trailing">
 				{#if isOwned}
@@ -386,7 +388,7 @@
 					<button
 						disabled={album.assetCount == 0}
 						on:click={() => (isShowShareUserSelection = true)}
-						class="immich-text-button border bg-immich-primary text-gray-50 hover:bg-immich-primary/75 px-6 text-sm disabled:opacity-25 disabled:bg-gray-500 disabled:cursor-not-allowed"
+						class="immich-text-button border bg-immich-primary dark:bg-immich-dark-primary text-gray-50 hover:bg-immich-primary/75 px-6 text-sm disabled:opacity-25 disabled:bg-gray-500 disabled:cursor-not-allowed dark:text-immich-dark-bg dark:border-immich-dark-gray"
 						><span class="px-2">Share</span></button
 					>
 				{/if}
@@ -404,9 +406,9 @@
 			}}
 			on:focus={() => (isEditingTitle = true)}
 			on:blur={() => (isEditingTitle = false)}
-			class={`transition-all text-6xl text-immich-primary w-[99%] border-b-2 border-transparent outline-none ${
+			class={`transition-all text-6xl text-immich-primary dark:text-immich-dark-primary w-[99%] border-b-2 border-transparent outline-none ${
 				isOwned ? 'hover:border-gray-400' : 'hover:border-transparent'
-			} focus:outline-none focus:border-b-2 focus:border-immich-primary bg-immich-bg`}
+			} focus:outline-none focus:border-b-2 focus:border-immich-primary dark:focus:border-immich-dark-primary bg-immich-bg dark:bg-immich-dark-bg dark:focus:bg-immich-dark-gray`}
 			type="text"
 			bind:value={album.albumName}
 			disabled={!isOwned}
@@ -468,13 +470,15 @@
 			<!-- Album is empty - Show asset selectection buttons -->
 			<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
 				<div class="w-[300px]">
-					<p class="text-xs">ADD PHOTOS</p>
+					<p class="text-xs dark:text-immich-dark-fg">ADD PHOTOS</p>
 					<button
 						on:click={() => (isShowAssetSelection = true)}
-						class="w-full py-8 border bg-white rounded-md mt-5 flex place-items-center gap-6 px-8 transition-all hover:bg-gray-100 hover:text-immich-primary"
+						class="w-full py-8 border bg-immich-bg dark:bg-immich-dark-gray text-immich-fg dark:text-immich-dark-fg dark:hover:text-immich-dark-primary rounded-md mt-5 flex place-items-center gap-6 px-8 transition-all hover:bg-gray-100 hover:text-immich-primary dark:border-none"
 					>
-						<span><Plus color="#4250af" size="24" /> </span>
-						<span class="text-lg text-immich-fg">Select photos</span>
+						<span class="text-text-immich-primary dark:text-immich-dark-primary"
+							><Plus size="24" />
+						</span>
+						<span class="text-lg">Select photos</span>
 					</button>
 				</div>
 			</section>

+ 6 - 6
web/src/lib/components/album-page/asset-selection.svelte

@@ -70,7 +70,7 @@
 
 <section
 	transition:fly={{ y: 500, duration: 100, easing: quintOut }}
-	class="absolute top-0 left-0 w-full h-full  bg-immich-bg z-[9999]"
+	class="absolute top-0 left-0 w-full h-full bg-immich-bg dark:bg-immich-dark-bg z-[9999]"
 >
 	<ControlAppBar
 		on:close-button-click={() => {
@@ -80,28 +80,28 @@
 	>
 		<svelte:fragment slot="leading">
 			{#if $selectedAssets.size == 0}
-				<p class="text-lg">Add to album</p>
+				<p class="text-lg dark:text-immich-dark-fg">Add to album</p>
 			{:else}
-				<p class="text-lg">{$selectedAssets.size} selected</p>
+				<p class="text-lg dark:text-immich-dark-fg">{$selectedAssets.size} selected</p>
 			{/if}
 		</svelte:fragment>
 
 		<svelte:fragment slot="trailing">
 			<button
 				on:click={() => openFileUploadDialog(UploadType.ALBUM)}
-				class="text-immich-primary text-sm hover:bg-immich-primary/10 transition-all px-6 py-2 rounded-lg font-medium"
+				class="text-immich-primary dark:text-immich-dark-primary text-sm hover:bg-immich-primary/10 dark:hover:bg-immich-dark-primary/25 transition-all px-6 py-2 rounded-lg font-medium"
 			>
 				Select from computer
 			</button>
 			<button
 				disabled={$selectedAssets.size === 0}
 				on:click={addSelectedAssets}
-				class="immich-text-button border bg-immich-primary text-gray-50 hover:bg-immich-primary/75 px-6 text-sm disabled:opacity-25 disabled:bg-gray-500 disabled:cursor-not-allowed"
+				class="immich-text-button border bg-immich-primary dark:bg-immich-dark-primary text-gray-50 hover:bg-immich-primary/75 px-6 text-sm disabled:opacity-25 disabled:bg-gray-500 disabled:cursor-not-allowed dark:text-immich-dark-bg dark:border-immich-dark-gray"
 				><span class="px-2">Done</span></button
 			>
 		</svelte:fragment>
 	</ControlAppBar>
-	<section class="pt-[100px] pl-[70px] grid h-screen bg-immich-bg">
+	<section class="pt-[100px] pl-[70px] grid h-screen bg-immich-bg dark:bg-immich-dark-bg">
 		<AssetGrid isAlbumSelectionMode={true} />
 	</section>
 </section>

+ 3 - 4
web/src/lib/components/album-page/share-info-modal.svelte

@@ -68,14 +68,14 @@
 <BaseModal on:close={() => dispatch('close')}>
 	<svelte:fragment slot="title">
 		<span class="flex gap-2 place-items-center">
-			<p class="font-medium text-immich-fg">Options</p>
+			<p class="font-medium text-immich-fg dark:text-immich-dark-fg">Options</p>
 		</span>
 	</svelte:fragment>
 
 	<section class="max-h-[400px] overflow-y-auto immich-scrollbar pb-4">
 		{#each album.sharedUsers as user}
 			<div
-				class="flex gap-4 p-5 place-items-center justify-between w-full transition-colors hover:bg-gray-50"
+				class="flex gap-4 p-5 place-items-center justify-between w-full transition-colors hover:bg-gray-50 dark:hover:bg-gray-700"
 			>
 				<div class="flex gap-4 place-items-center">
 					<CircleAvatar {user} />
@@ -88,14 +88,13 @@
 							on:click={() => showContextMenu(user.id)}
 							logo={DotsVertical}
 							backgroundColor={'transparent'}
-							logoColor={'#5f6368'}
 							hoverColor={'#e2e7e9'}
 							size={'20'}
 						/>
 					{:else if user.id == currentUser?.id}
 						<button
 							on:click={() => removeUser('me')}
-							class="text-sm text-immich-primary font-medium transition-colors hover:text-immich-primary/75"
+							class="text-sm text-immich-primary dark:text-immich-dark-primary font-medium transition-colors hover:text-immich-primary/75"
 							>Leave</button
 						>
 					{/if}

+ 5 - 5
web/src/lib/components/album-page/user-selection-modal.svelte

@@ -38,7 +38,7 @@
 	<svelte:fragment slot="title">
 		<span class="flex gap-2 place-items-center">
 			<img src="/immich-logo.svg" width="24" alt="Immich" />
-			<p class="font-medium text-immich-fg">Invite to album</p>
+			<p class="font-medium">Invite to album</p>
 		</span>
 	</svelte:fragment>
 
@@ -51,7 +51,7 @@
 					{#key user.id}
 						<button
 							on:click={() => deselectUser(user)}
-							class="flex gap-1 place-items-center border border-gray-400 rounded-full p-1 hover:bg-gray-200 transition-colors"
+							class="flex gap-1 place-items-center border border-gray-400 rounded-full p-1 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
 						>
 							<CircleAvatar size={28} {user} />
 							<p class="text-xs font-medium">{user.firstName} {user.lastName}</p>
@@ -68,11 +68,11 @@
 				{#each users as user}
 					<button
 						on:click={() => selectUser(user)}
-						class="w-full flex place-items-center gap-4 py-4 px-5 hover:bg-gray-200  transition-all"
+						class="w-full flex place-items-center gap-4 py-4 px-5 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all"
 					>
 						{#if selectedUsers.includes(user)}
 							<span
-								class="bg-immich-primary text-white rounded-full w-12 h-12 border flex place-items-center place-content-center text-3xl"
+								class="bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-bg rounded-full w-12 h-12 border flex place-items-center place-content-center text-3xl dark:border-immich-dark-gray"
 								>✓</span
 							>
 						{:else}
@@ -80,7 +80,7 @@
 						{/if}
 
 						<div class="text-left">
-							<p class="text-immich-fg">
+							<p class="text-immich-fg dark:text-immich-dark-fg">
 								{user.firstName}
 								{user.lastName}
 							</p>

+ 1 - 1
web/src/lib/components/asset-viewer/asset-viewer.svelte

@@ -207,7 +207,7 @@
 		<div
 			transition:fly={{ duration: 150 }}
 			id="detail-panel"
-			class="bg-immich-bg w-[360px] row-span-full transition-all overflow-y-auto"
+			class="bg-immich-bg w-[360px] row-span-full transition-all overflow-y-auto dark:bg-immich-dark-bg dark:border-l dark:border-l-immich-dark-gray"
 			translate="yes"
 		>
 			<DetailPanel {asset} albums={appearsInAlbums} on:close={() => (isShowDetail = false)} />

+ 7 - 7
web/src/lib/components/asset-viewer/detail-panel.svelte

@@ -97,16 +97,16 @@
 	};
 </script>
 
-<section class="p-2">
+<section class="p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
 	<div class="flex place-items-center gap-2">
 		<button
-			class="rounded-full p-3 flex place-items-center place-content-center hover:bg-gray-200 transition-colors"
+			class="rounded-full p-3 flex place-items-center place-content-center hover:bg-gray-200 transition-colors dark:text-immich-dark-fg dark:hover:bg-gray-900"
 			on:click={() => dispatch('close')}
 		>
-			<Close size="24" color="#232323" />
+			<Close size="24" />
 		</button>
 
-		<p class="text-immich-fg text-lg">Info</p>
+		<p class="text-immich-fg dark:text-immich-dark-fg text-lg">Info</p>
 	</div>
 
 	<div class="px-4 py-4">
@@ -202,10 +202,10 @@
 	<div class="h-[360px] w-full" id="map" />
 </div>
 
-<section class="p-2">
+<section class="p-2 dark:text-immich-dark-fg">
 	<div class="px-4 py-4">
 		{#if albums.length > 0}
-			<p class="text-sm pb-4">APPEARS IN</p>
+			<p class="text-sm pb-4 ">APPEARS IN</p>
 		{/if}
 		{#each albums as album}
 			<a data-sveltekit-prefetch href={`/albums/${album.id}`}>
@@ -219,7 +219,7 @@
 					</div>
 
 					<div class="mt-auto mb-auto">
-						<p>{album.albumName}</p>
+						<p class="dark:text-immich-dark-primary">{album.albumName}</p>
 						<div class="flex gap-2 text-sm">
 							<p>{album.assetCount} items</p>
 							{#if album.shared}

+ 10 - 4
web/src/lib/components/forms/admin-registration-form.svelte

@@ -51,11 +51,17 @@
 	}
 </script>
 
-<div class="border bg-white p-4 shadow-sm w-[500px] rounded-md py-8">
+<div
+	class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] rounded-3xl py-8 dark:text-immich-dark-fg"
+>
 	<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
 		<img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo" />
-		<h1 class="text-2xl text-immich-primary font-medium">Admin Registration</h1>
-		<p class="text-sm border rounded-md p-4 font-mono text-gray-600">
+		<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
+			Admin Registration
+		</h1>
+		<p
+			class="text-sm border rounded-md p-4 font-mono text-gray-600 dark:border-immich-dark-bg dark:text-gray-300"
+		>
 			Since you are the first user on the system, you will be assigned as the Admin and are
 			responsible for administrative tasks, and additional users will be created by you.
 		</p>
@@ -117,7 +123,7 @@
 		<div class="flex w-full">
 			<button
 				type="submit"
-				class="m-4 p-2 bg-immich-primary hover:bg-immich-primary/75 px-6 py-4 text-white rounded-md shadow-md w-full"
+				class="m-4 p-2 bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-4 text-white rounded-md shadow-md w-full"
 				>Sign Up</button
 			>
 		</div>

+ 10 - 4
web/src/lib/components/forms/change-password-form.svelte

@@ -43,12 +43,18 @@
 	}
 </script>
 
-<div class="border bg-white p-4 shadow-sm w-[500px] rounded-md py-8">
+<div
+	class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] rounded-3xl py-8 dark:text-immich-dark-fg"
+>
 	<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
 		<img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo" />
-		<h1 class="text-2xl text-immich-primary font-medium">Change Password</h1>
+		<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
+			Change Password
+		</h1>
 
-		<p class="text-sm border rounded-md p-4 font-mono text-gray-600">
+		<p
+			class="text-sm border rounded-md p-4 font-mono text-gray-600 dark:border-immich-dark-bg dark:text-gray-300"
+		>
 			Hi {user.firstName}
 			{user.lastName} ({user.email}),
 			<br />
@@ -93,7 +99,7 @@
 		<div class="flex w-full">
 			<button
 				type="submit"
-				class="m-4 p-2 bg-immich-primary hover:bg-immich-primary/75 px-6 py-4 text-white rounded-md shadow-md w-full"
+				class="m-4 p-2 bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-4 text-white rounded-md shadow-md w-full"
 				>Change Password</button
 			>
 		</div>

+ 10 - 4
web/src/lib/components/forms/create-user-form.svelte

@@ -53,11 +53,17 @@
 	}
 </script>
 
-<div class="border bg-white p-4 shadow-sm w-[500px] rounded-3xl py-8">
+<div
+	class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] rounded-3xl py-8 dark:text-immich-dark-fg"
+>
 	<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
 		<img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo" />
-		<h1 class="text-2xl text-immich-primary font-medium">Create new user</h1>
-		<p class="text-sm border rounded-md p-4 font-mono text-gray-600">
+		<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
+			Create new user
+		</h1>
+		<p
+			class="text-sm border rounded-md p-4 font-mono text-gray-600 dark:border-immich-dark-bg dark:text-gray-300"
+		>
 			Please provide your user with the password, they will have to change it on their first sign
 			in.
 		</p>
@@ -113,7 +119,7 @@
 		<div class="flex w-full">
 			<button
 				type="submit"
-				class="m-4 bg-immich-primary hover:bg-immich-primary/75 px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
+				class="m-4 bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 px-6 py-3 text-white dark:text-immich-dark-gray rounded-full shadow-md w-full font-medium"
 				>Create
 			</button>
 		</div>

+ 11 - 6
web/src/lib/components/forms/edit-user-form.svelte

@@ -65,11 +65,16 @@
 	};
 </script>
 
-<div class="border bg-white p-4 shadow-sm w-[500px] rounded-3xl py-8">
-	<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
-		<!--        <img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo"/>-->
-		<AccountEditOutline size="4em" color="#4250affe" />
-		<h1 class="text-2xl text-immich-primary font-medium">Edit user</h1>
+<div
+	class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] rounded-3xl py-8 dark:text-immich-dark-fg"
+>
+	<div
+		class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
+	>
+		<AccountEditOutline size="4em" />
+		<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
+			Edit user
+		</h1>
 	</div>
 
 	<form on:submit|preventDefault={editUser} autocomplete="off">
@@ -124,7 +129,7 @@
 			</button>
 			<button
 				type="submit"
-				class="flex-1 transition-colors bg-immich-primary hover:bg-immich-primary/75 px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
+				class="flex-1 transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
 				>Confirm
 			</button>
 		</div>

+ 6 - 4
web/src/lib/components/forms/login-form.svelte

@@ -32,15 +32,17 @@
 	};
 </script>
 
-<div class="border bg-white p-4 shadow-sm w-[500px] rounded-md py-8">
+<div
+	class="border bg-white dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] rounded-md py-8"
+>
 	<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
 		<img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo" />
-		<h1 class="text-2xl text-immich-primary font-medium">Login</h1>
+		<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">Login</h1>
 	</div>
 
 	{#if loginPageMessage}
 		<p
-			class="text-sm border rounded-md m-4 p-4 text-immich-primary font-medium bg-immich-primary/5"
+			class="text-sm border rounded-md m-4 p-4 text-immich-primary dark:text-immich-dark-primary font-medium bg-immich-primary/5 dark:border-immich-dark-bg"
 		>
 			{@html loginPageMessage}
 		</p>
@@ -78,7 +80,7 @@
 		<div class="flex w-full">
 			<button
 				type="submit"
-				class="m-4 p-2 bg-immich-primary hover:bg-immich-primary/75 px-6 py-4 text-white rounded-md shadow-md w-full font-semibold"
+				class="m-4 p-2 bg-immich-primary dark:bg-immich-dark-primary dark:text-immich-dark-gray dark:hover:bg-immich-dark-primary/80 hover:bg-immich-primary/75 px-6 py-4 text-white rounded-md shadow-md w-full font-semibold"
 				>Login</button
 			>
 		</div>

+ 3 - 1
web/src/lib/components/photos-page/asset-date-group.svelte

@@ -115,7 +115,9 @@
 			on:mouseleave={() => (isMouseOverGroup = false)}
 		>
 			<!-- Date group title -->
-			<p class="font-medium text-sm text-immich-fg mb-2 flex place-items-center h-6">
+			<p
+				class="font-medium text-sm text-immich-fg dark:text-immich-dark-fg mb-2 flex place-items-center h-6"
+			>
 				{#if (hoveredDateGroup == dateGroupTitle && isMouseOverGroup) || $selectedGroup.has(dateGroupTitle)}
 					<div
 						transition:fly={{ x: -24, duration: 200, opacity: 0.5 }}

+ 1 - 1
web/src/lib/components/shared-components/base-modal.svelte

@@ -36,7 +36,7 @@
 	<div
 		use:clickOutside
 		on:out-click={() => dispatch('close')}
-		class="bg-white w-[450px] min-h-[200px] max-h-[500px] rounded-lg shadow-md"
+		class="bg-immich-bg dark:bg-immich-dark-gray dark:text-immich-dark-fg w-[450px] min-h-[200px] max-h-[500px] rounded-lg shadow-md"
 	>
 		<div class="flex justify-between place-items-center p-5">
 			<div>

+ 2 - 3
web/src/lib/components/shared-components/circle-icon-button.svelte

@@ -9,7 +9,6 @@
 	export let logo: any;
 	export let backgroundColor = 'transparent';
 	export let hoverColor = '#e2e7e9';
-	export let logoColor = '#5f6368';
 	export let size = '24';
 	export let title = '';
 	let iconButton: HTMLButtonElement;
@@ -26,10 +25,10 @@
 <button
 	{title}
 	bind:this={iconButton}
-	class={`immich-circle-icon-button rounded-full p-3 flex place-items-center place-content-center transition-all`}
+	class={`immich-circle-icon-button dark:text-immich-dark-fg hover:dark:text-immich-dark-gray rounded-full p-3 flex place-items-center place-content-center transition-all`}
 	on:click={(mouseEvent) => dispatch('click', { mouseEvent })}
 >
-	<svelte:component this={logo} {size} color={logoColor} />
+	<svelte:component this={logo} {size} />
 </button>
 
 <style>

+ 1 - 1
web/src/lib/components/shared-components/context-menu/context-menu.svelte

@@ -32,7 +32,7 @@
 <div
 	transition:slide={{ duration: 200, easing: quintOut }}
 	bind:this={menuEl}
-	class="absolute bg-white w-[175px] z-[99999] rounded-lg shadow-md"
+	class="absolute w-[175px] z-[99999] rounded-lg shadow-md"
 	style={`top: ${y}px; left: ${x}px;`}
 	use:clickOutside
 	on:out-click={() => dispatch('clickoutside')}

+ 1 - 1
web/src/lib/components/shared-components/context-menu/menu-option.svelte

@@ -16,7 +16,7 @@
 <button
 	class:disabled={isDisabled}
 	on:click={handleClick}
-	class="bg-white hover:bg-immich-bg transition-all p-4 w-full text-left rounded-lg text-sm"
+	class="bg-white hover:bg-gray-300 dark:text-immich-dark-bg transition-all p-4 w-full text-left rounded-lg text-sm"
 >
 	{#if text}
 		{text}

+ 4 - 5
web/src/lib/components/shared-components/control-app-bar.svelte

@@ -14,7 +14,7 @@
 
 	const onScroll = () => {
 		if (window.pageYOffset > 80) {
-			appBarBorder = 'border border-gray-200 bg-gray-50';
+			appBarBorder = 'border border-gray-200 bg-gray-50 dark:border-gray-600';
 		} else {
 			appBarBorder = 'bg-immich-bg border border-transparent';
 		}
@@ -39,14 +39,13 @@
 >
 	<div
 		id="asset-selection-app-bar"
-		class={`flex justify-between ${appBarBorder} rounded-lg p-2 mx-2 mt-2 transition-all place-items-center ${tailwindClasses}`}
+		class={`flex justify-between ${appBarBorder} rounded-lg p-2 mx-2 mt-2 transition-all place-items-center ${tailwindClasses} dark:bg-immich-dark-gray`}
 	>
-		<div class="flex place-items-center gap-6">
+		<div class="flex place-items-center gap-6 dark:text-immich-dark-fg">
 			<CircleIconButton
 				on:click={() => dispatch('close-button-click')}
 				logo={backIcon}
 				backgroundColor={'transparent'}
-				logoColor={'rgb(75 85 99)'}
 				hoverColor={'#e2e7e9'}
 				size={'24'}
 			/>
@@ -54,7 +53,7 @@
 			<slot name="leading" />
 		</div>
 
-		<div class="flex place-items-center gap-1 mr-4">
+		<div class="flex place-items-center gap-1 mr-4 ">
 			<slot name="trailing" />
 		</div>
 	</div>

+ 1 - 1
web/src/lib/components/shared-components/immich-thumbnail.svelte

@@ -136,7 +136,7 @@
 	<div
 		style:width={`${thumbnailSize}px`}
 		style:height={`${thumbnailSize}px`}
-		class={`bg-gray-100 relative select-none  ${getSize()} ${
+		class={`bg-gray-100 dark:bg-immich-dark-gray relative select-none ${getSize()} ${
 			disabled ? 'cursor-not-allowed' : 'hover:cursor-pointer'
 		}`}
 		on:mouseenter={handleMouseOverThumbnail}

+ 26 - 16
web/src/lib/components/shared-components/navigation-bar.svelte

@@ -6,6 +6,7 @@
 	import TrayArrowUp from 'svelte-material-icons/TrayArrowUp.svelte';
 	import { clickOutside } from '../../utils/click-outside';
 	import { api, UserResponseDto } from '@api';
+	import ThemeButton from './theme-button.svelte';
 
 	export let user: UserResponseDto;
 	export let shouldShowUploadButton = true;
@@ -42,28 +43,35 @@
 	};
 </script>
 
-<section id="dashboard-navbar" class="fixed w-screen  z-[100] bg-immich-bg text-sm">
-	<div class="flex border-b place-items-center px-6 py-2 ">
+<section
+	id="dashboard-navbar"
+	class="fixed w-screen  z-[100] bg-immich-bg dark:bg-immich-dark-bg text-sm"
+>
+	<div class="flex border-b dark:border-b-immich-dark-gray place-items-center px-6 py-2 ">
 		<a
 			data-sveltekit-prefetch
 			class="flex gap-2 place-items-center hover:cursor-pointer"
 			href="/photos"
 		>
 			<img src="/immich-logo.svg" alt="immich logo" height="35" width="35" />
-			<h1 class="font-immich-title text-2xl text-immich-primary">IMMICH</h1>
+			<h1 class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary">
+				IMMICH
+			</h1>
 		</a>
 		<div class="flex-1 ml-24">
 			<input
-				class="w-[50%] border rounded-md bg-gray-200 px-8 py-4"
+				class="w-[50%] rounded-md bg-gray-200 dark:bg-immich-dark-gray  px-8 py-4"
 				placeholder="Search - Coming soon"
 			/>
 		</div>
 		<section class="flex gap-4 place-items-center">
+			<ThemeButton />
+
 			{#if $page.url.pathname !== '/admin' && shouldShowUploadButton}
 				<button
 					in:fly={{ x: 50, duration: 250 }}
 					on:click={() => dispatch('uploadClicked')}
-					class="immich-text-button"
+					class="immich-text-button dark:hover:bg-immich-dark-primary/25 dark:text-immich-dark-fg"
 				>
 					<TrayArrowUp size="20" />
 					<span> Upload </span>
@@ -73,8 +81,9 @@
 			{#if user.isAdmin}
 				<a data-sveltekit-prefetch href={`admin`}>
 					<button
-						class={`flex place-items-center place-content-center gap-2 hover:bg-immich-primary/5 p-2 rounded-lg font-medium ${
-							$page.url.pathname == '/admin' && 'text-immich-primary underline'
+						class={`flex place-items-center place-content-center gap-2 hover:bg-immich-primary/5  dark:hover:bg-immich-dark-primary/25 dark:text-immich-dark-fg p-2 rounded-lg font-medium ${
+							$page.url.pathname == '/admin' &&
+							'text-immich-primary dark:immich-dark-primary underline'
 						}`}>Administration</button
 					>
 				</a>
@@ -87,7 +96,7 @@
 				on:click={showAccountInfoPanel}
 			>
 				<button
-					class="flex place-items-center place-content-center rounded-full bg-immich-primary/80 h-12 w-12 text-gray-100 hover:bg-immich-primary"
+					class="flex place-items-center place-content-center rounded-full bg-immich-primary hover:bg-immich-primary/80 h-12 w-12 text-gray-100 dark:text-immich-dark-bg dark:bg-immich-dark-primary"
 				>
 					{#if shouldShowProfileImage}
 						<img
@@ -104,7 +113,7 @@
 					<div
 						in:fade={{ delay: 500, duration: 150 }}
 						out:fade={{ delay: 200, duration: 150 }}
-						class="absolute -bottom-12 right-5 border bg-gray-500 text-[12px] text-gray-100 p-2 rounded-md shadow-md"
+						class="absolute -bottom-12 right-5 border bg-gray-500 dark:bg-immich-dark-gray text-[12px] text-gray-100 p-2 rounded-md shadow-md dark:border-immich-dark-gray"
 					>
 						<p>{user.firstName} {user.lastName}</p>
 						<p>{user.email}</p>
@@ -119,13 +128,13 @@
 			in:fade={{ duration: 100 }}
 			out:fade={{ duration: 100 }}
 			id="account-info-panel"
-			class="absolute right-[25px] top-[75px] bg-white shadow-lg rounded-2xl w-[360px] text-center z-[100]"
+			class="absolute right-[25px] top-[75px] bg-immich-bg dark:bg-immich-dark-gray dark:border dark:border-immich-dark-gray shadow-lg rounded-2xl w-[360px] text-center z-[100]"
 			use:clickOutside
 			on:out-click={() => (shouldShowAccountInfoPanel = false)}
 		>
 			<div class="flex place-items-center place-content-center mt-6">
 				<button
-					class="flex place-items-center place-content-center rounded-full bg-immich-primary/80 h-20 w-20 text-gray-100 hover:bg-immich-primary"
+					class="flex place-items-center place-content-center rounded-full bg-immich-primary dark:bg-immich-dark-primary dark:immich-dark-primary/80 h-20 w-20 text-gray-100 hover:bg-immich-primary dark:text-immich-dark-bg"
 				>
 					{#if shouldShowProfileImage}
 						<img
@@ -141,20 +150,21 @@
 				</button>
 			</div>
 
-			<p class="text-lg text-immich-primary font-medium mt-4">
+			<p class="text-lg text-immich-primary dark:text-immich-dark-primary font-medium mt-4">
 				{user.firstName}
 				{user.lastName}
 			</p>
 
-			<p class="text-sm text-gray-500">{user.email}</p>
+			<p class="text-sm text-gray-500 dark:text-immich-dark-fg">{user.email}</p>
 
 			<div class="my-4">
-				<hr />
+				<hr class="dark:border-immich-dark-bg" />
 			</div>
 
 			<div class="mb-6">
-				<button class="border rounded-3xl px-6 py-2 hover:bg-gray-50" on:click={logOut}
-					>Sign Out</button
+				<button
+					class="border rounded-3xl px-6 py-2 hover:bg-gray-50 dark:border-immich-dark-gray dark:bg-gray-300 dark:hover:bg-immich-dark-primary"
+					on:click={logOut}>Sign Out</button
 				>
 			</div>
 		</div>

+ 3 - 3
web/src/lib/components/shared-components/scrollbar/scrollbar.svelte

@@ -109,7 +109,7 @@
 >
 	{#if isHover}
 		<div
-			class="border-b-2 border-immich-primary w-[100px] right-0 pr-6 py-1 text-sm pl-1 font-medium absolute bg-white z-50 pointer-events-none rounded-tl-md shadow-lg"
+			class="border-b-2 border-immich-primary dark:border-immich-dark-primary w-[100px] right-0 pr-6 py-1 text-sm pl-1 font-medium absolute bg-immich-bg dark:bg-immich-dark-gray z-50 pointer-events-none rounded-tl-md shadow-lg dark:text-immich-dark-fg"
 			style:top={currentMouseYLocation + 'px'}
 		>
 			{hoveredDate?.toLocaleString('default', { month: 'short' })}
@@ -120,7 +120,7 @@
 	<!-- Scroll Position Indicator Line -->
 	{#if !isDragging}
 		<div
-			class="absolute right-0 w-10 h-[2px] bg-immich-primary"
+			class="absolute right-0 w-10 h-[2px] bg-immich-primary dark:bg-immich-dark-primary"
 			style:top={scrollbarPosition + 'px'}
 		/>
 	{/if}
@@ -139,7 +139,7 @@
 				{#if segment.height > 8}
 					<div
 						aria-label={segment.timeGroup + ' ' + segment.count}
-						class="absolute right-0 pr-5 z-10 text-xs font-medium"
+						class="absolute right-0 pr-5 z-10 text-xs font-medium dark:text-immich-dark-fg"
 					>
 						{groupDate.getFullYear()}
 					</div>

+ 6 - 3
web/src/lib/components/shared-components/side-bar/side-bar-button.svelte

@@ -23,10 +23,13 @@
 
 <div
 	on:click={onButtonClicked}
-	class={`flex gap-4 place-items-center pl-5 py-3 rounded-tr-full rounded-br-full hover:bg-gray-200 hover:text-immich-primary hover:cursor-pointer
-    ${isSelected && 'bg-immich-primary/10 text-immich-primary hover:bg-immich-primary/25'}
+	class={`flex gap-4 place-items-center pl-5 py-3 rounded-tr-full rounded-br-full hover:bg-immich-gray dark:hover:bg-immich-dark-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary hover:cursor-pointer
+    ${
+			isSelected &&
+			'bg-immich-primary/10 dark:bg-immich-dark-primary/10 text-immich-primary dark:text-[#adcbfa] hover:bg-immich-primary/25'
+		}
   `}
 >
 	<svelte:component this={logo} size="24" />
-	<p class="font-medium text-sm">{title}</p>
+	<p class="font-medium text-sm ">{title}</p>
 </div>

+ 2 - 3
web/src/lib/components/shared-components/side-bar/side-bar.svelte

@@ -52,8 +52,7 @@
 	};
 </script>
 
-<section id="sidebar" class="flex flex-col gap-1 pt-8 pr-6">
-	<!-- {domCount} -->
+<section id="sidebar" class="flex flex-col gap-1 pt-8 pr-6 bg-immich-bg dark:bg-immich-dark-bg">
 	<a
 		data-sveltekit-prefetch
 		data-sveltekit-noscroll
@@ -127,7 +126,7 @@
 			{/if}
 		</div>
 	</a>
-	<div class="text-xs ml-5 my-4">
+	<div class="text-xs ml-5 my-4 dark:text-immich-dark-fg">
 		<p>LIBRARY</p>
 	</div>
 	<a data-sveltekit-prefetch href={$page.routeId !== 'albums' ? `/albums` : null} class="relative">

+ 12 - 12
web/src/lib/components/shared-components/status-box.svelte

@@ -46,18 +46,18 @@
 	};
 </script>
 
-<div>
+<div class="dark:text-immich-dark-fg">
 	<div class="storage-status grid grid-cols-[64px_auto]">
-		<div class="pl-5 pr-6 text-immich-primary">
+		<div class="pl-5 pr-6 text-immich-primary dark:text-immich-dark-primary">
 			<Cloud size={'24'} />
 		</div>
 		<div>
-			<p class="text-sm font-medium text-immich-primary">Storage</p>
+			<p class="text-sm font-medium text-immich-primary dark:text-immich-dark-primary">Storage</p>
 			{#if serverInfo}
 				<div class="w-full bg-gray-200 rounded-full h-[7px] dark:bg-gray-700 my-2">
 					<!-- style={`width: ${$downloadAssets[fileName]}%`} -->
 					<div
-						class="bg-immich-primary h-[7px] rounded-full"
+						class="bg-immich-primary dark:bg-immich-dark-primary h-[7px] rounded-full"
 						style={`width: ${getStorageUsagePercentage()}%`}
 					/>
 				</div>
@@ -70,29 +70,29 @@
 		</div>
 	</div>
 	<div>
-		<hr class="ml-5 my-4" />
+		<hr class="ml-5 my-4 dark:border-immich-dark-gray" />
 	</div>
 	<div class="server-status grid grid-cols-[64px_auto]">
-		<div class="pl-5 pr-6 text-immich-primary">
+		<div class="pl-5 pr-6 text-immich-primary dark:text-immich-dark-primary">
 			<Dns size={'24'} />
 		</div>
 
-		<div class="text-xs">
-			<p class="text-sm font-medium text-immich-primary">Server</p>
+		<div class="text-xs ">
+			<p class="text-sm font-medium text-immich-primary dark:text-immich-dark-primary">Server</p>
 
-			<div class="flex justify-items-center justify-between mt-2">
+			<div class="flex justify-items-center justify-between mt-2 ">
 				<p>Status</p>
 
 				{#if isServerOk}
-					<p class="font-medium text-immich-primary">Online</p>
+					<p class="font-medium text-immich-primary dark:text-immich-dark-primary">Online</p>
 				{:else}
 					<p class="font-medium text-red-500">Offline</p>
 				{/if}
 			</div>
 
-			<div class="flex justify-items-center justify-between mt-2">
+			<div class="flex justify-items-center justify-between mt-2 ">
 				<p>Version</p>
-				<p class="font-medium text-immich-primary">{serverVersion}</p>
+				<p class="font-medium text-immich-primary dark:text-immich-dark-primary">{serverVersion}</p>
 			</div>
 		</div>
 	</div>

+ 77 - 0
web/src/lib/components/shared-components/theme-button.svelte

@@ -0,0 +1,77 @@
+<script lang="ts">
+	import { onMount } from 'svelte';
+
+	let toggleButton: HTMLElement;
+
+	onMount(() => {
+		var themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
+		var themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
+
+		// Change the icons inside the button based on previous settings
+		if (
+			localStorage.getItem('color-theme') === 'dark' ||
+			(!('color-theme' in localStorage) &&
+				window.matchMedia('(prefers-color-scheme: dark)').matches)
+		) {
+			themeToggleLightIcon?.classList.remove('hidden');
+		} else {
+			themeToggleDarkIcon?.classList.remove('hidden');
+		}
+	});
+
+	const toggleTheme = () => {
+		var themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
+		var themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
+		// toggle icons inside button
+		themeToggleDarkIcon?.classList.toggle('hidden');
+		themeToggleLightIcon?.classList.toggle('hidden');
+
+		// if set via local storage previously
+		if (localStorage.getItem('color-theme')) {
+			if (localStorage.getItem('color-theme') === 'light') {
+				document.documentElement.classList.add('dark');
+				localStorage.setItem('color-theme', 'dark');
+			} else {
+				document.documentElement.classList.remove('dark');
+				localStorage.setItem('color-theme', 'light');
+			}
+		} else {
+			if (document.documentElement.classList.contains('dark')) {
+				document.documentElement.classList.remove('dark');
+				localStorage.setItem('color-theme', 'light');
+			} else {
+				document.documentElement.classList.add('dark');
+				localStorage.setItem('color-theme', 'dark');
+			}
+		}
+	};
+</script>
+
+<button
+	bind:this={toggleButton}
+	on:click={toggleTheme}
+	id="theme-toggle"
+	type="button"
+	class="text-gray-500 dark:text-immich-dark-primary hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none  rounded-lg text-sm p-2.5"
+>
+	<svg
+		id="theme-toggle-dark-icon"
+		class="hidden w-6 h-6"
+		fill="currentColor"
+		viewBox="0 0 20 20"
+		xmlns="http://www.w3.org/2000/svg"
+		><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" /></svg
+	>
+	<svg
+		id="theme-toggle-light-icon"
+		class="hidden w-6 h-6"
+		fill="currentColor"
+		viewBox="0 0 20 20"
+		xmlns="http://www.w3.org/2000/svg"
+		><path
+			d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
+			fill-rule="evenodd"
+			clip-rule="evenodd"
+		/></svg
+	>
+</button>

+ 7 - 4
web/src/lib/components/sharing-page/shared-album-list-tile.svelte

@@ -26,7 +26,7 @@
 </script>
 
 <div
-	class="flex min-w-[550px] border-b border-gray-300 place-items-center py-4  gap-6 transition-all hover:border-immich-primary"
+	class="flex min-w-[550px] border-b border-gray-300 dark:border-immich-dark-gray place-items-center py-4  gap-6 transition-all hover:border-immich-primary dark:hover:border-immich-dark-primary"
 >
 	<div>
 		{#await loadImageData(album.albumThumbnailAssetId)}
@@ -46,13 +46,16 @@
 	</div>
 
 	<div>
-		<p class="font-medium text-gray-800">{album.albumName}</p>
+		<p class="font-medium text-gray-800 dark:text-immich-dark-primary">{album.albumName}</p>
 
 		{#await getAlbumOwnerInfo() then albumOwner}
 			{#if user.email == albumOwner.email}
-				<p class="text-xs text-gray-600">Owned</p>
+				<p class="text-xs text-gray-600 dark:text-immich-dark-fg">Owned</p>
 			{:else}
-				<p class="text-xs text-gray-600">Shared by {albumOwner.firstName} {albumOwner.lastName}</p>
+				<p class="text-xs text-gray-600 dark:text-immich-dark-fg">
+					Shared by {albumOwner.firstName}
+					{albumOwner.lastName}
+				</p>
 			{/if}
 		{/await}
 	</div>

+ 37 - 20
web/src/routes/+layout.svelte

@@ -15,8 +15,10 @@
 	let localVersion: string;
 	let remoteVersion: string;
 	let showNavigationLoadingBar = false;
+	let canShow = false;
 
 	onMount(async () => {
+		checkUserTheme();
 		const res = await checkAppVersion();
 
 		shouldShowAnnouncement = res.shouldShowAnnouncement;
@@ -24,6 +26,21 @@
 		remoteVersion = res.remoteVersion ?? 'unknown';
 	});
 
+	const checkUserTheme = () => {
+		// On page load or when changing themes, best to add inline in `head` to avoid FOUC
+		if (
+			localStorage.getItem('color-theme') === 'dark' ||
+			(!('color-theme' in localStorage) &&
+				window.matchMedia('(prefers-color-scheme: dark)').matches)
+		) {
+			document.documentElement.classList.add('dark');
+		} else {
+			document.documentElement.classList.remove('dark');
+		}
+
+		canShow = true;
+	};
+
 	beforeNavigate(() => {
 		showNavigationLoadingBar = true;
 	});
@@ -34,24 +51,24 @@
 </script>
 
 <main>
-	<!-- {#key $page.url} -->
-	<div in:fade={{ duration: 100 }}>
-		{#if showNavigationLoadingBar}
-			<NavigationLoadingBar />
-		{/if}
-
-		<slot />
-
-		<DownloadPanel />
-		<UploadPanel />
-		<NotificationList />
-		{#if shouldShowAnnouncement}
-			<AnnouncementBox
-				{localVersion}
-				{remoteVersion}
-				on:close={() => (shouldShowAnnouncement = false)}
-			/>
-		{/if}
-	</div>
-	<!-- {/key} -->
+	{#if canShow}
+		<div in:fade={{ duration: 100 }}>
+			{#if showNavigationLoadingBar}
+				<NavigationLoadingBar />
+			{/if}
+
+			<slot />
+
+			<DownloadPanel />
+			<UploadPanel />
+			<NotificationList />
+			{#if shouldShowAnnouncement}
+				<AnnouncementBox
+					{localVersion}
+					{remoteVersion}
+					on:close={() => (shouldShowAnnouncement = false)}
+				/>
+			{/if}
+		</div>
+	{/if}
 </main>

+ 6 - 2
web/src/routes/+page.svelte

@@ -19,9 +19,13 @@
 		<div class="flex place-items-center place-content-center ">
 			<img class="text-center" src="immich-logo.svg" height="200" width="200" alt="immich-logo" />
 		</div>
-		<h1 class="text-4xl text-immich-primary font-bold font-immich-title">Welcome to IMMICH Web</h1>
+		<h1
+			class="text-4xl text-immich-primary dark:text-immich-dark-primary font-bold font-immich-title"
+		>
+			Welcome to IMMICH Web
+		</h1>
 		<button
-			class="border px-4 py-2 rounded-md bg-immich-primary hover:bg-immich-primary/75 text-white font-bold w-[200px]"
+			class="border px-4 py-4 rounded-md bg-immich-primary dark:bg-immich-dark-primary dark:text-immich-dark-gray dark:border-immich-dark-gray hover:bg-immich-primary/75 text-white font-bold w-[200px]"
 			on:click={onGettingStartedClicked}
 			>Getting Started
 		</button>

+ 5 - 3
web/src/routes/admin/+page.svelte

@@ -33,7 +33,7 @@
 	};
 
 	onMount(() => {
-		selectedAction = AdminSideBarSelection.JOBS;
+		selectedAction = AdminSideBarSelection.USER_MANAGEMENT;
 		getServerStats();
 	});
 
@@ -147,8 +147,10 @@
 	</section>
 	<section class="overflow-y-auto relative">
 		<div id="setting-title" class="pt-10 fixed w-full z-50">
-			<h1 class="text-lg ml-8 mb-4 text-immich-primary font-medium">{selectedAction}</h1>
-			<hr />
+			<h1 class="text-lg ml-8 mb-4 text-immich-primary dark:text-immich-dark-primary font-medium">
+				{selectedAction}
+			</h1>
+			<hr class="dark:border-immich-dark-gray" />
 		</div>
 
 		<section id="setting-content" class="relative pt-[85px] flex place-content-center">

+ 15 - 7
web/src/routes/albums/+page.svelte

@@ -42,20 +42,28 @@
 	<NavigationBar user={data.user} shouldShowUploadButton={false} />
 </section>
 
-<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg ">
+<section
+	class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg  dark:bg-immich-dark-bg"
+>
 	<SideBar />
 
 	<!-- Main Section -->
 
 	<section class="overflow-y-auto relative immich-scrollbar">
-		<section id="album-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg">
-			<div class="px-4 flex justify-between place-items-center">
+		<section
+			id="album-content"
+			class="relative pt-8 pl-4 mb-12 bg-immich-bg dark:bg-immich-dark-bg"
+		>
+			<div class="px-4 flex justify-between place-items-center dark:text-immich-dark-fg">
 				<div>
 					<p class="font-medium">Albums</p>
 				</div>
 
 				<div>
-					<button on:click={handleCreateAlbum} class="immich-text-button text-sm">
+					<button
+						on:click={handleCreateAlbum}
+						class="immich-text-button text-sm dark:hover:bg-immich-dark-primary/25 dark:text-immich-dark-fg"
+					>
 						<span>
 							<PlusBoxOutline size="18" />
 						</span>
@@ -65,7 +73,7 @@
 			</div>
 
 			<div class="my-4">
-				<hr />
+				<hr class="dark:border-immich-dark-gray" />
 			</div>
 
 			<!-- Album Card -->
@@ -85,11 +93,11 @@
 			<!-- Empty Message -->
 			{#if $albums.length === 0}
 				<div
-					class="border p-5 w-[50%] m-auto mt-10 bg-gray-50 rounded-3xl flex flex-col place-content-center place-items-center"
+					class="border dark:border-immich-dark-gray p-5 w-[50%] m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center"
 				>
 					<img src="/empty-1.svg" alt="Empty shared album" width="500" />
 
-					<p class="text-center text-immich-text-gray-500">
+					<p class="text-center text-immich-text-gray-500 dark:text-immich-dark-fg">
 						Create an album to organize your photos and videos
 					</p>
 				</div>

+ 6 - 2
web/src/routes/photos/+page.svelte

@@ -65,7 +65,9 @@
 			tailwindClasses={'bg-white shadow-md'}
 		>
 			<svelte:fragment slot="leading">
-				<p class="font-medium text-immich-primary">Selected {$selectedAssets.size}</p>
+				<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
+					Selected {$selectedAssets.size}
+				</p>
 			</svelte:fragment>
 			<svelte:fragment slot="trailing">
 				<CircleIconButton
@@ -83,7 +85,9 @@
 	{/if}
 </section>
 
-<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg">
+<section
+	class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg dark:bg-immich-dark-bg"
+>
 	<SideBar />
 	<AssetGrid />
 </section>

+ 11 - 6
web/src/routes/sharing/+page.svelte

@@ -39,13 +39,18 @@
 	<NavigationBar user={data.user} shouldShowUploadButton={false} />
 </section>
 
-<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg">
+<section
+	class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg dark:bg-immich-dark-bg"
+>
 	<SideBar />
 
 	<section class="overflow-y-auto relative">
-		<section id="album-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg">
+		<section
+			id="album-content"
+			class="relative pt-8 pl-4 mb-12 bg-immich-bg dark:bg-immich-dark-bg"
+		>
 			<!-- Main Section -->
-			<div class="px-4 flex justify-between place-items-center">
+			<div class="px-4 flex justify-between place-items-center dark:text-immich-dark-fg">
 				<div>
 					<p class="font-medium">Sharing</p>
 				</div>
@@ -53,7 +58,7 @@
 				<div>
 					<button
 						on:click={createSharedAlbum}
-						class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700"
+						class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700 dark:hover:bg-immich-dark-primary/25 dark:text-immich-dark-fg"
 					>
 						<span>
 							<PlusBoxOutline size="18" />
@@ -64,7 +69,7 @@
 			</div>
 
 			<div class="my-4">
-				<hr />
+				<hr class="dark:border-immich-dark-gray" />
 			</div>
 
 			<!-- Share Album List -->
@@ -79,7 +84,7 @@
 			<!-- Empty List -->
 			{#if data.sharedAlbums.length === 0}
 				<div
-					class="border p-5 w-[50%] m-auto mt-10 bg-gray-50 rounded-3xl flex flex-col place-content-center place-items-center"
+					class="border dark:border-immich-dark-gray p-5 w-[50%] m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center dark:text-immich-dark-fg"
 				>
 					<img src="/empty-2.svg" alt="Empty shared album" width="500" />
 					<p class="text-center text-immich-text-gray-500">

+ 9 - 3
web/tailwind.config.cjs

@@ -1,14 +1,20 @@
 module.exports = {
 	content: ['./src/**/*.{html,js,svelte,ts}'],
+	darkMode: 'class',
 	theme: {
 		extend: {
 			colors: {
+				// Light Theme
 				'immich-primary': '#4250af',
 				'immich-bg': 'white',
 				'immich-fg': 'black',
-				'immich-gray': '#F6F6F4'
-				// 'immich-bg': '#121212',
-				// 'immich-fg': '#D0D0D0',
+				'immich-gray': '#F6F6F4',
+
+				// Dark Theme
+				'immich-dark-primary': '#adcbfa',
+				'immich-dark-bg': 'black',
+				'immich-dark-fg': '#e5e7eb',
+				'immich-dark-gray': '#212121'
 			},
 			fontFamily: {
 				'immich-title': ['Snowburst One', 'cursive']