feat: add light/dark mode toggle with header view transitions

Add theme cycling (system/light/dark) with localStorage persistence
and FOUC prevention. Restructure CSS color tokens to respond to
data-theme attribute across all components. Redesign header as a
floating glass pill bar with smooth view transitions including
clip-reveal logo animation.
This commit is contained in:
2026-03-01 16:15:36 +01:00
parent 3942a18b2b
commit fdbbca3942
82 changed files with 2317 additions and 1276 deletions
@@ -198,9 +198,12 @@ dialog h2 {
}
@media (prefers-color-scheme: dark) {
.selector-content {
:global(:root:not([data-theme="light"])) .selector-content {
background-color: var(--nord1);
}
}
:global(:root[data-theme="dark"]) .selector-content {
background-color: var(--nord1);
}
</style>
@@ -115,10 +115,13 @@
}
@media (prefers-color-scheme: dark) {
.filter-label {
:global(:root:not([data-theme="light"])) .filter-label {
color: var(--nord6);
}
}
}
:global(:root[data-theme="dark"]) .filter-label {
color: var(--nord6);
}
@media (max-width: 968px) {
.filter-label {
+5 -18
View File
@@ -101,21 +101,21 @@
font-size: 0.7rem;
padding: 0.1rem 0.4rem;
border-radius: var(--radius-pill);
background-color: var(--nord5);
color: var(--nord3);
background-color: var(--color-bg-elevated);
color: var(--color-text-secondary);
text-decoration: none;
cursor: pointer;
transition: transform var(--transition-fast), background-color var(--transition-fast), box-shadow var(--transition-fast), color var(--transition-fast);
box-shadow: var(--shadow-sm);
box-shadow: none;
border: none;
display: inline-block;
}
.tag:hover,
.tag:focus-visible {
transform: scale(1.05);
background-color: var(--nord8);
background-color: var(--color-primary);
box-shadow: var(--shadow-hover);
color: var(--nord0);
color: var(--color-text-on-primary);
}
@media (min-width: 600px) {
.tag {
@@ -123,19 +123,6 @@
padding: 0.15rem 0.55rem;
}
}
@media (prefers-color-scheme: dark) {
.tag,
.tag:visited,
.tag:link {
background-color: var(--nord0);
color: var(--nord4);
}
.tag:hover,
.tag:focus-visible {
background-color: var(--nord8);
color: var(--nord0);
}
}
.icon {
position: absolute;
top: -1.2em;
@@ -635,13 +635,19 @@ h3{
fill: var(--nord1);
}
@media (prefers-color-scheme: dark){
.button_arrow{
:global(:root:not([data-theme="light"])) .button_arrow {
fill: var(--nord4);
}
.list_wrapper p[contenteditable]{
:global(:root:not([data-theme="light"])) .list_wrapper p[contenteditable] {
background-color: var(--accent-dark);
}
}
:global(:root[data-theme="dark"]) .button_arrow {
fill: var(--nord4);
}
:global(:root[data-theme="dark"]) .list_wrapper p[contenteditable] {
background-color: var(--accent-dark);
}
/* Styling for converted div-to-button elements */
@@ -688,12 +694,18 @@ h3{
}
@media (prefers-color-scheme: dark) {
.reference-container {
:global(:root:not([data-theme="light"])) .reference-container {
background-color: var(--nord1);
}
.reference-badge {
:global(:root:not([data-theme="light"])) .reference-badge {
color: var(--nord6);
}
}
:global(:root[data-theme="dark"]) .reference-container {
background-color: var(--nord1);
}
:global(:root[data-theme="dark"]) .reference-badge {
color: var(--nord6);
}
.insert-base-recipe-button {
@@ -664,20 +664,29 @@ h3{
}
}
@media (prefers-color-scheme: dark){
.additional_info div{
:global(:root:not([data-theme="light"])) .additional_info div {
background-color: var(--accent-dark);
}
.instructions{
:global(:root:not([data-theme="light"])) .instructions {
background-color: var(--nord6-dark);
}
}
:global(:root[data-theme="dark"]) .additional_info div {
background-color: var(--accent-dark);
}
:global(:root[data-theme="dark"]) .instructions {
background-color: var(--nord6-dark);
}
.button_arrow{
fill: var(--nord1);
}
@media (prefers-color-scheme: dark){
.button_arrow{
:global(:root:not([data-theme="light"])) .button_arrow {
fill: var(--nord4);
}
}
:global(:root[data-theme="dark"]) .button_arrow {
fill: var(--nord4);
}
/* Styling for converted div-to-button elements */
@@ -715,12 +724,18 @@ h3{
}
@media (prefers-color-scheme: dark) {
.reference-container {
:global(:root:not([data-theme="light"])) .reference-container {
background-color: var(--nord1);
}
.reference-badge {
:global(:root:not([data-theme="light"])) .reference-badge {
color: var(--nord6);
}
}
:global(:root[data-theme="dark"]) .reference-container {
background-color: var(--nord1);
}
:global(:root[data-theme="dark"]) .reference-badge {
color: var(--nord6);
}
.insert-base-recipe-button {
@@ -50,10 +50,13 @@
}
@media (prefers-color-scheme: dark) {
.filter-label {
:global(:root:not([data-theme="light"])) .filter-label {
color: var(--nord6);
}
}
}
:global(:root[data-theme="dark"]) .filter-label {
color: var(--nord6);
}
@media (max-width: 968px) {
.filter-label {
@@ -50,9 +50,9 @@
.toggle-button {
display: none;
background: transparent;
color: var(--nord3);
color: var(--color-text-secondary);
padding: 0.5rem 0.8rem;
border: 1px solid var(--nord2);
border: 1px solid var(--color-border);
border-radius: 6px;
cursor: pointer;
font-size: 0.85rem;
@@ -66,9 +66,9 @@
}
.toggle-button:hover {
background: var(--nord1);
color: var(--nord4);
border-color: var(--nord3);
background: var(--color-surface);
color: var(--color-text-primary);
border-color: var(--color-border-hover);
}
.arrow {
+5 -2
View File
@@ -13,10 +13,13 @@
box-shadow: 0em 0em 0.5em 0.2em rgba(0, 0, 0, 0.2);
}
@media (prefers-color-scheme: dark) {
a{
:global(:root:not([data-theme="light"])) a {
background-color: var(--accent-dark);
}
}
}
:global(:root[data-theme="dark"]) a {
background-color: var(--accent-dark);
}
a:hover{
--angle: 15deg;
animation: shake 0.5s ease forwards;
+5 -2
View File
@@ -107,10 +107,13 @@
}
@media (prefers-color-scheme: dark) {
.filter-label {
:global(:root:not([data-theme="light"])) .filter-label {
color: var(--nord6);
}
}
}
:global(:root[data-theme="dark"]) .filter-label {
color: var(--nord6);
}
@media (max-width: 968px) {
.filter-label {
@@ -386,11 +386,12 @@ function adjust_amount(string, multiplier){
/* Hover scale override - larger than default */
.multipliers :is(button, form):is(:hover, :focus-within){
scale: 1.2;
background-color: var(--nord8);
background-color: var(--color-primary);
color: var(--color-text-on-primary);
}
.selected{
background-color: var(--nord9) !important;
color: white !important;
background-color: var(--color-primary) !important;
color: var(--color-text-on-primary) !important;
font-weight: bold;
scale: 1.2 !important;
}
@@ -453,8 +454,8 @@ function adjust_amount(string, multiplier){
display: none;
}
.cake-form-selected {
background-color: var(--nord9);
color: white;
background-color: var(--color-primary);
color: var(--color-text-on-primary);
font-weight: bold;
}
.cake-form-inputs {
@@ -467,7 +468,7 @@ function adjust_amount(string, multiplier){
.cake-form-num {
width: 3.5em;
padding: 0.2em 0.4em;
border: 1px solid var(--nord4);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
text-align: center;
font-size: inherit;
@@ -478,7 +479,7 @@ function adjust_amount(string, multiplier){
text-align: center;
margin-top: 0.4rem;
font-weight: bold;
color: var(--nord10);
color: var(--color-primary);
}
</style>
@@ -102,13 +102,13 @@ const labels = $derived({
<style>
ol li::marker{
font-weight: bold;
color: var(--blue);
color: var(--color-primary);
font-size: 1.2rem;
}
.instructions{
flex-basis: 0;
flex-grow: 2;
background-color: var(--nord5);
background-color: var(--color-bg-secondary);
padding-block: 1rem;
padding-inline: 2rem;
}
@@ -129,18 +129,10 @@ ol li::marker{
.additional_info > *{
flex-grow: 0;
padding: 1em;
background-color: #FAFAFE;
background-color: var(--color-bg-tertiary);
box-shadow: var(--shadow-md);
max-width: 30%
}
@media (prefers-color-scheme: dark){
.instructions{
background-color: var(--nord6-dark);
}
.additional_info > *{
background-color: var(--accent-dark);
}
}
@media screen and (max-width: 500px){
.additional_info > *{
max-width: 60%;
@@ -48,10 +48,13 @@
}
@media (prefers-color-scheme: dark) {
.filter-label {
:global(:root:not([data-theme="light"])) .filter-label {
color: var(--nord6);
}
}
}
:global(:root[data-theme="dark"]) .filter-label {
color: var(--nord6);
}
@media (max-width: 968px) {
.filter-label {
@@ -70,10 +73,13 @@
}
@media (prefers-color-scheme: dark) {
.toggle-container {
:global(:root:not([data-theme="light"])) .toggle-container {
color: var(--nord6);
}
}
}
:global(:root[data-theme="dark"]) .toggle-container {
color: var(--nord6);
}
.toggle-switch {
position: relative;
@@ -115,10 +121,13 @@
}
@media (prefers-color-scheme: dark) {
.mode-label.active {
:global(:root:not([data-theme="light"])) .mode-label.active {
color: var(--nord8);
}
}
}
:global(:root[data-theme="dark"]) .mode-label.active {
color: var(--nord8);
}
.toggle-switch.or-mode + .mode-label.or {
color: var(--nord12);
@@ -135,7 +144,8 @@
onclick={() => checked = !checked}
role="button"
tabindex="0"
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); checked = !checked; } }}
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); checked = !checked; } }
}
>
<div class="toggle-knob"></div>
</div>
+5 -5
View File
@@ -307,14 +307,14 @@
input#search {
all: unset;
box-sizing: border-box;
background: var(--nord0);
color: #fff;
background: var(--color-surface);
color: var(--color-text-primary);
padding: 0.7rem 2rem;
border-radius: var(--radius-pill);
width: 100%;
}
input::placeholder{
color: var(--nord6);
color: var(--color-text-muted);
}
.search {
@@ -344,12 +344,12 @@ input::placeholder{
right: 0.5em;
width: 1.5em;
height: 1.5em;
color: var(--nord6);
color: var(--color-text-tertiary);
cursor: pointer;
transition: color 180ms ease-in-out;
}
.search-button:hover {
color: white;
color: var(--color-text-primary);
scale: 1.1 1.1;
}
.search-button:active{
@@ -101,10 +101,13 @@
}
@media (prefers-color-scheme: dark) {
.filter-label {
:global(:root:not([data-theme="light"])) .filter-label {
color: var(--nord6);
}
}
}
:global(:root[data-theme="dark"]) .filter-label {
color: var(--nord6);
}
@media (max-width: 968px) {
.filter-label {
@@ -29,8 +29,8 @@
a.month{
text-decoration: unset;
border-radius: var(--radius-pill);
background-color: var(--blue);
color: var(--nord5);
background-color: var(--color-primary);
color: var(--color-text-on-primary);
padding: 0.5em;
transition: var(--transition-fast);
min-width: 4em;
@@ -40,7 +40,7 @@ a.month:hover,
.active
{
transform: scale(1.1,1.1) !important;
background-color: var(--red) !important;
background-color: var(--color-accent) !important;
}
.months{
display:flex;
+5 -2
View File
@@ -91,10 +91,13 @@
}
@media (prefers-color-scheme: dark) {
.filter-label {
:global(:root:not([data-theme="light"])) .filter-label {
color: var(--nord6);
}
}
}
:global(:root[data-theme="dark"]) .filter-label {
color: var(--nord6);
}
@media (max-width: 968px) {
.filter-label {
@@ -46,9 +46,9 @@
.section {
margin-bottom: -20vh;
margin-top: calc(-3.5rem - 12px);
transform-origin: center top;
transform: translateY(-1rem)
scaleY(calc(1 - var(--scale)));
transform: scaleY(calc(1 - var(--scale)));
}
.section > * {
@@ -67,7 +67,7 @@
align-items: center;
justify-content: center;
top: 0;
height: max(50dvh, 500px);
height: max(55dvh, 540px);
z-index: -10;
margin: 0;
}
@@ -78,8 +78,8 @@
left: 0;
right: 0;
margin-inline: auto;
width: min(1000px, 100dvw);
height: max(60dvh,600px);
width: min(calc(1000px + 2rem), 100dvw);
height: max(65dvh, 640px);
overflow: hidden;
}
@@ -87,8 +87,8 @@
display: block;
position: absolute;
top: 0;
width: min(1000px, 100dvw);
height: max(60dvh,600px);
width: min(calc(1000px + 2rem), 100dvw);
height: max(65dvh, 640px);
object-fit: cover;
object-position: 50% 20%;
}
+16 -4
View File
@@ -66,15 +66,24 @@
color: var(--nord0);
}
@media (prefers-color-scheme: dark) {
.link-pill {
:global(:root:not([data-theme="light"])) .link-pill {
background-color: var(--nord0);
color: var(--nord4);
}
.link-pill:hover,
.link-pill:focus-visible {
:global(:root:not([data-theme="light"])) .link-pill:hover,
:global(:root:not([data-theme="light"])) .link-pill:focus-visible {
background-color: var(--nord8);
color: var(--nord0);
}
}
:global(:root[data-theme="dark"]) .link-pill {
background-color: var(--nord0);
color: var(--nord4);
}
:global(:root[data-theme="dark"]) .link-pill:hover,
:global(:root[data-theme="dark"]) .link-pill:focus-visible {
background-color: var(--nord8);
color: var(--nord0);
}
.notes {
font-size: 0.85rem;
@@ -86,9 +95,12 @@
overflow: hidden;
}
@media (prefers-color-scheme: dark) {
.notes {
:global(:root:not([data-theme="light"])) .notes {
color: var(--nord4);
}
}
:global(:root[data-theme="dark"]) .notes {
color: var(--nord4);
}
.card-btn {
position: absolute;
@@ -384,10 +384,14 @@
}
@media(prefers-color-scheme: light) {
.translation-approval {
:global(:root:not([data-theme="dark"])) .translation-approval {
background: var(--nord6);
border-color: var(--nord4);
}
}
:global(:root[data-theme="light"]) .translation-approval {
background: var(--nord6);
border-color: var(--nord4);
}
.header {
@@ -403,9 +407,12 @@
}
@media(prefers-color-scheme: light) {
.header h3 {
:global(:root:not([data-theme="dark"])) .header h3 {
color: var(--nord0);
}
}
:global(:root[data-theme="light"]) .header h3 {
color: var(--nord0);
}
.status-badge {
@@ -458,12 +465,18 @@
/* Fix button icon visibility in dark mode */
@media (prefers-color-scheme: dark) {
.list-wrapper :global(svg) {
:global(:root:not([data-theme="light"])) .list-wrapper :global(svg) {
fill: white !important;
}
.list-wrapper :global(.button_arrow) {
:global(:root:not([data-theme="light"])) .list-wrapper :global(.button_arrow) {
fill: var(--nord4) !important;
}
}
:global(:root[data-theme="dark"]) .list-wrapper :global(svg) {
fill: white !important;
}
:global(:root[data-theme="dark"]) .list-wrapper :global(.button_arrow) {
fill: var(--nord4) !important;
}
.column-header {
@@ -585,9 +598,12 @@ button:disabled {
}
@media(prefers-color-scheme: light) {
.idle-state {
:global(:root:not([data-theme="dark"])) .idle-state {
color: var(--nord2);
}
}
:global(:root[data-theme="light"]) .idle-state {
color: var(--nord2);
}
.idle-state p {
@@ -40,9 +40,12 @@
}
@media(prefers-color-scheme: light) {
.field-label {
:global(:root:not([data-theme="dark"])) .field-label {
color: var(--nord2);
}
}
:global(:root[data-theme="light"]) .field-label {
color: var(--nord2);
}
.field-value {
@@ -55,11 +58,16 @@
}
@media(prefers-color-scheme: light) {
.field-value {
:global(:root:not([data-theme="dark"])) .field-value {
background: var(--nord5);
color: var(--nord0);
border-color: var(--nord3);
}
}
:global(:root[data-theme="light"]) .field-value {
background: var(--nord5);
color: var(--nord0);
border-color: var(--nord3);
}
.field-value.readonly {
@@ -113,13 +121,19 @@ textarea.field-value {
}
@media(prefers-color-scheme: light) {
:global(.readonly-text strong) {
:global(:root:not([data-theme="dark"]) .readonly-text strong) {
color: var(--nord10);
}
:global(.readonly-text li) {
:global(:root:not([data-theme="dark"]) .readonly-text li) {
color: var(--nord2);
}
}
:global(:root[data-theme="light"]) :global(.readonly-text strong) {
color: var(--nord10);
}
:global(:root[data-theme="light"]) :global(.readonly-text li) {
color: var(--nord2);
}
</style>