From af8f55976f8a43ab8d748bca26e39e14935f26ae Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Fri, 9 Oct 2026 01:01:57 +0000 Subject: [PATCH] feat(client): add the Nostalgia appearance style Add a Style setting beside mode, accent, radius and background. Nostalgia restyles the app through tokens only (neutral surface palettes, system type, flat shell, top glow) and pins background, opacity and brightness. Add a top glow reach slider for both styles. Tidy the theme layer first: charts read live tokens, bare `rounded` follows the radius scale, one shared sign-in toolbar. Also carries the settings page header redesign. --- client/index.html | 1 + client/public/theme-init.js | 35 +- client/public/topbar.png | Bin 14499 -> 0 bytes client/src/App.vue | 10 +- client/src/assets/main.css | 71 +++- client/src/assets/theme/accents.css | 259 +++++++-------- client/src/assets/theme/bridge.css | 7 +- client/src/assets/theme/styles/nostalgia.css | 119 +++++++ client/src/assets/theme/surfaces.css | 128 ++++++++ client/src/assets/theme/tokens.css | 120 ++++--- client/src/components/AppHeader.vue | 64 +++- client/src/components/AppLayout.vue | 8 +- client/src/components/BackgroundPicker.vue | 9 +- client/src/components/GlowReachPicker.vue | 37 +++ client/src/components/IconPicker.vue | 2 +- client/src/components/RadiusPicker.vue | 42 ++- client/src/components/SelectionActionBar.vue | 2 +- client/src/components/StylePicker.vue | 35 ++ ...acePicker.vue => SurfaceOpacityPicker.vue} | 0 .../src/components/SurfacePalettePicker.vue | 54 ++++ client/src/components/ThemePicker.vue | 6 +- .../components/__tests__/AppHeader.spec.ts | 7 +- .../__tests__/GlowReachPicker.spec.ts | 40 +++ .../components/__tests__/StylePicker.spec.ts | 39 +++ .../__tests__/SurfacePalettePicker.spec.ts | 52 +++ .../src/components/sidebar/SidebarBadge.vue | 4 +- .../src/components/sidebar/SidebarNavItem.vue | 2 +- .../sidebar/SidebarSectionHeader.vue | 9 +- .../sidebar/SidebarSectionPopover.vue | 2 +- client/src/components/sidebar/SidebarZone.vue | 5 +- client/src/components/ui/TokenSelect.vue | 2 +- client/src/components/ui/sidebar/Sidebar.vue | 2 +- .../view-header/ViewHeaderDisplayControls.vue | 4 +- .../__tests__/useThemeSync.spec.ts | 83 +++++ client/src/composables/useAppStyleScope.ts | 9 + client/src/composables/useChartTokens.ts | 9 + client/src/composables/useThemeSync.ts | 47 ++- client/src/features/admin/UserFormDrawer.vue | 4 +- client/src/features/admin/UsersPage.vue | 2 +- .../admin/components/NewAccountDefaults.vue | 2 +- .../components/UserPermissionsSection.vue | 8 +- .../components/UserRestrictionsSection.vue | 4 +- .../components/AnnotationBookCombobox.vue | 2 +- .../components/AnnotationBookThumb.vue | 2 +- .../components/AnnotationListItem.vue | 24 +- .../components/AnnotationSyncDetailPanel.vue | 6 +- .../__tests__/AnnotationListItem.spec.ts | 2 +- .../components/hub/HubSideRail.vue | 6 +- .../components/shared/AnnotationEntry.vue | 2 +- .../shared/AnnotationEntryDetail.vue | 2 +- .../components/shared/AnnotationStream.vue | 6 +- .../features/annotations/lib/pill-styles.ts | 2 +- .../src/features/auth/ForgotPasswordPage.vue | 211 +----------- client/src/features/auth/LoginPage.vue | 212 +----------- client/src/features/auth/RegisterPage.vue | 217 +------------ .../src/features/auth/ResetPasswordPage.vue | 211 +----------- client/src/features/auth/SetupPage.vue | 213 +----------- .../features/auth/__tests__/LoginPage.spec.ts | 17 +- .../auth/__tests__/RegisterPage.spec.ts | 17 +- .../auth/components/AuthAppearanceToolbar.vue | 124 +++++++ .../__tests__/AuthAppearanceToolbar.spec.ts | 88 +++++ .../author/components/AuthorPortrait.vue | 2 +- .../src/features/author/views/AuthorsView.vue | 4 +- .../components/BookDockBulkEditDialog.vue | 4 +- .../components/BookDockCoverPeek.vue | 4 +- .../components/BookDockFileListView.vue | 8 +- .../components/AuthorEnrichmentExpanded.vue | 10 +- .../AuthorEnrichmentReportModal.vue | 6 +- .../components/BookMetadataFetchExpanded.vue | 10 +- .../BookMetadataFetchReportModal.vue | 6 +- .../components/BookMetadataFetchWidget.vue | 4 +- .../book-requests/BookRequestsPage.vue | 2 +- .../components/BookMissingFormatRequest.vue | 4 +- .../components/ReleasePickerPanel.vue | 4 +- .../book-requests/components/RequestCover.vue | 4 +- .../components/RequestDetailPanel.vue | 2 +- .../components/RequestListSkeleton.vue | 8 +- .../components/RequestListTable.vue | 16 +- .../components/RequestSeedPanel.vue | 2 +- .../book/components/BookCoverCard.vue | 12 +- .../book/components/BookDownloadButton.vue | 22 +- .../book/components/BookFilterBuilder.vue | 4 +- .../features/book/components/BookListRow.vue | 10 +- .../book/components/BookQuickView.vue | 26 +- .../book/components/BookSortBuilder.vue | 4 +- .../components/BulkEditMetadataDialog.vue | 6 +- .../book/components/CollapsedSeriesCard.vue | 2 +- .../book/components/CoverMediumSwitch.vue | 2 +- .../book/components/FilterChipTypeahead.vue | 2 +- .../book/components/FilterSummary.vue | 2 +- .../components/KeyboardShortcutOverlay.vue | 2 +- .../book/components/TableColumnPanel.vue | 26 +- .../book/components/VirtualBookTable.vue | 10 +- .../__tests__/BookCoverCard.spec.ts | 4 +- .../book/components/detail/BookCarousel.vue | 2 +- .../detail/WriteAndRenameResultPanel.vue | 2 +- .../detail/details/BookEditionsCard.vue | 2 +- .../details/BookReadingActivityCard.vue | 2 +- .../detail/files/FileDetailCard.vue | 10 +- .../components/detail/files/FileListCard.vue | 6 +- .../components/detail/files/FileRoleBadge.vue | 2 +- .../components/detail/files/FilesHeroBar.vue | 2 +- .../components/detail/files/WriteBackCard.vue | 8 +- .../detail/highlights/HighlightsSideRail.vue | 2 +- .../detail/reading-log/ReadingLogAttempts.vue | 12 +- .../detail/reading-log/ReadingLogLedger.vue | 10 +- .../reading-log/ReadingLogProgressChart.vue | 14 +- .../detail/reading-log/ReadingLogRecords.vue | 2 +- .../reading-log/ReadingLogTraceChart.vue | 16 +- .../detail/reading-log/ReadingLogVitals.vue | 4 +- .../detail/tabs/AddBookFileModal.vue | 8 +- .../components/detail/tabs/DetailsTab.vue | 64 ++-- .../detail/tabs/EditMetadataTab.vue | 10 +- .../detail/tabs/HighlightsExportMenu.vue | 9 +- .../detail/tabs/MetadataSearchDrawer.vue | 2 +- .../detail/tabs/MetadataSourceCard.vue | 2 +- .../detail/tabs/ReadingLogExportMenu.vue | 4 +- .../components/detail/tabs/ReadingLogTab.vue | 2 +- .../metadata-match/MetadataMatchRail.vue | 6 +- .../metadata-match/MetadataMatchShortcuts.vue | 4 +- .../components/table/BookTableActionsCell.vue | 2 +- .../components/table/BookTableBooleanCell.vue | 2 +- .../table/BookTableCellDispatcher.vue | 2 +- .../components/table/BookTableChipsCell.vue | 2 +- .../components/table/BookTableContextMenu.vue | 19 +- .../components/table/BookTableFormatCell.vue | 2 +- .../table/BookTableHeaderContextMenu.vue | 20 +- .../table/BookTableMetadataScoreCell.vue | 2 +- .../components/table/BookTableNumberCell.vue | 4 +- .../table/BookTableReadButtonCell.vue | 2 +- .../table/BookTableReadStatusCell.vue | 2 +- .../components/table/BookTableTextCell.vue | 6 +- client/src/features/book/lib/format-colors.ts | 9 +- .../components/IconUploadDialog.vue | 2 +- .../components/DashboardPodcastScroller.vue | 4 +- .../components/DashboardSettingsSheet.vue | 14 +- .../widgets/CurrentlyReadingWidget.vue | 8 +- .../widgets/DiversityScoreWidget.vue | 2 +- .../widgets/HighlightOfTheDayWidget.vue | 6 +- .../widgets/LibraryOverviewWidget.vue | 4 +- .../components/widgets/LongWaitWidget.vue | 6 +- .../widgets/MonthlyChallengeWidget.vue | 2 +- .../widgets/NeglectedGemsWidget.vue | 6 +- .../components/widgets/ReadingDnaWidget.vue | 2 +- .../components/widgets/ReadingGoalWidget.vue | 7 +- .../widgets/ReadingRhythmWidget.vue | 4 +- .../widgets/ReadingStreakWidget.vue | 2 +- .../widgets/YearProjectionWidget.vue | 4 +- .../features/email/components/HistoryTab.vue | 2 +- .../email/components/ProvidersTab.vue | 14 +- .../email/components/RecipientsTab.vue | 6 +- .../email/components/SendBookDialog.vue | 6 +- .../email/components/TemplatesTab.vue | 8 +- .../components/HardcoverImportReviewModal.vue | 4 +- .../components/HardcoverImportStatus.vue | 2 +- .../library/components/BookUploadModal.vue | 6 +- .../library/components/FolderPickerModal.vue | 8 +- .../components/LibraryCreatorScanner.vue | 2 +- .../migration/components/MigrationModal.vue | 40 +-- .../components/MigrationSourceFields.vue | 4 +- .../components/NotificationLevelSegmented.vue | 2 +- .../components/PodcastAddFeedSheet.vue | 2 +- .../podcast/components/PodcastEpisodeRow.vue | 2 +- .../components/PodcastShortcutsDialog.vue | 2 +- .../podcast/components/PodcastShowCard.vue | 2 +- .../podcast/components/PodcastStatusChip.vue | 2 +- .../reader/audiobook/AudiobookReaderView.vue | 2 +- .../epub/components/DictionaryPopover.vue | 2 +- .../reader/epub/components/ReaderFooter.vue | 2 +- .../epub/components/ReaderSearchPanel.vue | 4 +- .../reader/epub/components/ReaderSidebar.vue | 6 +- .../reader/epub/components/SelectionPopup.vue | 4 +- .../epub/components/TranslationPanel.vue | 4 +- .../components/MediaOverlayMiniPlayer.vue | 2 +- .../reader/pdf-v4/components/PdfNoteCard.vue | 2 +- .../pdf-v4/components/PdfOutlineTree.vue | 2 +- .../pdf-v4/components/PdfSearchPanel.vue | 2 +- .../pdf-v4/components/PdfSelectionPopup.vue | 4 +- .../pdf-v4/components/PdfThumbnailGrid.vue | 4 +- .../src/features/series/views/SeriesView.vue | 12 +- .../src/features/settings/AccountSettings.vue | 2 +- .../settings/AppearanceIconsSettings.vue | 6 +- .../settings/AppearancePreferenceStorage.vue | 6 +- .../settings/AppearanceThemeSettings.vue | 64 +++- .../settings/AuthorEnrichmentSettings.vue | 6 +- .../features/settings/FontLibraryPanel.vue | 4 +- .../settings/IndexerManagersPanel.vue | 2 +- .../features/settings/MagicLinksSettings.vue | 4 +- .../features/settings/MigrationSettings.vue | 38 +-- client/src/features/settings/OidcSettings.vue | 8 +- .../src/features/settings/ReaderSettings.vue | 6 +- .../settings/RequestIndexersPanel.vue | 2 +- .../components/AuthorMetadataPreferences.vue | 2 +- .../components/NamingRuleEditor.vue | 4 +- .../file-naming/components/NamingRuleRail.vue | 2 +- .../CustomMetadataSettings.vue | 2 +- .../components/FieldRuleDetail.vue | 4 +- .../components/FieldRuleRow.vue | 2 +- .../components/FieldRulesScopeTabs.vue | 2 +- .../components/FieldRulesToolbar.vue | 6 +- .../components/MergeStrategySegmented.vue | 2 +- .../components/ProviderAddMenu.vue | 4 +- .../components/ProviderBulkMenu.vue | 2 +- .../components/ProviderChipList.vue | 2 +- .../components/ScoreRescoreProgress.vue | 2 +- .../components/ScoreWeightRow.vue | 4 +- .../components/ScoreWeightsToolbar.vue | 2 +- .../components/CreateSmartScopeDialog.vue | 4 +- .../statistics/components/StatisticsPage.vue | 10 +- .../library/GenreDistributionChart.vue | 9 +- .../library/LibraryIntegrityGaugeChart.vue | 5 +- ...ibraryMetadataCompletenessHeatmapChart.vue | 10 +- .../library/MetadataFreshnessGaugeChart.vue | 5 +- .../components/library/TopAuthorsChart.vue | 9 +- .../components/library/TopSeriesChart.vue | 9 +- .../user/FavoriteReadingDaysChart.vue | 6 +- .../user/GenreReadingTimeTreemapChart.vue | 9 +- .../components/user/PeakReadingHoursChart.vue | 6 +- .../components/user/ReadingClockChart.vue | 6 +- .../components/user/ReadingHeatmapChart.vue | 8 +- .../user/ReadingPaceScatterChart.vue | 6 +- .../user/ReadingSessionTimelineChart.vue | 6 +- .../user/SourceDistributionChart.vue | 6 +- .../features/statistics/lib/breakdown.test.ts | 4 +- .../lib/source-bucket-colors.test.ts | 66 +--- .../statistics/lib/source-bucket-colors.ts | 36 +-- .../components/StorygraphConnectionCard.vue | 4 +- .../components/StorygraphLinkedBooks.vue | 2 +- .../components/DismissedDuplicatesSection.vue | 2 +- .../components/DuplicateCopyCard.vue | 4 +- .../components/DuplicateLedgerRow.vue | 2 +- .../views/BookDuplicatesView.vue | 2 +- .../components/BulkRenameDetail.vue | 6 +- .../bulk-rename/components/PathDiffLine.vue | 2 +- .../components/BrowseMergeModal.vue | 2 +- .../components/BulkDeleteModal.vue | 2 +- .../entity-manager/components/DeleteModal.vue | 2 +- .../components/DismissedPairsSection.vue | 2 +- .../components/DuplicateCompare.vue | 6 +- .../components/EntityDataGrid.vue | 6 +- .../entity-manager/components/RenameModal.vue | 2 +- .../entity-manager/components/SplitModal.vue | 2 +- .../components/BrokenCoverRow.vue | 4 +- .../components/MissingBookRow.vue | 8 +- .../components/MissingResourceTable.vue | 2 +- .../components/OrphanedCoverRow.vue | 2 +- .../features/tts/TtsOpenAiVoiceCuration.vue | 18 +- .../tts/components/TtsAdminProviderCard.vue | 4 +- .../tts/components/TtsAdminProviderForm.vue | 2 +- .../features/tts/components/TtsMiniPlayer.vue | 2 +- .../tts/components/TtsVoicePicker.vue | 4 +- client/src/lib/css-color.test.ts | 39 +++ client/src/lib/css-color.ts | 91 ++++++ client/src/lib/echarts.test.ts | 70 ++-- client/src/lib/echarts.ts | 120 +++---- client/src/lib/heatmap-palette.ts | 2 +- client/src/lib/theme-accent-meta.test.ts | 4 +- client/src/lib/theme-styles.spec.ts | 44 +++ client/src/lib/theme-styles.ts | 91 ++++++ client/src/locales/en.json | 79 ++++- .../src/stores/__tests__/theme-init.spec.ts | 93 +++++- client/src/stores/__tests__/theme.spec.ts | 186 +++++++++++ client/src/stores/theme.ts | 303 +++++++++++------- client/src/views/BookDetailView.vue | 14 +- client/src/views/HomeView.vue | 2 +- client/src/views/SettingsView.vue | 39 ++- .../src/views/__tests__/SettingsView.spec.ts | 46 +-- packages/types/src/theme.ts | 43 +++ .../user-preferences.service.test.ts | 49 ++- .../user-preferences.service.ts | 7 + 270 files changed, 2972 insertions(+), 2317 deletions(-) delete mode 100644 client/public/topbar.png create mode 100644 client/src/assets/theme/styles/nostalgia.css create mode 100644 client/src/assets/theme/surfaces.css create mode 100644 client/src/components/GlowReachPicker.vue create mode 100644 client/src/components/StylePicker.vue rename client/src/components/{SurfacePicker.vue => SurfaceOpacityPicker.vue} (100%) create mode 100644 client/src/components/SurfacePalettePicker.vue create mode 100644 client/src/components/__tests__/GlowReachPicker.spec.ts create mode 100644 client/src/components/__tests__/StylePicker.spec.ts create mode 100644 client/src/components/__tests__/SurfacePalettePicker.spec.ts create mode 100644 client/src/composables/useAppStyleScope.ts create mode 100644 client/src/composables/useChartTokens.ts create mode 100644 client/src/features/auth/components/AuthAppearanceToolbar.vue create mode 100644 client/src/features/auth/components/__tests__/AuthAppearanceToolbar.spec.ts create mode 100644 client/src/lib/css-color.test.ts create mode 100644 client/src/lib/css-color.ts create mode 100644 client/src/lib/theme-styles.spec.ts create mode 100644 client/src/lib/theme-styles.ts diff --git a/client/index.html b/client/index.html index 3467bc698..aeb6517ce 100644 --- a/client/index.html +++ b/client/index.html @@ -10,6 +10,7 @@ +
dYlt+{nLi___h>;8yE4rYzt=*;gamh~6tB2=>>gNgn|fo hvs%JCgPFT2FDYN<`X>6`$z#7(IIKN9dZw3 zAGON&x!7qF3hW{-scZ$}Q!L(ljr=~h>SsYs?MZH}BeU%^`FkVDh%2w0Vz~{F+cz!K zm_R>z{+#zqwRh&1`BH%P0M=zojrE6}+tW@J?q2tpsXfh+QfZXc{%GFn@Msfw;FI=F z^8J ASaaw(8&AzG!JEXF77RCfK9a0O+vUr80f+n3PQ>@mILw`wROo`t_|1biH+a zv;hG5YwA)dZ>_fgCtnlWPb&w?0$)3nTTf0ijFldpWIe1p9FDz-G}^|J%-kaCp|yz= zGSRp^J~OmOe8wLJ+;C_nI>dfH4E(hf@ldK#eHzv-Iq(s%B(!QZJ2h*z%k=p{&f@2? zbFX)2k+g%I3z(n gW{ytps9*6|5;iExxmLy@^roOQz}!6QdHN3~&ACWnmRzhXV1NH*x)& zD@hOE!l_NJmJcM(6f?>q)1`ROfB_ift$E02D*7`Ms@09!Zc`u8i~R8|W~)g8JD03I zriqX2NmLlo*x3X@)B7{TS+PrI87Q#9U&5?Wq+@W;RFR`~HW>XaiH)+uQ_(qiMr)h{ zafVc~CsbLiSwLI6-VK)FiXf!9jLkOi)ff*dNT_c?cg7 m>W_O}+%1#43) zHgt@{LO~n?#(NREM6L6tEZmJN5TLw7u7dK-zhGjVe-=!P8Hly_w+QYL*7m|a%7%ak zGQc;nu0)M|+E6*9XjI$;f>8Y~!@S{^J&5M6CPyoewvYRwiDaxGOcV7P6j lO>&}jA)TuC~Bq05NTl^Jr zpbke_c|uRjib{#Qm!}ytmRQ+dQts04fwVifq0YfFjHcHAd~N)NOFxH?ovTNW7WxHL zU ut&RIFoNx+Y5Uz?F^-`QScgpp4-u(XeLT(&~2 @ zX(;vy(pXmiR4Yc83ybC$>_ft&v8Rqf!bNAv`7(U|)#5Y4QX=4a^jhJC5lzW`&lES6 zlgq~F%=KVPh?~F7I3$lV5A8Co`+m8Q=lL=ZuQsX~vuKJxG6 sO4#g{sDRA=Y3(sU$f-vx{s<2XFD!ELw-BCbV-Hf zSa-Qeq3~qQWrIj|Qr|+4bzqaRZ;HQEocS^9A?sYot-McU0;K2hnvP{jIkK<^m&^}z z-lkG&=&=f7wZQH17d)txje^lks`zlmT>huKXC?lB2CPMdf?9GdGWzGJSlm*1mnm`T zYCfiuqKYl^JC_R?`ttqtrnQBUtlyov)_Ha|$3wi3*R+iOwUTj%sjp6Y< 3$vCEM z;@>-vik+RdXhIpNib8OrPPquIZ7T^3{ewo1j%@c0jXFV5bOF=kX64+=k=h3MHAS6n zo?a^?#k8b}=<25x3Yi!%4{}(w?h3!`?bA>#eAAj1rKN0Ji9{>Hk93oQz3{ZKJ98PQ zi4CPbSztyiL+R^cn5D4rLsReu?^YK(vDN=;#pW7Cp2G&y5p}`r1n%GL4z>@TRkG z^v4r=M%MF7>|c9d_PkuEbStnWq`c@>$-J{v|I{A!QQzpz{Qhp&QPeJumzzR;`ytQ5 zC6C}PC3;SGMCuSa`UuqKG$Utf!E^ZJDcK@91ZB(Nz*q8z?|SNxRwId#Vs}i$ if;N0}pZ+aYwhFN e!#PjiO-}D%t^Oa zANu9p?U*imr!hH_d;}*k=BT1Q%b<{3yOrBicHThyWl9;H^Zsrw3DSWFw^Fu^FkU!% zV{_1#T3#P&;}=@)IW^*WZ*m*p-76F2<@NdSMu*YJE&krK?k88(Ya-?}>#b0-H=Jne z(~o2JU-1iEKy{C@Du|N7?#JsKb~lhR8tKfWR$`Cojd$iq+4xOLPhX7BR&Z~vIaIBE z_wUKS$F>FT0)Rg?I(klQ)pAck4yyc-(k`JC%N^~1D kz z=&Aki*{~R?a?78n(A%CBBCjJ9j=Rja #ifYbCEe}SrILBhPz zsT-epe5bgg3j%OS&dCW}a+m1<$!pmddaWOOZ%8T+*~M-sZjm62xIp)$IEH?EfFln| z6%*-fHcrqMim{A53^kq7;edE8QAfi6^`Q8F95*(0v~ED5*));ZHh|j}y}5Lt2m=Lc ziPgj35(g!eB>Z}E4*m@-H_QVK7378$_29bhTL~zBTHs*i3NX33)X}TFk~|K fWYskH^=l%$*r1}OD2?3ySRlk6^7xH~tL)N1MCUmb35;W7YkvF;V44x-^aHQn#@ zkOec)Gs0$#DRDT55rWy{_;v=QPO<(ty5zC4F}Rw6ZZ)-K)j$SR+1INH=leAVV+Dy! zC;T@A%U$%mWA^+?@pC^$e(*;M>|+T37AuCRG`wcU-F`zZZ_ED$3CXs@bv%5kU{5wF zah(5e`Iu&rE^;YE@L`{S>1sCJXZ893z1CYT2;a-o4kq(6ZU#YPE`QS5i)9!xB7VKu zY=WqD`%IM{2xa =aylya4Ym2p+;B&_f`YtqEjDn{MDS1KtcMRF044T)o&&3?A}d#aypLDLX1Okmo=;W2h=*c>z=c62tY1AD$87O{t%HF@tKv zzsBOiOiKHY3SxRS5htI**q}(WfFOLRLuNy3awCU}n@NAk*87GZWR5F_EJFLq;W({^ z?8c2lKXX^ _YuyRbk1FtDOF!Oh?V2|LTRmruI zbirq6KsGasF;DLBcZkdz!llrv{O4k+r%Bou&4%f %g;Z zk$e3hG=f{6tm%}v_zbyl{r1Xuk+2BzfDGgHtoO5~@+kgq3%{BgWh1Ve|6X<6OoSkI zgR4{bB@ov&>`e68GT6)Oeb*p9Qb6mf5{6x-k=O}kpXTikQ(L{nlb`(O&VC7F$8OKF z(vD7B_0q@JW|0b@q46_df&;{$Vc6%>Y7#peAv9=PCy%hjW(VOu(JXOQ7r{|NMVq2h zM?ig{xp 2r?C5N)GKHWSWFO9gY6fBd?eVUL zrV*Xio>r`SSCa>s`Cq4?Etr0v#I>RkP0o>hgRa9MqW~7Fcgzr9Uuw2L`}^=ax(8XO z9~zAj{8MtxyboQ}kL*!8ztQB#*zdT5KxuZYY7-XC+%-_8&?9%SZDg)Drgs2qKe8{b z6iTm9TZWvXi?5| r+^;% zqqOXF-1^=2!R+X5?qD~}xyruDwjH$X%j|oL;Vq{;{c_?M-Oq=w$Fyu`OMBcE71d^n z9t^PZrw{W_$lJy%PRBjj!QQzh-SO#&!4FkofkM=lM~MeHUs`I;kc(LnCq{yD+ONkQ z4v 7->BG?~BHZ zj}wn~$Z5ZOzZCi*aO~K0Pr`T9@{#XZpm W8#o<#aiFD?G;{+CZ=-4VdqeI&D+36iWEX zB}dBWdHfmy*_?4Egn(93~)nfB$$a$ zovZq*u_Ex1P8h^Awq?iiX;8YxH=B7VAF0AP2z&=I|B9EA^fr$mEp=`v4VNJ!m%6>T z>2rNg2l4y@*FFU@zlr$HMY%@(7sJbif8>gKN_o%g5XYZ{Sk=1ZK9PTN)A{z^fTXyE z3qO>4J)gUgWTqb*6MqIhv$!bTN;B~t)0LG~e)r%Zo?d+MW%j$+u->mY!Gp?euIH{O zE^6^_>M4z8H>J~?wzx1aad*p-?7V{)&rBr+YNYwkt;ye(pW1n>5W-~JZnZ5pT%8#p zOjKW9wadfV6Z^zyMzGpsZImu9Y`cktaFc6P$0clXrz#|=wEXeojs`?>`686^jO?`% z84fO&u&~)RQs&gg578_5Wxk83#+3RV!6-B}azU`Jh+xdH$a8s31ELq@ug%i?Z67Xe zu97N9Jhi$n{-UyisekX}iZ{cnEBgXDZ}lvua_)GMZhJFL)qmv-p2mC5_z)bb6Hw(# z))}Px_}C*Gkvq-Lnf7Y(e6f_KGop(>#BiU+VhFw+-Ve>hCxNg%7e3~d->)cZ6_xoM zucL)OHC3m*uG{2 |t`Zt)ktclSC&uc #b$;bLQ0`N2x$)TZ! Ef20A)`<4JjIw ktzo zcK%4b7f{g=yI8)iN6Q%b2Op+8k+86jY>Dw1 S4@^*yfEq%CFUL^W-qCj zTm&&|Kv@-1E$=%C^5zVEM#1+;Ws26`@E>CA pe3{g}36>u0ZC4Rd*)+;V z*=F|QJ1{FZi$*qg^%&Da_lS|P4=JrS&&aewvF!pgcaJ}0#v *ZRR@t(tJS=GVKFRq?&o)om9fYE07${C`A|i@iuF6)}Sz9P6`@qf;~K( za0=A-VJ7L!SgN~dEjqqOt{^G>^?r+P+^;T55O+NRQysAZyafH6XXCX0KAa&X91ohh z+QQyK4Yh>h0)A%?W6b}OFDIwVL=jDIk3Jyz8i=DIUxbE*LS}uk5|sMoZqVD<_fw$i zQu+8?h%y|UG+$vl@kaS~=*W3nF(@|Ud>eJ;fNt-E)NA$ND4_~eF*5qf5&VTT3F>d6 zkjrLpu${|clFvp1 U0wN!eq>DNe#TnSV6Tq{8EaWqWy@ +bN7_;m FC=oT=eeD>++J^v4e%W#%p1UJnVuJa!*5_4?v;w}iRV6dd=11Wx(b`QQzE^eM zz1zyZ{`?~zGv!|M_di-h1^$p69)fvr!AEcl^V& f2VjLT}u8zhGd;A5-%xZ_cpXK#z2JM?&`6%ptyBnm6?#JvZl`+jIQqS%Zgf zy*YncaBTwVE1J8%<3atJ3gL(2FnOB6amwHD5{jJde%D-rC8l-E7nG9f*0j$8{nQI8 zDcvz> zuJ@ZZnVOymH{&!~?k@`j52%o#d>5*`$Z<7ri|+s3qGV#Mt$V zkth8NE*axY>PrQf^8H=Ph@ SEZ&3ka=TPqS2rOQtnB*69|&l~I^jzYEBK z;}f1Z1xZ{QJ4g_t97t*O2Hj#fN^49^{%hipsa`lw4Z>!;{1{9?+Ed?vv8|s21H<>7 z$fH;EJ^X<4w{)4>sV9E5Q02dPx6>Jh>kh@069drwFl9kiU?z>e)TKC|K7g4FCV2JJ zLL9!S5r?CiO|LXu;l=bqo4|F-(B@u(#PN|7^p8TXo0s&%FJH+E?k4?eT~xddmE^1x zm_yMl=J;Jqt4*ta9(k7q*K2tkRrcLBY@yr@`)g4m!Tq;25@UkC)#M}MdIC$@xM!H* z%-}|E-olH;6Osg$$J6scWIKEV;qd~;eL{2~WDF$|#O5z%EH;f1s*%{Hv9&y%uc9W1 zgizK)bArgJ&DKrr4y))P%Sc+N$e?6@IMYunNYlLdk8mbzja5sy{I(stkkuU3gsq%+ zlkl%v3CrqQUvd^LuR9WNlc c4aZ{g2K)T9ZdPW&FYBT{noPJ!G`zZO6bM2p_E;0U4p{s2(S-%0 z)HP9V!MF*&olY7yVbwtp2*Ob5B;@CK2qUk~qn|t_;VUe}o0&rv#z&wG1Z_hY0HMEz zJRLPd`Bafr`7T^I?f`r-JJaKf!^~Q_pD=eR6B)pLH{}!7nx~2S2mh-M?}u6iskcq6 zmtC7}3WmV$m_2#!hl )2|fSzU&L#B>vJmNVLrz4?}N@w zcI^I^T~5A3Ju9y*zR$0s5BXb{coe7ih%-i!x@1Rvq_1&0B~;wY{ _B zbI!6H%UBA(C-xMn5GJx2E8L;#arguN*UXVHyhS2DEW#kB%!^e6kkqG_3%p*4?DDEu zU}JnSZ)WHV)92+8TZDG39V?AB^>%ZqPMq46JWf*W_SyCY?zNddIxLl1# y*U`JoADRiY(LYMVEI;xidqeZo@ld2fT)#g1>SXU6xAf9LMY%7Li{Qs>yKph;3T< z_1+(SEJL!$i7I9;o6wBHAcVAQIt+w KteT^w2j~D#(H{&b{Sua6&|$83`NML=NShE>vFnIl?AI z7JTkJ|(p%JGgqZxax`z1|DQNW{=~|_thx}?~SPcs9#QL*|?wLw;z2^ z{BE^mZTsQ%U+x|1-6_bmKeArpCL|Q=z8xZ-7GjcA-DTY{aR1zZ*|{4ty}W1k;>V&9 z_LChoFUydy_z `+f6f7JDhDtpAe=ij*N e zp4|B1#F|$22cy(;65)x`ea_pBJ^At6-Cd(QMt-)vo>$tsazq^W+6SBt$9)fM?EopI zbbM3ueAlv3oR!c@CT&GKf)m3-O17yZrRqz#Ik=YmkLFv@JsZl;OoryLfhf1-Z6UYy z $ig~+ZL%L(bJ8h`}a|ME0HTY_lfBa`DC!3Fq9aV~dyEQ6dSk8Cs zOpSto0V`b^cxxhi9(mxO9@q=j`Fb;WCN)lXqBHy%Z!xWOz2)zVW|aQwL)TN0UzdPo zOStBR{nL*hR&CDq9BczlUJ2H4LFvWlO~>@TF5Pq7hi$wh^Y7BeAF>Kv6!o-kOL6nW zW30X1Cm(n{mE+#tOugW7&N{x~EzB|WhK#Cw7Q_|U77-Y~Tr>sduEHK=*i{j(Clrmu zInOOdG|%2O-xk=jy^v2I5wxuIdu?BSfBH~Uvc{)BsXLF~I6|urZQ{C!=d@lvt~wC^ zWu%LLG5A4VHbP0FS9Y{5Od;CszO+wv1T>!9(fE()qoUnGnPE$EXU?#oj~-h?Lf5kr z9>Ci6#on4D_*4^8nx=z!!HeAMjM1BGH{$)6Wqy}cQeJ-y3Bb+Kj;!)__iFwe!>t79 zjP1a9;O^%}n<(9HDfJlZ(+^c&M92g?-y*>@lCMA4eqHmFf9zlW2lqm9? Iw_TR4v>Dmv^gMUECkKPy& zn?&u|^oV;EWlds4^eGMjG6RmToQp`gC-%dEGQ{Ko6u|}E2B|PC;)~%c8)E|~(!qgX zmfwJiIqAy)JDYBmfy#!AiA@_&jF%S#w!NC1gBj$I@^D}Xei^NwImUtoaI0eXwUwUe zR{18nEgnFt@ln@ABl<>tinj%}sf6Y&oD(SYj57pWh_fWE9yYF!q|?GLCx-Kf*1y5J zm6=09%-W&FCd<;|_|^yzm+2%LwS|e &~l+ |?2T;jb+jl3`7x;&nE- aHijqf zUq8vOBL-=terDP!SJd-$>L#vcQe%}H=I5P7m`Tczw`d%nbx%C6pBY^8tl3)ZPIPKW zcaxm)ejCRa&mCB~WRES))jt@L!iEov{$gfm5q+-qV)B@^t<)v%kSl_{E61}N#vq@QOSuEDn!hIrdn&6(*rinFQf=l+h;Hf#uCjQ-_-aV%3g<{kwDIz|! z(x#VSy20oETnRqsKNltfnHS7ujQF_(2^{H^@eCDPj+maBuxw=AWvVVATl)CfFp+67 zY0mU^bh=42M3d5P(GAAFt*u6t#xFuw%x^k`WAdY`ghgb`ecm{pVtPB#P*Oj#;hDfs z%}#F%3N}2t#0=*PP+?w2GrQg$n%I)aQDQ2{U294LwZon_^dyFUwJo@)dVE~=^ls(O z!oEl0<{Rrd!SyDII^SqTjPQ@)?+(#2p3BF_*j~d6elhEW_lsxN^4JJF3yQcODQ-iR zBw3DB2xIU+D)g*M%#1BPIF@H@GuzxOvn6pSVhXjq={hpks&wLI!@6lnVw3Uiygskj zW)`|)^(Qr1C!?AkNo}jy4%hK;mEB41ok44J<~kP>Xt%xRsw;Ek+82l$6YRTmEY4^D z-@!tQRmJga{q2?BM?Lnz4(9_)TQs=|Qo#2lyXt^3)Uu%ENv$65kB}h);OwVEyX8C0 zbb$Z;`bZa6g@=0)RqgmbNf>QogX`jk|09n2U)Id1iQ5uqpMuOy}eNkM_#>C! zGfH_|&PaZtw>3O}mrrE ;?|q z`nkLETNymv)aXg{Kb-ud_(#ndTcb>UA6=2vKF&1=E~_~%nEe|a#dnr)FNq8mbgu8v zZlGrj$J@Dlu{RJuB&)Ed7N5F*{C3*OD5*uJ=X1(%oB2+;D*)X00B8ICFNQQz m|B!#+cGXDPU(L>0y z#V_VElo3XFQK4@A1& 7s|7%Ttkg-Yn6&IRwRi Ap^#!j@eTX3vTS?rCTD874tU7;k @bzj4Oa8;YY3hsF^wP3R(VQ5;i0rq|ZB^aKM{0Xw zT|hi5ZB?R%poZh_ymkVG2lWlWkyZ=unwKz?-<%bGc&fj((_yaDIdL7m-RhQUpF|=0 zVC?E90tG*m)U;S;6BRP Z>X87@H#SYo|b{OL6F48q774JgU(Eouo8`>lm`$nbo-rfvUz0*s$amtNekV zA^mqPUF)*I_*L#&aZrWF93|H@JSY6}Z#^d*#Co?GJ7*O1<}451t~YVArC|T%CS01k z2(#qBiIcnP&CVk$&%Q!bSs@{vLvAsB iP6SwpgUwZ87P@YzFTsirfNofXk zK&oJg;I)By^A$7tVd#!RV5We!SLKao&PY3rZNa_M-!%1Soe=%-$2PLH>MMA6@2FlO z>T%plwwY@T#ORt}G c{F$wK#ysCTq@zUxs7`RbRhJd33v zL2ba6^+XS|G- z5c9islKy148)v6T8rb}GODX= ZmXmV=*b(~Kkljz zn4f;91k7KISDf!Rt2|IYsW{mZO&?D@=bg6)VEPtp0}iQ4j%KcTKj;{4ml$p9Muud2 zL+JU(Zooitdy1YmmIspMx@791^YxVZI8rdAK^J9}7ta6YpY;WA1778NS}tW!k_^%Q zRwMpP>p1V@GK?R1S5J{T-# 3t{>LkbS-)n>kT;%OFky@3 z57C@c-~Krp$1CD{Z)`Yf#yY$p4 `5?Mc7Qa#kI61VAbgS9^# z!#G`9&Q8SPE0L_J{3`wp)1Z~vZ&tDHFA}bx6jC;kgRVu%CSSg2m|Unwq0?W#xZk#E zu~tqV39BRj*<`8q=!heMl!O2^ptv`jlfN*biH3<5knnxqB!$9+hZrUls)E?xYPs4{ zv`%<2B2rjg+b1;C%JM6aq)GqV;wyd>KD1kF6#Aw5XEoRyC3`Weh;VvDBlZj WPIKDsMW~Rgb^&;+Sal zfvU2?diJ;Y=neYsTYM3eXgC#{bKwH$n}30Jdr`Od(Wnorhr@5+JYpqTluYnVV%JO} zXYGXeXUJ#FRhnKi#H f?I-T-%s9$?EdhYeq3qMbhiOm()Vqk%PMn&*B06A DP1UOGG5H`;a&`&<=Z4}=wMtmLL^3UPFxBIhX47NsHOWrU0 zWEwlHtKCtr!_ZAJ`XLD~fUqmXZ&g2n_ap1h)F?~0jwfu{aX&?09yuH{(Pli??}d#0 zVp2uSK`iY&>_(~lVC(j3%ks-fx#^vY?`M9yv(s|It{T!ZjYDa&xX4}F6gJT~12((a z{h284J8r+lA}focKPjdByhG=HHlPR4+>c3bvYk3w;udz^iH2jgZ4G~@DBapomgWB0 zU0VvcpIGM;e|q}F#`mtXoN}S>U$}i82m!$l>sj-Jo`EcuJz+t1xP6i0kh z{Jyq+IN|AQqzu>kHlznk>{w=B)Go+!lZ_ofMh%RF6J;AnQYUW*N!NcKm-Rd4icngg zmMPr9mIm(UbEe`wPwlm_kWSe@EwlK_b8RCd#9dzFZ@{Xi{m*}12IUZxIml_<``>`^ z{I(3Kpu3{2BcI2Iayu3RhG?fpWg6w&mZf3(4N9ZxOHcH=zesn;sibwf(xJ$ID9kw0 z)U^g3Rmw_rR4?eBT$zOKd+a&HT$yb7DJ}6=_y3&^audiGyfqI5R3zoYr?g}dg> (jP8k@!OGzQU(hUbxjX9)41=M)b{Sm ^2*1UEiHb8zCGb>by6%fPM+rm6%)wX8Njr`> z5gk9xcqJp?yuXRCK)g3ySK r(`JuRp_8(p7}lR;f_##gjqK^5%7lHmG_4#SBTd+s{a|eDQuOyz7$K0 z+brU@!Rg)xDwY}^&S4VwF tw7!G}d9CJ15c+at2HQs;?^!Xveup{cy*65S z^8l2`1U1!bQ}aTLSzi|4tjoGr= OK!M$C%R=(y{YAl+C^~#KkT?LzYh~@OEr@1E=V@KF!tA*O#`XLBz@N` zQtWu4oq$5O!jGvDvE5(}c|;_pFY6gbeT^wtG{UbfRJ_=h15HB)ywv9uUTCpSCF;V; zWqOa|g9$V6;H&l9!ILrjXbUb!FGvB6 zX@K6@y9RERy%UqWJNy(*$o2u1#%Kegx=s@E0nG>TWg?3p)PN%$y~AN=eE){&lZzBa7GK1u1k3mIMwRS|(}t zuQrvO7b1mHH_l4(PV#eY`|Ie?ssWjY*jcHwifKe9=W`%*Db Ct&93p@{8FIfU&+O*n|zUK-fJbLnhQPlPsrAqQ=lO9zsIHZu-KXA>w5vTGq1wW{Oiv$g$piZXPxz*GkotcH~FUH>L*6 zCl$rOjVPae9ey=uHJtYx5!Tv`-M0TU=7~+)7k}`?6W_Y62&rjb$%q|)z|Y|{=T8E? zM|VIZ=e3pG-z@6%czYD)s<`8lFnZTq(bxy(%V3TfFT`q0@8I;Gnna7*EU;L(+RJ_4 z$gWNC#4C*DQ0wDBmOG-MWd#ZK>o(*LWLy#tRl`5Z${D?|wxL3R&?L2WZ-bkf|NQOO z{-;e#2PtfGFYs9VQ9181Bs=71p9}qEUG;*Cl`L|KxtBk-;y&(lZCyWE0)qjNAIN0` zzilrcN}lhV!b3H7+W**gpWKo(elqOq^resO)# #Azt=#uMF_JxU=Jp zHpaJQeV9@ya$d0&0iLTW01) i>C6PyUw71OTe* zWo}4#@_)J#B#RS>4}=e^0v#O^t_^gmpOXDgL8~0lv9IvaHxKy$IR?2esDVrZ{$p9d zgUiohC0mb40h9cH0l>Xm_Y?EQ9X+*KBiX%0>}#@W4U?k(Srq^4?ScIIf;4brN85TG z%a@5bO4uy=9A0ku`v@gh=dU$fnVm0qM>L6WbkX2e;AN4c CVMoq6 diff --git a/client/src/App.vue b/client/src/App.vue index 708dd650f..1afbd619a 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -4,8 +4,8 @@ import { useRoute } from 'vue-router' import { INIT_OPTIONS_KEY, THEME_KEY } from 'vue-echarts' import { APP_FEATURES } from '@bookorbit/types' import { useChangePasswordDialog } from '@/composables/useChangePasswordDialog' -import { useThemeStore } from '@/stores/theme' -import { getBookorbitThemeName, initChartThemes } from '@/lib/echarts' +import { useChartTokens } from '@/composables/useChartTokens' +import { buildChartTheme } from '@/lib/echarts' import ChangePasswordDialog from '@/features/auth/ChangePasswordDialog.vue' import WhatsNewDialog from '@/features/whats-new/WhatsNewDialog.vue' import { useWhatsNew } from '@/features/whats-new/composables/useWhatsNew' @@ -40,7 +40,7 @@ const TOASTER_MOBILE_OFFSET = { } const { isOpen } = useChangePasswordDialog() -const themeStore = useThemeStore() +const chartTokens = useChartTokens() const route = useRoute() const { user } = useAuth() @@ -79,12 +79,10 @@ watch( (name) => syncPopup(name as string | undefined), ) -initChartThemes() - provide(INIT_OPTIONS_KEY, { renderer: 'svg' }) provide( THEME_KEY, - computed(() => getBookorbitThemeName(themeStore.resolvedTheme, themeStore.accent)), + computed(() => buildChartTheme(chartTokens.value)), ) diff --git a/client/src/assets/main.css b/client/src/assets/main.css index e8538a074..b3c4d4e97 100644 --- a/client/src/assets/main.css +++ b/client/src/assets/main.css @@ -6,6 +6,8 @@ @import './theme/tokens.css'; @import './theme/accents.css'; @import './theme/radius.css'; +@import './theme/surfaces.css'; +@import './theme/styles/nostalgia.css'; @import './theme/bridge.css'; @import './theme/cover-effects.css'; @@ -252,11 +254,34 @@ } .glow-wrapper { - background-image: radial-gradient(50% 50% at center -25px, color-mix(in oklch, var(--primary) 35%, transparent) 0%, transparent 100%); - background-blend-mode: hard-light, color-dodge; + background-image: var(--glow-image); background-position: top; background-repeat: no-repeat; - background-size: auto 12rem; + background-size: auto calc(var(--glow-band) * var(--glow-reach)); +} + +/* The texture sits on its own layer so its tiles keep their size; the reach only moves the fade. Negative + z-index keeps it under the shell's content and the glow, like the background patterns. */ +.glow-wrapper::after { + content: ''; + position: absolute; + inset: 0 0 auto; + z-index: -1; + height: calc(var(--glow-band) * var(--glow-reach)); + pointer-events: none; + background-image: var(--glow-texture); + background-position: top center; + background-size: var(--glow-texture-size); + -webkit-mask-image: radial-gradient(ellipse 62% 100% at 50% 0%, #000 0%, transparent 100%); + mask-image: radial-gradient(ellipse 62% 100% at 50% 0%, #000 0%, transparent 100%); +} + +html[data-glow='off'] .glow-wrapper { + background-image: none; +} + +html[data-glow='off'] .glow-wrapper::after { + content: none; } @layer base { @@ -358,7 +383,7 @@ @layer components { .viewer-btn { - @apply flex items-center justify-center w-8 h-8 rounded text-foreground hover:bg-muted transition-colors shrink-0 disabled:opacity-30 disabled:cursor-not-allowed; + @apply flex items-center justify-center w-8 h-8 rounded-xs text-foreground hover:bg-muted transition-colors shrink-0 disabled:opacity-30 disabled:cursor-not-allowed; } .viewer-sep { @apply w-px h-5 mx-1 bg-border shrink-0; @@ -385,6 +410,44 @@ .settings-value { @apply text-sm font-medium tabular-nums text-foreground; } + /* ── App shell ───────────────────────────────────────────── + The glass behind the sidebar and header cards. A style swaps + --shell-backdrop to change it. + ────────────────────────────────────────────────────────────── */ + .shell-backdrop { + backdrop-filter: var(--shell-backdrop); + -webkit-backdrop-filter: var(--shell-backdrop); + } + .sidebar-section-label { + font-size: var(--sidebar-heading-size); + font-weight: var(--sidebar-heading-weight); + letter-spacing: var(--sidebar-heading-tracking); + color: var(--sidebar-heading-foreground); + text-transform: uppercase; + } + /* Count chips beside sidebar rows and collapsed sections. Colour utilities on the element still win. */ + .sidebar-count { + background-color: var(--sidebar-count-bg); + font-size: var(--sidebar-count-size); + font-weight: var(--sidebar-count-weight); + color: var(--sidebar-count-foreground); + } + + /* ── Book metadata chips ─────────────────────────────────── + Genres and tags on the book detail page. The ring is inset so + a style can add one without moving the genre fold measurement. + ────────────────────────────────────────────────────────────── */ + .chip-genre { + background-color: var(--chip-genre-bg); + color: var(--chip-genre-fg); + box-shadow: inset 0 0 0 1px var(--chip-genre-border); + } + .chip-tag { + background-color: var(--chip-tag-bg); + color: var(--chip-tag-fg); + border-color: var(--chip-tag-border); + } + /* ── Provider chips ──────────────────────────────────────── Callers set --provider-color; the ink/fill/ring mixes come from the theme so light mode darkens the brand hue enough to pass contrast. See tokens.css. diff --git a/client/src/assets/theme/accents.css b/client/src/assets/theme/accents.css index e59f4ac6f..c7eea518f 100644 --- a/client/src/assets/theme/accents.css +++ b/client/src/assets/theme/accents.css @@ -1,6 +1,7 @@ /* ── Accent presets ────────────────────────────────────────── Applied as a class on . Overrides --tint-h (surfaces) - and --primary (interactive elements). + and --accent-primary, which --primary reads unless a style + derives its own shade from it. Vivid: high chroma. Pastel: low chroma, muted feel. Yellow and yellow-green accents use dark foreground text where needed. ────────────────────────────────────────────────────────────── */ @@ -10,7 +11,7 @@ html.accent-white { --tint-h: 0; --tint-c: 0; - --primary: oklch(0.2 0 0); + --accent-primary: oklch(0.2 0 0); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.2 0 0); --sidebar: oklch(0.985 0 0); @@ -20,7 +21,7 @@ html.accent-white { html.accent-white.dark { --tint-h: 0; --tint-c: 0; - --primary: oklch(0.985 0 0); + --accent-primary: oklch(0.985 0 0); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.985 0 0); --sidebar: oklch(0.205 0 0); @@ -31,7 +32,7 @@ html.accent-white.dark { html.accent-grey { --tint-h: 0; --tint-c: 0; - --primary: oklch(0.55 0 0); + --accent-primary: oklch(0.55 0 0); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.55 0 0); --sidebar: oklch(0.96 0 0); @@ -41,7 +42,7 @@ html.accent-grey { html.accent-grey.dark { --tint-h: 0; --tint-c: 0; - --primary: oklch(0.75 0 0); + --accent-primary: oklch(0.75 0 0); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.75 0 0); --sidebar: oklch(0.22 0 0); @@ -51,14 +52,14 @@ html.accent-grey.dark { html.accent-scarlet { --tint-h: 2; - --primary: oklch(0.58 0.25 2); + --accent-primary: oklch(0.58 0.25 2); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.58 0.25 2); --sidebar-primary: oklch(0.58 0.25 2); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-scarlet.dark { - --primary: oklch(0.74 0.2 2); + --accent-primary: oklch(0.74 0.2 2); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.74 0.2 2); --sidebar-primary: oklch(0.74 0.2 2); @@ -67,14 +68,14 @@ html.accent-scarlet.dark { html.accent-rose { --tint-h: 15; - --primary: oklch(0.57 0.24 15); + --accent-primary: oklch(0.57 0.24 15); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.57 0.24 15); --sidebar-primary: oklch(0.57 0.24 15); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-rose.dark { - --primary: oklch(0.73 0.2 15); + --accent-primary: oklch(0.73 0.2 15); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.73 0.2 15); --sidebar-primary: oklch(0.73 0.2 15); @@ -83,14 +84,14 @@ html.accent-rose.dark { html.accent-orange { --tint-h: 42; - --primary: oklch(0.64 0.22 42); + --accent-primary: oklch(0.64 0.22 42); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.64 0.22 42); --sidebar-primary: oklch(0.64 0.22 42); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-orange.dark { - --primary: oklch(0.78 0.18 42); + --accent-primary: oklch(0.78 0.18 42); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.78 0.18 42); --sidebar-primary: oklch(0.78 0.18 42); @@ -99,14 +100,14 @@ html.accent-orange.dark { html.accent-marigold { --tint-h: 82; - --primary: oklch(0.76 0.18 82); + --accent-primary: oklch(0.76 0.18 82); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.76 0.18 82); --sidebar-primary: oklch(0.76 0.18 82); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-marigold.dark { - --primary: oklch(0.84 0.15 82); + --accent-primary: oklch(0.84 0.15 82); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.84 0.15 82); --sidebar-primary: oklch(0.84 0.15 82); @@ -115,14 +116,14 @@ html.accent-marigold.dark { html.accent-amber { --tint-h: 70; - --primary: oklch(0.72 0.17 70); + --accent-primary: oklch(0.72 0.17 70); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.72 0.17 70); --sidebar-primary: oklch(0.72 0.17 70); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-amber.dark { - --primary: oklch(0.82 0.17 70); + --accent-primary: oklch(0.82 0.17 70); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.82 0.17 70); --sidebar-primary: oklch(0.82 0.17 70); @@ -131,14 +132,14 @@ html.accent-amber.dark { html.accent-yellow { --tint-h: 95; - --primary: oklch(0.75 0.18 95); + --accent-primary: oklch(0.75 0.18 95); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.75 0.18 95); --sidebar-primary: oklch(0.75 0.18 95); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-yellow.dark { - --primary: oklch(0.84 0.16 95); + --accent-primary: oklch(0.84 0.16 95); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.84 0.16 95); --sidebar-primary: oklch(0.84 0.16 95); @@ -147,14 +148,14 @@ html.accent-yellow.dark { html.accent-viridian { --tint-h: 171; - --primary: oklch(0.54 0.17 171); + --accent-primary: oklch(0.54 0.17 171); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.54 0.17 171); --sidebar-primary: oklch(0.54 0.17 171); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-viridian.dark { - --primary: oklch(0.73 0.14 171); + --accent-primary: oklch(0.73 0.14 171); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.73 0.14 171); --sidebar-primary: oklch(0.73 0.14 171); @@ -163,14 +164,14 @@ html.accent-viridian.dark { html.accent-lime { --tint-h: 118; - --primary: oklch(0.64 0.2 118); + --accent-primary: oklch(0.64 0.2 118); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.64 0.2 118); --sidebar-primary: oklch(0.64 0.2 118); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-lime.dark { - --primary: oklch(0.77 0.18 118); + --accent-primary: oklch(0.77 0.18 118); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.77 0.18 118); --sidebar-primary: oklch(0.77 0.18 118); @@ -179,14 +180,14 @@ html.accent-lime.dark { html.accent-green { --tint-h: 142; - --primary: oklch(0.527 0.18 142); + --accent-primary: oklch(0.527 0.18 142); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.527 0.18 142); --sidebar-primary: oklch(0.527 0.18 142); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-green.dark { - --primary: oklch(0.72 0.18 142); + --accent-primary: oklch(0.72 0.18 142); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.72 0.18 142); --sidebar-primary: oklch(0.72 0.18 142); @@ -195,14 +196,14 @@ html.accent-green.dark { html.accent-emerald { --tint-h: 162; - --primary: oklch(0.52 0.17 162); + --accent-primary: oklch(0.52 0.17 162); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.52 0.17 162); --sidebar-primary: oklch(0.52 0.17 162); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-emerald.dark { - --primary: oklch(0.72 0.15 162); + --accent-primary: oklch(0.72 0.15 162); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.72 0.15 162); --sidebar-primary: oklch(0.72 0.15 162); @@ -211,14 +212,14 @@ html.accent-emerald.dark { html.accent-teal { --tint-h: 180; - --primary: oklch(0.52 0.18 180); + --accent-primary: oklch(0.52 0.18 180); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.52 0.18 180); --sidebar-primary: oklch(0.52 0.18 180); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-teal.dark { - --primary: oklch(0.74 0.16 180); + --accent-primary: oklch(0.74 0.16 180); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.74 0.16 180); --sidebar-primary: oklch(0.74 0.16 180); @@ -227,14 +228,14 @@ html.accent-teal.dark { html.accent-cyan { --tint-h: 197; - --primary: oklch(0.52 0.2 197); + --accent-primary: oklch(0.52 0.2 197); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.52 0.2 197); --sidebar-primary: oklch(0.52 0.2 197); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-cyan.dark { - --primary: oklch(0.75 0.17 197); + --accent-primary: oklch(0.75 0.17 197); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.75 0.17 197); --sidebar-primary: oklch(0.75 0.17 197); @@ -243,14 +244,14 @@ html.accent-cyan.dark { html.accent-jade { --tint-h: 155; - --primary: oklch(0.62 0.2 155); + --accent-primary: oklch(0.62 0.2 155); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.62 0.2 155); --sidebar-primary: oklch(0.62 0.2 155); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-jade.dark { - --primary: oklch(0.78 0.17 155); + --accent-primary: oklch(0.78 0.17 155); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.78 0.17 155); --sidebar-primary: oklch(0.78 0.17 155); @@ -259,14 +260,14 @@ html.accent-jade.dark { html.accent-iris { --tint-h: 270; - --primary: oklch(0.52 0.25 270); + --accent-primary: oklch(0.52 0.25 270); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.52 0.25 270); --sidebar-primary: oklch(0.52 0.25 270); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-iris.dark { - --primary: oklch(0.72 0.21 270); + --accent-primary: oklch(0.72 0.21 270); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.72 0.21 270); --sidebar-primary: oklch(0.72 0.21 270); @@ -275,14 +276,14 @@ html.accent-iris.dark { html.accent-blue { --tint-h: 263; - --primary: oklch(0.487 0.25 263); + --accent-primary: oklch(0.487 0.25 263); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.487 0.25 263); --sidebar-primary: oklch(0.487 0.25 263); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-blue.dark { - --primary: oklch(0.72 0.2 263); + --accent-primary: oklch(0.72 0.2 263); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.72 0.2 263); --sidebar-primary: oklch(0.72 0.2 263); @@ -291,14 +292,14 @@ html.accent-blue.dark { html.accent-indigo { --tint-h: 276; - --primary: oklch(0.51 0.26 276); + --accent-primary: oklch(0.51 0.26 276); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.51 0.26 276); --sidebar-primary: oklch(0.51 0.26 276); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-indigo.dark { - --primary: oklch(0.72 0.22 276); + --accent-primary: oklch(0.72 0.22 276); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.72 0.22 276); --sidebar-primary: oklch(0.72 0.22 276); @@ -307,14 +308,14 @@ html.accent-indigo.dark { html.accent-violet { --tint-h: 292; - --primary: oklch(0.491 0.27 292); + --accent-primary: oklch(0.491 0.27 292); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.491 0.27 292); --sidebar-primary: oklch(0.491 0.27 292); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-violet.dark { - --primary: oklch(0.72 0.23 292); + --accent-primary: oklch(0.72 0.23 292); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.72 0.23 292); --sidebar-primary: oklch(0.72 0.23 292); @@ -323,14 +324,14 @@ html.accent-violet.dark { html.accent-fuchsia { --tint-h: 312; - --primary: oklch(0.56 0.27 312); + --accent-primary: oklch(0.56 0.27 312); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.56 0.27 312); --sidebar-primary: oklch(0.56 0.27 312); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-fuchsia.dark { - --primary: oklch(0.75 0.22 312); + --accent-primary: oklch(0.75 0.22 312); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.75 0.22 312); --sidebar-primary: oklch(0.75 0.22 312); @@ -339,14 +340,14 @@ html.accent-fuchsia.dark { html.accent-pink { --tint-h: 328; - --primary: oklch(0.56 0.26 328); + --accent-primary: oklch(0.56 0.26 328); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.56 0.26 328); --sidebar-primary: oklch(0.56 0.26 328); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-pink.dark { - --primary: oklch(0.75 0.22 328); + --accent-primary: oklch(0.75 0.22 328); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.75 0.22 328); --sidebar-primary: oklch(0.75 0.22 328); @@ -357,14 +358,14 @@ html.accent-pink.dark { html.accent-rosewater { --tint-h: 2; - --primary: oklch(0.7 0.125 2); + --accent-primary: oklch(0.7 0.125 2); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.7 0.125 2); --sidebar-primary: oklch(0.7 0.125 2); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-rosewater.dark { - --primary: oklch(0.8 0.11 2); + --accent-primary: oklch(0.8 0.11 2); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.8 0.11 2); --sidebar-primary: oklch(0.8 0.11 2); @@ -373,14 +374,14 @@ html.accent-rosewater.dark { html.accent-coral { --tint-h: 15; - --primary: oklch(0.69 0.12 15); + --accent-primary: oklch(0.69 0.12 15); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.69 0.12 15); --sidebar-primary: oklch(0.69 0.12 15); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-coral.dark { - --primary: oklch(0.79 0.11 15); + --accent-primary: oklch(0.79 0.11 15); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.79 0.11 15); --sidebar-primary: oklch(0.79 0.11 15); @@ -389,14 +390,14 @@ html.accent-coral.dark { html.accent-peach { --tint-h: 42; - --primary: oklch(0.76 0.11 42); + --accent-primary: oklch(0.76 0.11 42); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.76 0.11 42); --sidebar-primary: oklch(0.76 0.11 42); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-peach.dark { - --primary: oklch(0.84 0.1 42); + --accent-primary: oklch(0.84 0.1 42); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.84 0.1 42); --sidebar-primary: oklch(0.84 0.1 42); @@ -405,14 +406,14 @@ html.accent-peach.dark { html.accent-flax { --tint-h: 82; - --primary: oklch(0.82 0.09 82); + --accent-primary: oklch(0.82 0.09 82); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.82 0.09 82); --sidebar-primary: oklch(0.82 0.09 82); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-flax.dark { - --primary: oklch(0.88 0.08 82); + --accent-primary: oklch(0.88 0.08 82); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.88 0.08 82); --sidebar-primary: oklch(0.88 0.08 82); @@ -421,14 +422,14 @@ html.accent-flax.dark { html.accent-butter { --tint-h: 70; - --primary: oklch(0.82 0.085 70); + --accent-primary: oklch(0.82 0.085 70); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.82 0.085 70); --sidebar-primary: oklch(0.82 0.085 70); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-butter.dark { - --primary: oklch(0.88 0.09 70); + --accent-primary: oklch(0.88 0.09 70); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.88 0.09 70); --sidebar-primary: oklch(0.88 0.09 70); @@ -437,14 +438,14 @@ html.accent-butter.dark { html.accent-lemon { --tint-h: 95; - --primary: oklch(0.82 0.09 95); + --accent-primary: oklch(0.82 0.09 95); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.82 0.09 95); --sidebar-primary: oklch(0.82 0.09 95); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-lemon.dark { - --primary: oklch(0.88 0.09 95); + --accent-primary: oklch(0.88 0.09 95); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.88 0.09 95); --sidebar-primary: oklch(0.88 0.09 95); @@ -453,14 +454,14 @@ html.accent-lemon.dark { html.accent-foam { --tint-h: 171; - --primary: oklch(0.68 0.085 171); + --accent-primary: oklch(0.68 0.085 171); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.085 171); --sidebar-primary: oklch(0.68 0.085 171); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-foam.dark { - --primary: oklch(0.79 0.08 171); + --accent-primary: oklch(0.79 0.08 171); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.79 0.08 171); --sidebar-primary: oklch(0.79 0.08 171); @@ -469,14 +470,14 @@ html.accent-foam.dark { html.accent-celadon { --tint-h: 118; - --primary: oklch(0.76 0.1 118); + --accent-primary: oklch(0.76 0.1 118); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.76 0.1 118); --sidebar-primary: oklch(0.76 0.1 118); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-celadon.dark { - --primary: oklch(0.83 0.1 118); + --accent-primary: oklch(0.83 0.1 118); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.83 0.1 118); --sidebar-primary: oklch(0.83 0.1 118); @@ -485,14 +486,14 @@ html.accent-celadon.dark { html.accent-sage { --tint-h: 142; - --primary: oklch(0.68 0.09 142); + --accent-primary: oklch(0.68 0.09 142); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.09 142); --sidebar-primary: oklch(0.68 0.09 142); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-sage.dark { - --primary: oklch(0.78 0.1 142); + --accent-primary: oklch(0.78 0.1 142); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.78 0.1 142); --sidebar-primary: oklch(0.78 0.1 142); @@ -501,14 +502,14 @@ html.accent-sage.dark { html.accent-mint { --tint-h: 162; - --primary: oklch(0.68 0.085 162); + --accent-primary: oklch(0.68 0.085 162); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.085 162); --sidebar-primary: oklch(0.68 0.085 162); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-mint.dark { - --primary: oklch(0.78 0.08 162); + --accent-primary: oklch(0.78 0.08 162); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.78 0.08 162); --sidebar-primary: oklch(0.78 0.08 162); @@ -517,14 +518,14 @@ html.accent-mint.dark { html.accent-seafoam { --tint-h: 180; - --primary: oklch(0.68 0.09 180); + --accent-primary: oklch(0.68 0.09 180); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.09 180); --sidebar-primary: oklch(0.68 0.09 180); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-seafoam.dark { - --primary: oklch(0.8 0.09 180); + --accent-primary: oklch(0.8 0.09 180); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.8 0.09 180); --sidebar-primary: oklch(0.8 0.09 180); @@ -533,14 +534,14 @@ html.accent-seafoam.dark { html.accent-powder { --tint-h: 197; - --primary: oklch(0.68 0.1 197); + --accent-primary: oklch(0.68 0.1 197); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.1 197); --sidebar-primary: oklch(0.68 0.1 197); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-powder.dark { - --primary: oklch(0.81 0.09 197); + --accent-primary: oklch(0.81 0.09 197); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.81 0.09 197); --sidebar-primary: oklch(0.81 0.09 197); @@ -549,14 +550,14 @@ html.accent-powder.dark { html.accent-sea-glass { --tint-h: 155; - --primary: oklch(0.74 0.1 155); + --accent-primary: oklch(0.74 0.1 155); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.74 0.1 155); --sidebar-primary: oklch(0.74 0.1 155); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-sea-glass.dark { - --primary: oklch(0.84 0.09 155); + --accent-primary: oklch(0.84 0.09 155); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.84 0.09 155); --sidebar-primary: oklch(0.84 0.09 155); @@ -565,14 +566,14 @@ html.accent-sea-glass.dark { html.accent-cornflower { --tint-h: 247; - --primary: oklch(0.68 0.14 247); + --accent-primary: oklch(0.68 0.14 247); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.14 247); --sidebar-primary: oklch(0.68 0.14 247); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-cornflower.dark { - --primary: oklch(0.76 0.12 247); + --accent-primary: oklch(0.76 0.12 247); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.76 0.12 247); --sidebar-primary: oklch(0.76 0.12 247); @@ -581,14 +582,14 @@ html.accent-cornflower.dark { html.accent-periwinkle { --tint-h: 263; - --primary: oklch(0.68 0.125 263); + --accent-primary: oklch(0.68 0.125 263); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.125 263); --sidebar-primary: oklch(0.68 0.125 263); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-periwinkle.dark { - --primary: oklch(0.78 0.11 263); + --accent-primary: oklch(0.78 0.11 263); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.78 0.11 263); --sidebar-primary: oklch(0.78 0.11 263); @@ -597,14 +598,14 @@ html.accent-periwinkle.dark { html.accent-wisteria { --tint-h: 276; - --primary: oklch(0.68 0.13 276); + --accent-primary: oklch(0.68 0.13 276); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.13 276); --sidebar-primary: oklch(0.68 0.13 276); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-wisteria.dark { - --primary: oklch(0.78 0.12 276); + --accent-primary: oklch(0.78 0.12 276); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.78 0.12 276); --sidebar-primary: oklch(0.78 0.12 276); @@ -613,14 +614,14 @@ html.accent-wisteria.dark { html.accent-lavender { --tint-h: 292; - --primary: oklch(0.68 0.135 292); + --accent-primary: oklch(0.68 0.135 292); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.135 292); --sidebar-primary: oklch(0.68 0.135 292); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-lavender.dark { - --primary: oklch(0.78 0.13 292); + --accent-primary: oklch(0.78 0.13 292); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.78 0.13 292); --sidebar-primary: oklch(0.78 0.13 292); @@ -629,14 +630,14 @@ html.accent-lavender.dark { html.accent-orchid { --tint-h: 312; - --primary: oklch(0.68 0.135 312); + --accent-primary: oklch(0.68 0.135 312); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.135 312); --sidebar-primary: oklch(0.68 0.135 312); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-orchid.dark { - --primary: oklch(0.81 0.12 312); + --accent-primary: oklch(0.81 0.12 312); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.81 0.12 312); --sidebar-primary: oklch(0.81 0.12 312); @@ -645,14 +646,14 @@ html.accent-orchid.dark { html.accent-blush { --tint-h: 328; - --primary: oklch(0.68 0.13 328); + --accent-primary: oklch(0.68 0.13 328); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.13 328); --sidebar-primary: oklch(0.68 0.13 328); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-blush.dark { - --primary: oklch(0.81 0.12 328); + --accent-primary: oklch(0.81 0.12 328); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.81 0.12 328); --sidebar-primary: oklch(0.81 0.12 328); @@ -661,14 +662,14 @@ html.accent-blush.dark { html.accent-vermilion { --tint-h: 28; - --primary: oklch(0.62 0.24 28); + --accent-primary: oklch(0.62 0.24 28); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.62 0.24 28); --sidebar-primary: oklch(0.62 0.24 28); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-vermilion.dark { - --primary: oklch(0.77 0.19 28); + --accent-primary: oklch(0.77 0.19 28); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.77 0.19 28); --sidebar-primary: oklch(0.77 0.19 28); @@ -677,14 +678,14 @@ html.accent-vermilion.dark { html.accent-salmon { --tint-h: 28; - --primary: oklch(0.74 0.12 28); + --accent-primary: oklch(0.74 0.12 28); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.74 0.12 28); --sidebar-primary: oklch(0.74 0.12 28); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-salmon.dark { - --primary: oklch(0.83 0.1 28); + --accent-primary: oklch(0.83 0.1 28); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.83 0.1 28); --sidebar-primary: oklch(0.83 0.1 28); @@ -693,14 +694,14 @@ html.accent-salmon.dark { html.accent-copper { --tint-h: 56; - --primary: oklch(0.65 0.19 56); + --accent-primary: oklch(0.65 0.19 56); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.65 0.19 56); --sidebar-primary: oklch(0.65 0.19 56); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-copper.dark { - --primary: oklch(0.79 0.16 56); + --accent-primary: oklch(0.79 0.16 56); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.79 0.16 56); --sidebar-primary: oklch(0.79 0.16 56); @@ -709,14 +710,14 @@ html.accent-copper.dark { html.accent-sand { --tint-h: 56; - --primary: oklch(0.77 0.095 56); + --accent-primary: oklch(0.77 0.095 56); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.77 0.095 56); --sidebar-primary: oklch(0.77 0.095 56); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-sand.dark { - --primary: oklch(0.85 0.09 56); + --accent-primary: oklch(0.85 0.09 56); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.85 0.09 56); --sidebar-primary: oklch(0.85 0.09 56); @@ -725,14 +726,14 @@ html.accent-sand.dark { html.accent-chartreuse { --tint-h: 106; - --primary: oklch(0.76 0.2 106); + --accent-primary: oklch(0.76 0.2 106); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.76 0.2 106); --sidebar-primary: oklch(0.76 0.2 106); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-chartreuse.dark { - --primary: oklch(0.83 0.17 106); + --accent-primary: oklch(0.83 0.17 106); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.83 0.17 106); --sidebar-primary: oklch(0.83 0.17 106); @@ -741,14 +742,14 @@ html.accent-chartreuse.dark { html.accent-pear { --tint-h: 106; - --primary: oklch(0.82 0.1 106); + --accent-primary: oklch(0.82 0.1 106); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.82 0.1 106); --sidebar-primary: oklch(0.82 0.1 106); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-pear.dark { - --primary: oklch(0.88 0.09 106); + --accent-primary: oklch(0.88 0.09 106); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.88 0.09 106); --sidebar-primary: oklch(0.88 0.09 106); @@ -757,14 +758,14 @@ html.accent-pear.dark { html.accent-wasabi { --tint-h: 130; - --primary: oklch(0.68 0.19 130); + --accent-primary: oklch(0.68 0.19 130); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.19 130); --sidebar-primary: oklch(0.68 0.19 130); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-wasabi.dark { - --primary: oklch(0.79 0.16 130); + --accent-primary: oklch(0.79 0.16 130); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.79 0.16 130); --sidebar-primary: oklch(0.79 0.16 130); @@ -773,14 +774,14 @@ html.accent-wasabi.dark { html.accent-sprout { --tint-h: 130; - --primary: oklch(0.8 0.095 130); + --accent-primary: oklch(0.8 0.095 130); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.8 0.095 130); --sidebar-primary: oklch(0.8 0.095 130); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-sprout.dark { - --primary: oklch(0.85 0.09 130); + --accent-primary: oklch(0.85 0.09 130); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.85 0.09 130); --sidebar-primary: oklch(0.85 0.09 130); @@ -789,14 +790,14 @@ html.accent-sprout.dark { html.accent-malachite { --tint-h: 152; - --primary: oklch(0.56 0.18 152); + --accent-primary: oklch(0.56 0.18 152); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.56 0.18 152); --sidebar-primary: oklch(0.56 0.18 152); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-malachite.dark { - --primary: oklch(0.73 0.15 152); + --accent-primary: oklch(0.73 0.15 152); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.73 0.15 152); --sidebar-primary: oklch(0.73 0.15 152); @@ -805,14 +806,14 @@ html.accent-malachite.dark { html.accent-aloe { --tint-h: 152; - --primary: oklch(0.68 0.09 152); + --accent-primary: oklch(0.68 0.09 152); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.09 152); --sidebar-primary: oklch(0.68 0.09 152); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-aloe.dark { - --primary: oklch(0.79 0.08 152); + --accent-primary: oklch(0.79 0.08 152); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.79 0.08 152); --sidebar-primary: oklch(0.79 0.08 152); @@ -821,14 +822,14 @@ html.accent-aloe.dark { html.accent-turquoise { --tint-h: 188; - --primary: oklch(0.58 0.18 188); + --accent-primary: oklch(0.58 0.18 188); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.58 0.18 188); --sidebar-primary: oklch(0.58 0.18 188); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-turquoise.dark { - --primary: oklch(0.75 0.15 188); + --accent-primary: oklch(0.75 0.15 188); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.75 0.15 188); --sidebar-primary: oklch(0.75 0.15 188); @@ -837,14 +838,14 @@ html.accent-turquoise.dark { html.accent-aqua { --tint-h: 188; - --primary: oklch(0.7 0.09 188); + --accent-primary: oklch(0.7 0.09 188); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.7 0.09 188); --sidebar-primary: oklch(0.7 0.09 188); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-aqua.dark { - --primary: oklch(0.81 0.08 188); + --accent-primary: oklch(0.81 0.08 188); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.81 0.08 188); --sidebar-primary: oklch(0.81 0.08 188); @@ -853,14 +854,14 @@ html.accent-aqua.dark { html.accent-acid-green { --tint-h: 112; - --primary: oklch(0.7 0.2 112); + --accent-primary: oklch(0.7 0.2 112); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.7 0.2 112); --sidebar-primary: oklch(0.7 0.2 112); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-acid-green.dark { - --primary: oklch(0.82 0.17 112); + --accent-primary: oklch(0.82 0.17 112); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.82 0.17 112); --sidebar-primary: oklch(0.82 0.17 112); @@ -869,14 +870,14 @@ html.accent-acid-green.dark { html.accent-pistachio { --tint-h: 112; - --primary: oklch(0.8 0.11 112); + --accent-primary: oklch(0.8 0.11 112); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.8 0.11 112); --sidebar-primary: oklch(0.8 0.11 112); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-pistachio.dark { - --primary: oklch(0.88 0.09 112); + --accent-primary: oklch(0.88 0.09 112); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.88 0.09 112); --sidebar-primary: oklch(0.88 0.09 112); @@ -885,14 +886,14 @@ html.accent-pistachio.dark { html.accent-electric-blue { --tint-h: 230; - --primary: oklch(0.54 0.24 230); + --accent-primary: oklch(0.54 0.24 230); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.54 0.24 230); --sidebar-primary: oklch(0.54 0.24 230); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-electric-blue.dark { - --primary: oklch(0.73 0.19 230); + --accent-primary: oklch(0.73 0.19 230); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.73 0.19 230); --sidebar-primary: oklch(0.73 0.19 230); @@ -901,14 +902,14 @@ html.accent-electric-blue.dark { html.accent-baby-blue { --tint-h: 230; - --primary: oklch(0.68 0.12 230); + --accent-primary: oklch(0.68 0.12 230); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.12 230); --sidebar-primary: oklch(0.68 0.12 230); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-baby-blue.dark { - --primary: oklch(0.79 0.1 230); + --accent-primary: oklch(0.79 0.1 230); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.79 0.1 230); --sidebar-primary: oklch(0.79 0.1 230); @@ -917,14 +918,14 @@ html.accent-baby-blue.dark { html.accent-ultramarine { --tint-h: 247; - --primary: oklch(0.5 0.28 247); + --accent-primary: oklch(0.5 0.28 247); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.5 0.28 247); --sidebar-primary: oklch(0.5 0.28 247); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-ultramarine.dark { - --primary: oklch(0.7 0.22 247); + --accent-primary: oklch(0.7 0.22 247); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.7 0.22 247); --sidebar-primary: oklch(0.7 0.22 247); @@ -933,14 +934,14 @@ html.accent-ultramarine.dark { html.accent-bluebell { --tint-h: 270; - --primary: oklch(0.68 0.125 270); + --accent-primary: oklch(0.68 0.125 270); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.125 270); --sidebar-primary: oklch(0.68 0.125 270); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-bluebell.dark { - --primary: oklch(0.78 0.12 270); + --accent-primary: oklch(0.78 0.12 270); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.78 0.12 270); --sidebar-primary: oklch(0.78 0.12 270); @@ -949,14 +950,14 @@ html.accent-bluebell.dark { html.accent-purple { --tint-h: 284; - --primary: oklch(0.51 0.25 284); + --accent-primary: oklch(0.51 0.25 284); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.51 0.25 284); --sidebar-primary: oklch(0.51 0.25 284); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-purple.dark { - --primary: oklch(0.72 0.21 284); + --accent-primary: oklch(0.72 0.21 284); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.72 0.21 284); --sidebar-primary: oklch(0.72 0.21 284); @@ -965,14 +966,14 @@ html.accent-purple.dark { html.accent-thistle { --tint-h: 284; - --primary: oklch(0.68 0.125 284); + --accent-primary: oklch(0.68 0.125 284); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.125 284); --sidebar-primary: oklch(0.68 0.125 284); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-thistle.dark { - --primary: oklch(0.78 0.12 284); + --accent-primary: oklch(0.78 0.12 284); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.78 0.12 284); --sidebar-primary: oklch(0.78 0.12 284); @@ -981,14 +982,14 @@ html.accent-thistle.dark { html.accent-amethyst { --tint-h: 302; - --primary: oklch(0.54 0.24 302); + --accent-primary: oklch(0.54 0.24 302); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.54 0.24 302); --sidebar-primary: oklch(0.54 0.24 302); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-amethyst.dark { - --primary: oklch(0.73 0.2 302); + --accent-primary: oklch(0.73 0.2 302); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.73 0.2 302); --sidebar-primary: oklch(0.73 0.2 302); @@ -997,14 +998,14 @@ html.accent-amethyst.dark { html.accent-mauve { --tint-h: 302; - --primary: oklch(0.68 0.12 302); + --accent-primary: oklch(0.68 0.12 302); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.12 302); --sidebar-primary: oklch(0.68 0.12 302); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-mauve.dark { - --primary: oklch(0.79 0.11 302); + --accent-primary: oklch(0.79 0.11 302); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.79 0.11 302); --sidebar-primary: oklch(0.79 0.11 302); @@ -1013,14 +1014,14 @@ html.accent-mauve.dark { html.accent-raspberry { --tint-h: 344; - --primary: oklch(0.56 0.23 344); + --accent-primary: oklch(0.56 0.23 344); --primary-foreground: oklch(0.985 0 0); --ring: oklch(0.56 0.23 344); --sidebar-primary: oklch(0.56 0.23 344); --sidebar-primary-foreground: oklch(0.985 0 0); } html.accent-raspberry.dark { - --primary: oklch(0.74 0.19 344); + --accent-primary: oklch(0.74 0.19 344); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.74 0.19 344); --sidebar-primary: oklch(0.74 0.19 344); @@ -1029,14 +1030,14 @@ html.accent-raspberry.dark { html.accent-rose-quartz { --tint-h: 344; - --primary: oklch(0.68 0.115 344); + --accent-primary: oklch(0.68 0.115 344); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.68 0.115 344); --sidebar-primary: oklch(0.68 0.115 344); --sidebar-primary-foreground: oklch(0.145 0 0); } html.accent-rose-quartz.dark { - --primary: oklch(0.8 0.1 344); + --accent-primary: oklch(0.8 0.1 344); --primary-foreground: oklch(0.145 0 0); --ring: oklch(0.8 0.1 344); --sidebar-primary: oklch(0.8 0.1 344); diff --git a/client/src/assets/theme/bridge.css b/client/src/assets/theme/bridge.css index daf165b8c..02e0b6af7 100644 --- a/client/src/assets/theme/bridge.css +++ b/client/src/assets/theme/bridge.css @@ -2,6 +2,7 @@ Maps CSS variables → Tailwind utility classes. ────────────────────────────────────────────────────────────── */ @theme inline { + --radius-xs: calc(var(--radius) - 6px); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); @@ -9,9 +10,9 @@ --radius-2xl: calc(var(--radius) + 8px); --radius-3xl: calc(var(--radius) + 12px); - --font-sans: 'Inter Variable', ui-sans-serif, system-ui, -apple-system, sans-serif; - --font-serif: 'Fraunces Variable', ui-serif, Georgia, serif; - --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; + --font-sans: var(--app-font-sans); + --font-serif: var(--app-font-serif); + --font-mono: var(--app-font-mono); --color-background: var(--background); --color-foreground: var(--foreground); diff --git a/client/src/assets/theme/styles/nostalgia.css b/client/src/assets/theme/styles/nostalgia.css new file mode 100644 index 000000000..79abbc92b --- /dev/null +++ b/client/src/assets/theme/styles/nostalgia.css @@ -0,0 +1,119 @@ +/* ── Nostalgia style ───────────────────────────────────────── + Neutral grounds from the surface palette, system type, a flat + shell and a tall accent glow over a faint grid of squares. + Only reassigns tokens, so components never ask which style is + active. Accent, radius and background stay the user's: the + accent is only re-shaded (from --accent-primary), never replaced. + + Same specificity as the accent classes and imported after them, + so where both set a token (--sidebar) the style wins. +────────────────────────────────────────────────────────────── */ +html[data-style='nostalgia'] { + --app-font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; + --app-font-serif: var(--app-font-sans); + + --background: var(--surface-ground); + --foreground: oklch(0.2 calc(var(--surface-c) * 0.6) var(--surface-h)); + --card: var(--surface-card); + --card-foreground: var(--foreground); + --popover: var(--card); + --popover-foreground: var(--foreground); + --secondary: oklch(calc(0.94 + var(--surface-l) * 0.4) calc(var(--surface-c) * 0.6) var(--surface-h)); + --secondary-foreground: var(--foreground); + --muted: oklch(calc(0.94 + var(--surface-l) * 0.4) calc(var(--surface-c) * 0.6) var(--surface-h)); + --muted-foreground: oklch(0.5 calc(var(--surface-c) * 0.6) var(--surface-h)); + --accent: oklch(calc(0.94 + var(--surface-l) * 0.4) calc(var(--surface-c) * 0.6) var(--surface-h)); + --accent-foreground: var(--foreground); + --border: var(--surface-edge); + --input: oklch(calc(0.85 + var(--surface-l) * 0.4) calc(var(--surface-c) * 1.2) var(--surface-h)); + + --surface-1: var(--card); + --surface-2: oklch(calc(0.965 + var(--surface-l) * 0.4) calc(var(--surface-c) * 0.6) var(--surface-h)); + --surface-3: var(--muted); + --surface-4: oklch(calc(0.915 + var(--surface-l) * 0.4) calc(var(--surface-c) * 0.6) var(--surface-h)); + --volume-unread: oklch(calc(0.86 + var(--surface-l) * 0.4) calc(var(--surface-c) * 0.8) var(--surface-h)); + --dot-color: oklch(calc(0.83 + var(--surface-l) * 0.4) calc(var(--surface-c) * 0.8) var(--surface-h)); + --scrollbar-thumb: oklch(0.8 calc(var(--surface-c) * 0.8) var(--surface-h)); + --scrollbar-thumb-hover: oklch(0.7 calc(var(--surface-c) * 0.8) var(--surface-h)); + + --sidebar: var(--card); + --sidebar-foreground: var(--foreground); + --sidebar-accent: var(--muted); + --sidebar-accent-foreground: var(--foreground); + --sidebar-border: var(--border); + /* Book counts wear the accent hue, held dark enough to clear AA on the sidebar card. */ + --sidebar-count-foreground: oklch(from var(--primary) min(l, 0.5) c h); + --sidebar-heading-foreground: var(--foreground); + --sidebar-heading-size: 12px; + --sidebar-heading-weight: 700; + --sidebar-heading-tracking: 0.06em; + --sidebar-count-bg: transparent; + --sidebar-count-size: 14px; + --sidebar-count-weight: 400; + /* The classic active row is a faint wash, so the accent text and count on it keep their full contrast. */ + --sidebar-active-bg: color-mix(in oklch, var(--primary) 7%, transparent); + --sidebar-active-weight: 700; + + --shell-border: var(--border); + --shell-backdrop: none; + --shell-shadow: 0 3px 5px oklch(0 0 0 / 0.02), 0 0 2px oklch(0 0 0 / 0.05), 0 1px 4px oklch(0 0 0 / 0.08); + --shell-accent-wash: color-mix(in oklch, var(--primary) 8%, transparent); + --shell-accent-tint: color-mix(in oklch, var(--primary) 16%, transparent); + + --glow-image: radial-gradient(50% 50% at center -25px, color-mix(in oklch, var(--primary) 38%, transparent) 0%, transparent 100%); + --glow-band: 20rem; + --glow-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 84' fill='%23000'%3E%3Crect x='1' y='1' width='19' height='19' fill-opacity='0.044'/%3E%3Crect x='22' y='1' width='19' height='19' fill-opacity='0.058'/%3E%3Crect x='43' y='1' width='19' height='19' fill-opacity='0.042'/%3E%3Crect x='64' y='1' width='19' height='19' fill-opacity='0.035'/%3E%3Crect x='1' y='22' width='19' height='19' fill-opacity='0.059'/%3E%3Crect x='22' y='22' width='19' height='19' fill-opacity='0.02'/%3E%3Crect x='43' y='22' width='19' height='19' fill-opacity='0.054'/%3E%3Crect x='64' y='22' width='19' height='19' fill-opacity='0.03'/%3E%3Crect x='1' y='43' width='19' height='19' fill-opacity='0.024'/%3E%3Crect x='22' y='43' width='19' height='19' fill-opacity='0.023'/%3E%3Crect x='43' y='43' width='19' height='19' fill-opacity='0.031'/%3E%3Crect x='64' y='43' width='19' height='19' fill-opacity='0.052'/%3E%3Crect x='1' y='64' width='19' height='19' fill-opacity='0.026'/%3E%3Crect x='22' y='64' width='19' height='19' fill-opacity='0.042'/%3E%3Crect x='43' y='64' width='19' height='19' fill-opacity='0.045'/%3E%3Crect x='64' y='64' width='19' height='19' fill-opacity='0.034'/%3E%3C/svg%3E"); + --glow-texture-size: 107px 107px; + + --cover-hover-transform: translateY(-3px); + + --format-chip-fill: 18%; + --format-chip-border: 55%; + + --chip-genre-bg: oklch(0.6 0.2 25 / 0.12); + --chip-genre-fg: oklch(0.5 0.17 25); + --chip-genre-border: oklch(0.6 0.2 25 / 0.35); + --chip-tag-bg: oklch(0.65 0.17 150 / 0.12); + --chip-tag-fg: oklch(0.47 0.13 150); + --chip-tag-border: oklch(0.65 0.17 150 / 0.35); +} + +/* The classic ash ladder, shifted by the palette's --surface-l. Ground, card and edge come from surfaces.css. The style pins surface brightness to 0, so + --bg-lift adds nothing here. Light mode takes 40% of the shift: enough to tell greys apart, not to dim a page. */ +html[data-style='nostalgia'].dark { + /* Accents at their classic 400-shade brightness: the darker ones (greens, lime) lift, the rest already sit there. */ + --primary: oklch(from var(--accent-primary) max(l, 0.78) c h); + + --foreground: oklch(1 0 0); + --secondary: oklch(calc(0.307 + var(--bg-lift) + var(--surface-l)) var(--surface-c) var(--surface-h)); + --muted: oklch(calc(0.307 + var(--bg-lift) + var(--surface-l)) var(--surface-c) var(--surface-h)); + --muted-foreground: oklch(0.76 calc(var(--surface-c) * 0.6) var(--surface-h)); + --accent: oklch(calc(0.307 + var(--bg-lift) + var(--surface-l)) var(--surface-c) var(--surface-h)); + --input: oklch(calc(0.432 + var(--bg-lift) + var(--surface-l)) calc(var(--surface-c) * 1.4) var(--surface-h)); + + --surface-2: oklch(calc(0.262 + var(--bg-lift) + var(--surface-l)) var(--surface-c) var(--surface-h)); + --surface-4: oklch(calc(0.362 + var(--bg-lift) + var(--surface-l)) var(--surface-c) var(--surface-h)); + --volume-unread: oklch(calc(0.362 + var(--bg-lift) + var(--surface-l)) var(--surface-c) var(--surface-h)); + --dot-color: oklch(calc(0.302 + var(--bg-lift) + var(--surface-l)) var(--surface-c) var(--surface-h)); + --scrollbar-thumb: oklch(0.38 var(--surface-c) var(--surface-h)); + --scrollbar-thumb-hover: oklch(0.48 var(--surface-c) var(--surface-h)); + + --sidebar-count-foreground: oklch(from var(--primary) max(l, 0.72) c h); + + --glow-image: radial-gradient(50% 50% at center -25px, color-mix(in oklch, var(--primary) 60%, transparent) 0%, transparent 100%); + --glow-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 84' fill='%23fff'%3E%3Crect x='1' y='1' width='19' height='19' fill-opacity='0.056'/%3E%3Crect x='22' y='1' width='19' height='19' fill-opacity='0.042'/%3E%3Crect x='43' y='1' width='19' height='19' fill-opacity='0.082'/%3E%3Crect x='64' y='1' width='19' height='19' fill-opacity='0.036'/%3E%3Crect x='1' y='22' width='19' height='19' fill-opacity='0.073'/%3E%3Crect x='22' y='22' width='19' height='19' fill-opacity='0.059'/%3E%3Crect x='43' y='22' width='19' height='19' fill-opacity='0.035'/%3E%3Crect x='64' y='22' width='19' height='19' fill-opacity='0.071'/%3E%3Crect x='1' y='43' width='19' height='19' fill-opacity='0.033'/%3E%3Crect x='22' y='43' width='19' height='19' fill-opacity='0.065'/%3E%3Crect x='43' y='43' width='19' height='19' fill-opacity='0.036'/%3E%3Crect x='64' y='43' width='19' height='19' fill-opacity='0.037'/%3E%3Crect x='1' y='64' width='19' height='19' fill-opacity='0.064'/%3E%3Crect x='22' y='64' width='19' height='19' fill-opacity='0.096'/%3E%3Crect x='43' y='64' width='19' height='19' fill-opacity='0.04'/%3E%3Crect x='64' y='64' width='19' height='19' fill-opacity='0.048'/%3E%3C/svg%3E"); + + /* Solid chips in a deep shade of each format's hue with white ink, as the classic look wore them; at this + lightness every format clears 5.4:1. */ + --format-chip-fill: 100%; + --format-chip-border: 100%; + --format-chip-fill-l: 0.5; + --format-chip-ink: oklch(1 0 0); + + --chip-genre-bg: oklch(0.64 0.2 25 / 0.18); + --chip-genre-fg: oklch(0.82 0.1 25); + --chip-genre-border: oklch(0.64 0.2 25 / 0.35); + --chip-tag-bg: oklch(0.72 0.17 150 / 0.18); + --chip-tag-fg: oklch(0.84 0.12 150); + --chip-tag-border: oklch(0.72 0.17 150 / 0.35); +} diff --git a/client/src/assets/theme/surfaces.css b/client/src/assets/theme/surfaces.css new file mode 100644 index 000000000..b19143d8f --- /dev/null +++ b/client/src/assets/theme/surfaces.css @@ -0,0 +1,128 @@ +/* ── Surface palettes ──────────────────────────────────────── + Neutral grounds for styles that declare a surface palette + control. A palette is a hue, a chroma and a small lightness + offset on the style's ladder (darker for onyx, lighter for + charcoal), bounded so every palette keeps the ladder's AA + contrast. Applied as data-surface on , or on a swatch + to preview a palette in place. +────────────────────────────────────────────────────────────── */ +[data-surface='onyx'] { + --surface-h: 0; + --surface-c: 0; + --surface-l: -0.06; +} +[data-surface='carbon'] { + --surface-h: 248; + --surface-c: 0.005; + --surface-l: -0.035; +} +[data-surface='neutral'] { + --surface-h: 0; + --surface-c: 0; + --surface-l: -0.015; +} +[data-surface='zinc'] { + --surface-h: 286; + --surface-c: 0.006; + --surface-l: -0.025; +} +[data-surface='iron'] { + --surface-h: 248; + --surface-c: 0.007; + --surface-l: 0; +} +[data-surface='steel'] { + --surface-h: 258; + --surface-c: 0.01; + --surface-l: -0.005; +} +[data-surface='ash'] { + --surface-h: 245; + --surface-c: 0.012; + --surface-l: 0; +} +[data-surface='smoke'] { + --surface-h: 270; + --surface-c: 0.006; + --surface-l: 0.005; +} +[data-surface='charcoal'] { + --surface-h: 0; + --surface-c: 0; + --surface-l: 0.025; +} +[data-surface='slate'] { + --surface-h: 262; + --surface-c: 0.03; + --surface-l: -0.038; +} +[data-surface='midnight-blue'] { + --surface-h: 252; + --surface-c: 0.022; + --surface-l: 0.027; +} +[data-surface='ocean'] { + --surface-h: 228; + --surface-c: 0.034; + --surface-l: 0.038; +} +[data-surface='pine'] { + --surface-h: 165; + --surface-c: 0.022; + --surface-l: 0; +} +[data-surface='olive'] { + --surface-h: 135; + --surface-c: 0.018; + --surface-l: 0.03; +} +[data-surface='stone'] { + --surface-h: 58; + --surface-c: 0.009; + --surface-l: -0.02; +} +[data-surface='sepia'] { + --surface-h: 70; + --surface-c: 0.024; + --surface-l: 0; +} +[data-surface='crimson'] { + --surface-h: 18; + --surface-c: 0.016; + --surface-l: 0.018; +} +[data-surface='plum'] { + --surface-h: 330; + --surface-c: 0.02; + --surface-l: 0; +} + +/* The ground, card and edge of the ladder, in the active mode, for whichever palette the element carries. The + Nostalgia style paints its surfaces from these, and a picker tile previews a palette with the very same values. */ +[data-surface] { + --surface-ground: oklch(calc(0.955 + var(--surface-l) * 0.4) calc(var(--surface-c) * 0.6) var(--surface-h)); + --surface-card: oklch(calc(0.99 + var(--surface-l) * 0.4) calc(var(--surface-c) * 0.3) var(--surface-h)); + --surface-edge: oklch(calc(0.87 + var(--surface-l) * 0.4) calc(var(--surface-c) * 1.2) var(--surface-h)); +} +.dark[data-surface], +.dark [data-surface] { + --surface-ground: oklch(calc(0.167 + var(--bg-lift) + var(--surface-l)) var(--surface-c) var(--surface-h)); + --surface-card: oklch(calc(0.232 + var(--bg-lift) + var(--surface-l)) var(--surface-c) var(--surface-h)); + --surface-edge: oklch(calc(0.402 + var(--bg-lift) + var(--surface-l)) calc(var(--surface-c) * 1.4) var(--surface-h)); +} + +/* Picker tile: a small window, ground behind a card panel. */ +.surface-tile { + position: relative; + overflow: hidden; + background-color: var(--surface-ground); + border: 1px solid var(--surface-edge); +} +.surface-tile::before { + content: ''; + position: absolute; + inset: 34% -1px -1px 26%; + border: 1px solid var(--surface-edge); + border-top-left-radius: 4px; + background-color: var(--surface-card); +} diff --git a/client/src/assets/theme/tokens.css b/client/src/assets/theme/tokens.css index 09674cd08..bed8aa13f 100644 --- a/client/src/assets/theme/tokens.css +++ b/client/src/assets/theme/tokens.css @@ -25,7 +25,9 @@ --popover: oklch(0.975 var(--tint-c-surface) var(--tint-h)); --popover-foreground: oklch(0.145 var(--tint-c-surface) var(--tint-h)); - --primary: oklch(0.21 var(--tint-c-surface) var(--tint-h)); + /* The accent class sets --accent-primary; a style may derive --primary from it instead. */ + --accent-primary: oklch(0.21 var(--tint-c-surface) var(--tint-h)); + --primary: var(--accent-primary); --primary-foreground: oklch(0.985 0.004 var(--tint-h)); --secondary: oklch(0.955 var(--tint-c-surface) var(--tint-h)); @@ -39,12 +41,6 @@ --destructive: oklch(0.577 0.245 27.325); --destructive-foreground: oklch(0.985 0 0); - --success: oklch(0.52 0.16 150); - --success-foreground: oklch(0.985 0 0); - --warning: oklch(0.57 0.15 75); - --warning-foreground: oklch(0.145 0 0); - --info: oklch(0.52 0.17 245); - --info-foreground: oklch(0.985 0 0); --success: oklch(0.52 0.14 148); --success-foreground: oklch(0.985 0 0); @@ -56,6 +52,7 @@ /* In flight: neither finished nor broken. Tuned per theme for the same reason. */ --info: oklch(0.55 0.12 255); + --info-foreground: oklch(0.985 0 0); --star-highlight: oklch(0.76 0.18 90); @@ -129,6 +126,16 @@ /* Shape */ --radius: 0.625rem; + /* The default surface palette (steel, as in surfaces.css), so a ladder that reads these is valid before data-surface lands. */ + --surface-h: 258; + --surface-c: 0.01; + --surface-l: -0.005; + + /* Type. The bridge maps the Tailwind font utilities onto these, so a style can swap them at runtime. */ + --app-font-sans: 'Inter Variable', ui-sans-serif, system-ui, -apple-system, sans-serif; + --app-font-serif: 'Fraunces Variable', ui-serif, Georgia, serif; + --app-font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; + /* App shell (sidebar + header): one card gap, one material. --shell-surface-opacity is user-controlled and overwritten on . */ --shell-gap: 0.75rem; @@ -140,6 +147,43 @@ --shell-accent-tint: color-mix(in oklch, var(--primary) 12%, transparent); --shell-accent-line: color-mix(in oklch, var(--primary) 30%, transparent); --shell-border: var(--shell-accent-line); + --shell-backdrop: blur(24px) saturate(150%); + --shell-shadow: var(--elevation-lg); + + /* Accent glow behind the top of the shell (.glow-wrapper). --glow-band is the style's own reach and + --glow-reach the user's multiplier on it, written on . A texture keeps its tile size at any reach; + only the fade that reveals it follows the band. */ + --glow-reach: 0.8; + --glow-band: 12rem; + --glow-image: radial-gradient(50% 50% at center -25px, color-mix(in oklch, var(--primary) 35%, transparent) 0%, transparent 100%); + --glow-texture: none; + --glow-texture-size: auto; + + /* How a grid cover answers the pointer. */ + --cover-hover-transform: scale(1.02); + + /* Sidebar section labels, counts and the active navigation row. */ + --sidebar-heading-foreground: var(--muted-foreground); + --sidebar-heading-size: 11px; + --sidebar-heading-weight: 500; + --sidebar-heading-tracking: 0.14em; + --sidebar-count-bg: var(--shell-accent-tint); + --sidebar-count-size: 11px; + --sidebar-count-weight: 600; + --sidebar-active-bg: var(--shell-accent-tint); + --sidebar-active-weight: 500; + + /* How strongly a format chip wears its format colour (see formatChipStyle). */ + --format-chip-fill: 10%; + --format-chip-border: 40%; + + /* Book detail metadata chips. Tag ink is tuned like --warning so it clears AA on a light card. */ + --chip-genre-bg: var(--muted); + --chip-genre-fg: var(--foreground); + --chip-genre-border: transparent; + --chip-tag-bg: oklch(0.769 0.188 70.08 / 0.1); + --chip-tag-fg: oklch(0.52 0.13 68); + --chip-tag-border: oklch(0.769 0.188 70.08 / 0.3); --dot-color: oklch(0.82 var(--tint-c-surface) var(--tint-h)); @@ -250,31 +294,6 @@ --audit-collections: oklch(0.55 0.09 255); --audit-integrations: oklch(0.58 0.15 150); --audit-settings: oklch(0.55 0.15 25); - - /* File format, worn as a chip whose own text carries the colour. Tuned per theme for the same - reason every other text colour is: one set of hex values for both themes left the brightest - (yellow on white, around 1.9:1) unreadable in light mode and the darkest invisible in dark. */ - --pill-format-default: oklch(0.55 0.02 260); - --pill-format-epub: oklch(0.55 0.14 150); - --pill-format-kepub: oklch(0.55 0.12 178); - --pill-format-mobi: oklch(0.55 0.18 275); - --pill-format-azw3: oklch(0.53 0.11 185); - --pill-format-azw: oklch(0.55 0.14 235); - --pill-format-fb2: oklch(0.55 0.17 355); - --pill-format-djvu: oklch(0.55 0.04 255); - --pill-format-txt: oklch(0.55 0.03 60); - --pill-format-pdf: oklch(0.55 0.17 25); - --pill-format-cbz: oklch(0.55 0.16 260); - --pill-format-cbr: oklch(0.58 0.15 55); - --pill-format-cb7: oklch(0.55 0.19 300); - --pill-format-cbx: oklch(0.55 0.17 325); - --pill-format-m4b: oklch(0.58 0.13 75); - --pill-format-m4a: oklch(0.58 0.13 95); - --pill-format-mp3: oklch(0.56 0.15 145); - --pill-format-opus: oklch(0.55 0.11 205); - --pill-format-ogg: oklch(0.56 0.15 130); - --pill-format-flac: oklch(0.55 0.13 165); - --pill-format-readalong: oklch(0.52 0.1 186); } @media (min-width: 40rem) { @@ -304,7 +323,7 @@ --popover: oklch(calc(0.18 + var(--bg-lift)) var(--tint-c-surface) var(--tint-h)); --popover-foreground: oklch(0.985 0.001 var(--tint-h)); - --primary: oklch(0.91 var(--tint-c-surface) var(--tint-h)); + --accent-primary: oklch(0.91 var(--tint-c-surface) var(--tint-h)); --primary-foreground: oklch(0.18 var(--tint-c-surface) var(--tint-h)); --secondary: oklch(calc(0.245 + var(--bg-lift)) var(--tint-c-surface) var(--tint-h)); @@ -318,12 +337,6 @@ --destructive: oklch(0.704 0.191 22.216); --destructive-foreground: oklch(0.985 0 0); - --success: oklch(0.74 0.15 150); - --success-foreground: oklch(0.145 0 0); - --warning: oklch(0.78 0.14 75); - --warning-foreground: oklch(0.145 0 0); - --info: oklch(0.75 0.14 245); - --info-foreground: oklch(0.145 0 0); --success: oklch(0.72 0.15 148); --success-foreground: oklch(0.145 0 0); @@ -332,6 +345,7 @@ --warning-foreground: oklch(0.145 0 0); --info: oklch(0.72 0.12 255); + --info-foreground: oklch(0.145 0 0); --star-highlight: oklch(0.84 0.17 95); @@ -390,6 +404,8 @@ --scrollbar-thumb: oklch(0.35 var(--tint-c) var(--tint-h)); --scrollbar-thumb-hover: oklch(0.45 var(--tint-c) var(--tint-h)); + --chip-tag-fg: oklch(0.828 0.189 84.429); + /* Lifted lightness so pills read on dark surfaces */ --pill-web: oklch(0.72 0.14 245); --pill-ios: oklch(0.75 0.14 150); @@ -474,30 +490,4 @@ --audit-collections: oklch(0.72 0.09 255); --audit-integrations: oklch(0.75 0.13 150); --audit-settings: oklch(0.72 0.15 25); - - /* File format, worn as a chip whose own text carries the colour. Tuned per theme for the same - reason every other text colour is: one set of hex values for both themes left the brightest - (yellow on white, around 1.9:1) unreadable in light mode and the darkest invisible in dark. */ - - --pill-format-default: oklch(0.75 0.02 260); - --pill-format-epub: oklch(0.75 0.13 150); - --pill-format-kepub: oklch(0.75 0.11 178); - --pill-format-mobi: oklch(0.72 0.16 275); - --pill-format-azw3: oklch(0.76 0.11 185); - --pill-format-azw: oklch(0.72 0.13 235); - --pill-format-fb2: oklch(0.73 0.15 355); - --pill-format-djvu: oklch(0.75 0.04 255); - --pill-format-txt: oklch(0.76 0.03 60); - --pill-format-pdf: oklch(0.72 0.15 25); - --pill-format-cbz: oklch(0.72 0.14 260); - --pill-format-cbr: oklch(0.76 0.13 55); - --pill-format-cb7: oklch(0.72 0.17 300); - --pill-format-cbx: oklch(0.73 0.15 325); - --pill-format-m4b: oklch(0.79 0.13 75); - --pill-format-m4a: oklch(0.8 0.13 95); - --pill-format-mp3: oklch(0.78 0.15 145); - --pill-format-opus: oklch(0.76 0.11 205); - --pill-format-ogg: oklch(0.78 0.15 130); - --pill-format-flac: oklch(0.75 0.13 165); - --pill-format-readalong: oklch(0.78 0.11 186); } diff --git a/client/src/components/AppHeader.vue b/client/src/components/AppHeader.vue index ad913d0cf..979c45859 100644 --- a/client/src/components/AppHeader.vue +++ b/client/src/components/AppHeader.vue @@ -42,7 +42,10 @@ import RadiusPicker from '@/components/RadiusPicker.vue' import BackgroundPicker from '@/components/BackgroundPicker.vue' import ThemePicker from '@/components/ThemePicker.vue' import SurfaceBrightnessPicker from '@/components/SurfaceBrightnessPicker.vue' -import SurfacePicker from '@/components/SurfacePicker.vue' +import SurfaceOpacityPicker from '@/components/SurfaceOpacityPicker.vue' +import StylePicker from '@/components/StylePicker.vue' +import SurfacePalettePicker from '@/components/SurfacePalettePicker.vue' +import GlowReachPicker from '@/components/GlowReachPicker.vue' import { useGlobalSearch, type GlobalSearchResult } from '@/features/book/composables/useGlobalSearch' import BookCoverImage from '@/features/book/components/BookCoverImage.vue' import { useAuth } from '@/features/auth/composables/useAuth' @@ -71,6 +74,8 @@ const { onLibraryUploadCompleted } = useLibraryUploadEvents() const { subscribe: subscribeNotifications } = useNotifications() const { hasUnseen: hasUnseenWhatsNew } = useWhatsNew() const themeStore = useThemeStore() +const showSurfaceOpacity = computed(() => themeStore.appearanceControls.surfaceOpacity) +const showSurfaceBrightness = computed(() => themeStore.resolvedTheme === 'dark' && themeStore.appearanceControls.brightness) const localeStore = useLocaleStore() const { openLegalNotices } = useLegalNotices() const currentLanguageLabel = computed(() => LOCALE_LABELS[localeStore.locale]) @@ -343,7 +348,7 @@ function resultFormatEntries(result: GlobalSearchResult) { @@ -397,7 +402,7 @@ function resultFormatEntries(result: GlobalSearchResult) { :book-id="row.result.id" type="thumbnail" :version="row.result.coverVersion" - class="h-16 w-12 object-cover rounded shrink-0 bg-muted" + class="h-16 w-12 object-cover rounded-xs shrink-0 bg-muted" :alt="row.result.title ?? ''" /> @@ -427,7 +432,7 @@ function resultFormatEntries(result: GlobalSearchResult) { v-for="entry in resultFormatEntries(row.result)" :key="entry.key" :format-key="entry.key" - class="gap-0.5 text-[11px] px-1 py-0.5 rounded" + class="gap-0.5 text-[11px] px-1 py-0.5 rounded-xs" />@@ -481,7 +486,7 @@ function resultFormatEntries(result: GlobalSearchResult) { ⌘ K @@ -522,7 +527,7 @@ function resultFormatEntries(result: GlobalSearchResult) { :book-id="row.result.id" type="thumbnail" :version="row.result.coverVersion" - class="h-16 w-12 object-cover rounded shrink-0 bg-muted" + class="h-16 w-12 object-cover rounded-xs shrink-0 bg-muted" :alt="row.result.title ?? ''" />@@ -552,7 +557,7 @@ function resultFormatEntries(result: GlobalSearchResult) { v-for="entry in resultFormatEntries(row.result)" :key="entry.key" :format-key="entry.key" - class="gap-0.5 text-[11px] px-1 py-0.5 rounded" + class="gap-0.5 text-[11px] px-1 py-0.5 rounded-xs" />@@ -773,6 +778,18 @@ function resultFormatEntries(result: GlobalSearchResult) {{{ t('components.appHeader.appearance') }}
++ {{ t('components.appHeader.style') }} +++ + {{ t('components.appHeader.surfacePalette') }} +++ + {{ t('components.appHeader.topGlow') }} ++ {{ t('components.appHeader.theme') }}-@@ -785,15 +802,15 @@ function resultFormatEntries(result: GlobalSearchResult) { {{ t('components.appHeader.radius') }} +{{ t('components.appHeader.surfaceOpacity') }} --+ +{{ t('settings.appearance.theme.surfaceBrightness.label') }}- +{{ t('components.appHeader.background') }}@@ -892,6 +909,21 @@ function resultFormatEntries(result: GlobalSearchResult) {{{ t('components.appHeader.appearanceDescription') }} +diff --git a/client/src/features/book-dock/components/BookDockCoverPeek.vue b/client/src/features/book-dock/components/BookDockCoverPeek.vue index 5199e7142..2171d929a 100644 --- a/client/src/features/book-dock/components/BookDockCoverPeek.vue +++ b/client/src/features/book-dock/components/BookDockCoverPeek.vue @@ -60,7 +60,7 @@ function onCoverLoad(event: Event) {+ +++{{ t('components.appHeader.style') }}
++ ++{{ t('components.appHeader.surfacePalette') }}
++ ++{{ t('components.appHeader.topGlow') }}
++ - {{ t('components.appHeader.theme') }}
@@ -907,18 +939,18 @@ function resultFormatEntries(result: GlobalSearchResult) { - +diff --git a/client/src/features/book-dock/components/BookDockBulkEditDialog.vue b/client/src/features/book-dock/components/BookDockBulkEditDialog.vue index cb5098b49..07a3e020b 100644 --- a/client/src/features/book-dock/components/BookDockBulkEditDialog.vue +++ b/client/src/features/book-dock/components/BookDockBulkEditDialog.vue @@ -136,7 +136,7 @@ function handleClose() {+ -{{ t('components.appHeader.surfaceOpacity') }}
-+ +-{{ t('settings.appearance.theme.surfaceBrightness.label') }}
+ diff --git a/client/src/components/AppLayout.vue b/client/src/components/AppLayout.vue index eedaee3ba..81bb0269d 100644 --- a/client/src/components/AppLayout.vue +++ b/client/src/components/AppLayout.vue @@ -5,12 +5,12 @@ import { SidebarProvider, SidebarInset } from '@/components/ui/sidebar' import AppSidebar from '@/components/AppSidebar.vue' import AppHeader from '@/components/AppHeader.vue' import BookMetadataFetchWidget from '@/features/book-metadata-fetch/components/BookMetadataFetchWidget.vue' -import { useThemeStore, BACKGROUND_OPTIONS } from '@/stores/theme' +import { useAppStyleScope } from '@/composables/useAppStyleScope' +import { useThemeStore } from '@/stores/theme' const route = useRoute() const themeStore = useThemeStore() - -const backgroundClass = computed(() => BACKGROUND_OPTIONS.find((b) => b.id === themeStore.background)?.cssClass ?? '') +useAppStyleScope() const BOOK_ROUTE_NAMES = new Set(['book-detail']) @@ -50,7 +50,7 @@ const viewKey = computed(() => { - {{ t('components.appHeader.background') }}
+ diff --git a/client/src/components/BackgroundPicker.vue b/client/src/components/BackgroundPicker.vue index 2210e2671..ed9977bd2 100644 --- a/client/src/components/BackgroundPicker.vue +++ b/client/src/components/BackgroundPicker.vue @@ -1,8 +1,10 @@ + + + + + {{ valueLabel }} ++ diff --git a/client/src/components/IconPicker.vue b/client/src/components/IconPicker.vue index cba9f9878..71c7f559e 100644 --- a/client/src/components/IconPicker.vue +++ b/client/src/components/IconPicker.vue @@ -305,7 +305,7 @@ onUnmounted(() => {- +@@ -72,7 +72,7 @@ function onCoverLoad(event: Event) { - - + +diff --git a/client/src/features/book-metadata-fetch/components/AuthorEnrichmentExpanded.vue b/client/src/features/book-metadata-fetch/components/AuthorEnrichmentExpanded.vue index d246da6e6..87a0dec61 100644 --- a/client/src/features/book-metadata-fetch/components/AuthorEnrichmentExpanded.vue +++ b/client/src/features/book-metadata-fetch/components/AuthorEnrichmentExpanded.vue @@ -85,7 +85,7 @@ async function handleCancelConfirm() {- - + +@@ -94,7 +94,7 @@ async function handleCancelConfirm() { @@ -103,7 +103,7 @@ async function handleCancelConfirm() { {{ t('bookMetadataFetch.actions.cancelQueued') }} @@ -118,12 +118,12 @@ async function handleCancelConfirm() { diff --git a/client/src/features/book-metadata-fetch/components/AuthorEnrichmentReportModal.vue b/client/src/features/book-metadata-fetch/components/AuthorEnrichmentReportModal.vue index 874edf86a..943013322 100644 --- a/client/src/features/book-metadata-fetch/components/AuthorEnrichmentReportModal.vue +++ b/client/src/features/book-metadata-fetch/components/AuthorEnrichmentReportModal.vue @@ -87,7 +87,7 @@ onMounted(() => loadPage(1)){{ t('bookMetadataFetch.cancel.confirm') }} -+ {{ t('bookMetadataFetch.cancel.keepRunning') }} {{ t('common.previous') }} @@ -95,7 +95,7 @@ onMounted(() => loadPage(1)) {{ page }} / {{ Math.ceil(data.total / limit) }} {{ t('common.next') }} @@ -103,7 +103,7 @@ onMounted(() => loadPage(1)) diff --git a/client/src/features/book-metadata-fetch/components/BookMetadataFetchExpanded.vue b/client/src/features/book-metadata-fetch/components/BookMetadataFetchExpanded.vue index 296f8a09b..8d75e7a0a 100644 --- a/client/src/features/book-metadata-fetch/components/BookMetadataFetchExpanded.vue +++ b/client/src/features/book-metadata-fetch/components/BookMetadataFetchExpanded.vue @@ -81,7 +81,7 @@ async function handleCancelConfirm() { @@ -90,7 +90,7 @@ async function handleCancelConfirm() { @@ -99,7 +99,7 @@ async function handleCancelConfirm() { {{ t('bookMetadataFetch.actions.cancelQueued') }} @@ -114,12 +114,12 @@ async function handleCancelConfirm() { diff --git a/client/src/features/book-metadata-fetch/components/BookMetadataFetchReportModal.vue b/client/src/features/book-metadata-fetch/components/BookMetadataFetchReportModal.vue index 9dc4bd2af..b17e3b9ab 100644 --- a/client/src/features/book-metadata-fetch/components/BookMetadataFetchReportModal.vue +++ b/client/src/features/book-metadata-fetch/components/BookMetadataFetchReportModal.vue @@ -90,7 +90,7 @@ onMounted(() => loadPage(1)){{ t('bookMetadataFetch.cancel.confirm') }} -+ {{ t('bookMetadataFetch.cancel.keepRunning') }} {{ t('common.previous') }} @@ -98,7 +98,7 @@ onMounted(() => loadPage(1)){{ t('common.next') }} @@ -106,7 +106,7 @@ onMounted(() => loadPage(1)){{ t('bookMetadataFetch.report.retryAllFailed') }} diff --git a/client/src/features/book-metadata-fetch/components/BookMetadataFetchWidget.vue b/client/src/features/book-metadata-fetch/components/BookMetadataFetchWidget.vue index ce9739f1d..8cbe75780 100644 --- a/client/src/features/book-metadata-fetch/components/BookMetadataFetchWidget.vue +++ b/client/src/features/book-metadata-fetch/components/BookMetadataFetchWidget.vue @@ -242,7 +242,7 @@ onMounted(() => { @@ -275,7 +275,7 @@ onMounted(() => { diff --git a/client/src/features/book-requests/BookRequestsPage.vue b/client/src/features/book-requests/BookRequestsPage.vue index 8f15a2a79..2a4d951a2 100644 --- a/client/src/features/book-requests/BookRequestsPage.vue +++ b/client/src/features/book-requests/BookRequestsPage.vue @@ -686,7 +686,7 @@ function goToNextPage() { {{ t('bookRequests.filters.showDismissed') }} diff --git a/client/src/features/book-requests/components/BookMissingFormatRequest.vue b/client/src/features/book-requests/components/BookMissingFormatRequest.vue index 6b2bc1cb1..c61be82c3 100644 --- a/client/src/features/book-requests/components/BookMissingFormatRequest.vue +++ b/client/src/features/book-requests/components/BookMissingFormatRequest.vue @@ -84,14 +84,14 @@ async function handleRequest() { {{ t('bookRequests.missingFormat.open') }} {{ t('bookRequests.missingFormat.viewRequest') }} diff --git a/client/src/features/book-requests/components/ReleasePickerPanel.vue b/client/src/features/book-requests/components/ReleasePickerPanel.vue index 60ec13905..8dbe68162 100644 --- a/client/src/features/book-requests/components/ReleasePickerPanel.vue +++ b/client/src/features/book-requests/components/ReleasePickerPanel.vue @@ -1421,7 +1421,7 @@ function showEverySource(): void {@@ -1452,7 +1452,7 @@ function showEverySource(): void { {{ reasonText(reason) }} {{ reasonPoints(reason.points) }} diff --git a/client/src/features/book-requests/components/RequestCover.vue b/client/src/features/book-requests/components/RequestCover.vue index 8339fe8ee..8798fc0f1 100644 --- a/client/src/features/book-requests/components/RequestCover.vue +++ b/client/src/features/book-requests/components/RequestCover.vue @@ -61,12 +61,12 @@ function handleError() { v-if="showImage" :src="currentSrc ?? undefined" alt="" - class="shrink-0 rounded border border-border bg-muted object-cover" + class="shrink-0 rounded-xs border border-border bg-muted object-cover" :class="props.class" loading="lazy" @error="handleError" /> - {{ t('bookRequests.list.loading') }} diff --git a/client/src/features/book-requests/components/RequestListTable.vue b/client/src/features/book-requests/components/RequestListTable.vue index 9bd0d3c3a..f30edc035 100644 --- a/client/src/features/book-requests/components/RequestListTable.vue +++ b/client/src/features/book-requests/components/RequestListTable.vue @@ -291,7 +291,7 @@ function rowDismissed(row: BookRequestItem): boolean {{{ t('bookRequests.table.title') }} @@ -313,7 +313,7 @@ function rowDismissed(row: BookRequestItem): boolean { {{ t('bookRequests.table.mediaKind') }} @@ -324,7 +324,7 @@ function rowDismissed(row: BookRequestItem): boolean { {{ t('bookRequests.table.requestedBy') }} @@ -335,7 +335,7 @@ function rowDismissed(row: BookRequestItem): boolean { {{ t('bookRequests.table.age') }} @@ -346,7 +346,7 @@ function rowDismissed(row: BookRequestItem): boolean { {{ t('bookRequests.table.state') }} @@ -375,7 +375,7 @@ function rowDismissed(row: BookRequestItem): boolean { {{ t('bookRequests.seed.removeHint') }} diff --git a/client/src/features/book/components/BookCoverCard.vue b/client/src/features/book/components/BookCoverCard.vue index b9c9a1ef4..68ecc248e 100644 --- a/client/src/features/book/components/BookCoverCard.vue +++ b/client/src/features/book/components/BookCoverCard.vue @@ -423,7 +423,7 @@ const secondaryLabelText = computed(() => resolveBookLabel(gridCardSecondaryLabe @@ -523,7 +523,7 @@ const secondaryLabelText = computed(() => resolveBookLabel(gridCardSecondaryLabe :class="selected ? 'bg-primary/20 ring-2 ring-inset ring-primary' : ''" >resolveBookLabel(gridCardSecondaryLabe @@ -532,7 +532,9 @@ const secondaryLabelText = computed(() => resolveBookLabel(gridCardSecondaryLabe - +{{ t('book.card.missing') }} @@ -763,7 +765,7 @@ const secondaryLabelText = computed(() => resolveBookLabel(gridCardSecondaryLabe - diff --git a/client/src/features/book/components/BookDownloadButton.vue b/client/src/features/book/components/BookDownloadButton.vue index 47c9604ac..58ee370a9 100644 --- a/client/src/features/book/components/BookDownloadButton.vue +++ b/client/src/features/book/components/BookDownloadButton.vue @@ -102,27 +102,27 @@ function handleExportPrimary() {+ - + {{ t('book.download.audiobookZip') }} - + {{ formatFileSize(file.sizeBytes) }} - @@ -133,21 +133,21 @@ function handleExportPrimary() {+ {{ t('book.download.audiolessEpub') }} {{ t('book.download.noAudio') }} - + {{ formatFileSize(file.sizeBytes) }} {{ t('book.file.primary') }} - + {{ t('book.download.audiolessEpub') }} {{ t('book.download.noAudio') }} {{ @@ -156,14 +156,14 @@ function handleExportPrimary() { {{ t('book.download.allFormatsZip') }} diff --git a/client/src/features/book/components/BookFilterBuilder.vue b/client/src/features/book/components/BookFilterBuilder.vue index 60c844864..79d65baa0 100644 --- a/client/src/features/book/components/BookFilterBuilder.vue +++ b/client/src/features/book/components/BookFilterBuilder.vue @@ -524,7 +524,7 @@ function showValueToInput(operator: RuleOperator): boolean { {{ libraryName }} @@ -562,7 +562,7 @@ function showValueToInput(operator: RuleOperator): boolean { {{ READ_STATUS_LABELS[status] ?? status }} diff --git a/client/src/features/book/components/BookListRow.vue b/client/src/features/book/components/BookListRow.vue index acdef3af0..5f25300b5 100644 --- a/client/src/features/book/components/BookListRow.vue +++ b/client/src/features/book/components/BookListRow.vue @@ -305,7 +305,7 @@ function handleRowClick(event: MouseEvent) { @@ -314,7 +314,7 @@ function handleRowClick(event: MouseEvent) { {{ t('book.card.missing') }} @@ -382,7 +382,7 @@ function handleRowClick(event: MouseEvent) { - diff --git a/client/src/features/book/components/BookQuickView.vue b/client/src/features/book/components/BookQuickView.vue index 44468227f..29c401b14 100644 --- a/client/src/features/book/components/BookQuickView.vue +++ b/client/src/features/book/components/BookQuickView.vue @@ -180,7 +180,7 @@ function handleDelete() {+ -+ @@ -192,7 +192,7 @@ function handleDelete() {
-- - + + + - + @@ -281,17 +281,17 @@ function handleDelete() { :key="entry.key" :format-key="entry.key" :primary="entry.primary" - class="rounded px-2 py-0.5 text-[10px] tracking-wider" + class="rounded-xs px-2 py-0.5 text-[10px] tracking-wider" /> - + {{ t('book.quickView.pages', { count: detail.pageCount }) }} - + {{ publishedDisplay }} {{ detail.language }} @@ -309,8 +309,8 @@ function handleDelete() { {{ primaryFile.filename ?? t('book.quickView.fileNumber', { id: primaryFile.id }) }} -diff --git a/client/src/features/book/components/BookSortBuilder.vue b/client/src/features/book/components/BookSortBuilder.vue index e96bcc5cf..092d03224 100644 --- a/client/src/features/book/components/BookSortBuilder.vue +++ b/client/src/features/book/components/BookSortBuilder.vue @@ -96,7 +96,7 @@ const canAddMore = computed(() => props.modelValue.length < allFields.value.leng- + + {{ formatBytes(primaryFile.sizeBytes) }} @@ -108,7 +108,7 @@ const canAddMore = computed(() => props.modelValue.length < allFields.value.leng diff --git a/client/src/features/book/components/BulkEditMetadataDialog.vue b/client/src/features/book/components/BulkEditMetadataDialog.vue index 000dd4f1c..22ff7ca58 100644 --- a/client/src/features/book/components/BulkEditMetadataDialog.vue +++ b/client/src/features/book/components/BulkEditMetadataDialog.vue @@ -228,7 +228,7 @@ watch( @@ -255,7 +255,7 @@ watch( :key="m" type="button" :class="[ - 'px-2.5 py-1 rounded text-xs font-medium transition-colors', + 'px-2.5 py-1 rounded-xs text-xs font-medium transition-colors', arrayModes[key] === m ? m === 'clear' ? 'bg-destructive text-destructive-foreground' @@ -285,7 +285,7 @@ watch( diff --git a/client/src/features/book/components/CollapsedSeriesCard.vue b/client/src/features/book/components/CollapsedSeriesCard.vue index efb87e78d..2b288be97 100644 --- a/client/src/features/book/components/CollapsedSeriesCard.vue +++ b/client/src/features/book/components/CollapsedSeriesCard.vue @@ -208,7 +208,7 @@ const secondaryLabelText = computed(() => resolveSeriesLabel(gridCardSecondaryLa{{ chip }} diff --git a/client/src/features/book/components/FilterSummary.vue b/client/src/features/book/components/FilterSummary.vue index 5abfc735f..adc80d1d4 100644 --- a/client/src/features/book/components/FilterSummary.vue +++ b/client/src/features/book/components/FilterSummary.vue @@ -14,7 +14,7 @@ defineProps<{ {{ node.join === 'AND' ? t('book.filter.all') : t('book.filter.any') }} diff --git a/client/src/features/book/components/KeyboardShortcutOverlay.vue b/client/src/features/book/components/KeyboardShortcutOverlay.vue index 056d8a642..8dd40eaa2 100644 --- a/client/src/features/book/components/KeyboardShortcutOverlay.vue +++ b/client/src/features/book/components/KeyboardShortcutOverlay.vue @@ -68,7 +68,7 @@ const sections = computed(() => [ {{ key }} diff --git a/client/src/features/book/components/TableColumnPanel.vue b/client/src/features/book/components/TableColumnPanel.vue index 59f7d324f..4b3367798 100644 --- a/client/src/features/book/components/TableColumnPanel.vue +++ b/client/src/features/book/components/TableColumnPanel.vue @@ -148,14 +148,14 @@ function columnLabel(column: ColumnDef & { visible: boolean }): string { {{ t('book.columnPanel.presets') }}@@ -168,14 +168,14 @@ function columnLabel(column: ColumnDef & { visible: boolean }): string { {{ preset.name }} @@ -183,14 +183,14 @@ function columnLabel(column: ColumnDef & { visible: boolean }): string { {{ t('book.columnPanel.rename') }} @@ -198,7 +198,7 @@ function columnLabel(column: ColumnDef & { visible: boolean }): string { {{ t('book.columnPanel.builtIn') }} @@ -223,24 +223,24 @@ function columnLabel(column: ColumnDef & { visible: boolean }): string { {{ t('book.columnPanel.savedViews') }}-+@@ -262,7 +262,7 @@ function columnLabel(column: ColumnDef & { visible: boolean }): string {{{ view.name }} - + - + {{ t('book.columnPanel.rename') }} -+ -