Web frontend: switchable dolphin/dinosaur skins
Two kids, two looks: ?theme=dolphin or ?theme=dinosaur picks one, and the choice is remembered in the browser so the installed home-screen app keeps it. Everything that differs per child lives in one Skin object per file under web/src/lib/skins/; layout and feature toggles in lib/theme.ts stay shared. The dolphin renders pixel-identical to before. The hardcoded sea hues (210 music, 175 typing game, 340 accent) are now CSS variables written once at startup, and the Gecko-friendly sRGB gradient stops come from the skin. The dinosaur is jungle green with a lava-orange accent, its own mascot, icons, manifest and browser colour. Nothing underwater is left in the dinosaur skin: leaves instead of bubbles (play-view canvas, room page, typing game), a walking mascot, lava blobs in the bubble game, a hatching egg instead of the treasure chest, dino pets that walk or fly, a prehistoric speed ladder with the T-Rex as the hero rung, and renamed games and light colours. Pets and ladder rungs are keyed by the backend's existing ids, used as slots, so the backend and save format are unchanged. Pets show as emoji until drawings exist. python-backend/.gitignore: ignore tippen-curriculum-dino.yml, the dino device's personal curriculum, like the original. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,9 +5,9 @@
|
||||
/* Liquid-glass surfaces over the sea gradient: a dark translucent fill, a strong
|
||||
blur with a saturation boost so colour behind it still glows through, and a thin
|
||||
bright rim along the top edge. */
|
||||
--m-glass-fill: oklch(32% 0.04 210 / 0.42);
|
||||
--m-glass-fill: oklch(32% 0.04 var(--hue) / 0.42);
|
||||
--m-glass-rim: oklch(100% 0 0 / 0.22);
|
||||
--m-glass-shadow: 0 10px 30px oklch(10% 0.04 210 / 0.45);
|
||||
--m-glass-shadow: 0 10px 30px oklch(10% 0.04 var(--hue) / 0.45);
|
||||
--m-gutter: 16px;
|
||||
/* Height of the floating dock, so scrolling content can clear it. */
|
||||
--m-dock-clearance: calc(96px + env(safe-area-inset-bottom));
|
||||
@@ -73,7 +73,7 @@ body {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 3px 8px oklch(15% 0.05 210 / 0.5));
|
||||
filter: drop-shadow(0 3px 8px oklch(15% 0.05 var(--hue) / 0.5));
|
||||
}
|
||||
|
||||
.m-title {
|
||||
@@ -82,14 +82,14 @@ body {
|
||||
font-weight: 900;
|
||||
color: var(--paper);
|
||||
letter-spacing: -0.01em;
|
||||
text-shadow: 0 2px 10px oklch(15% 0.05 210 / 0.45);
|
||||
text-shadow: 0 2px 10px oklch(15% 0.05 var(--hue) / 0.45);
|
||||
}
|
||||
|
||||
.m-status {
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
color: var(--paper);
|
||||
background: oklch(97% 0.01 210 / 0.16);
|
||||
background: oklch(97% 0.01 var(--hue) / 0.16);
|
||||
padding: 5px 12px;
|
||||
border-radius: 999px;
|
||||
white-space: nowrap;
|
||||
@@ -157,7 +157,7 @@ body {
|
||||
margin: -6px var(--m-gutter) 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: oklch(90% 0.02 210 / 0.75);
|
||||
color: oklch(90% 0.02 var(--hue) / 0.75);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------- segmented -- */
|
||||
@@ -167,11 +167,11 @@ body {
|
||||
gap: 2px;
|
||||
padding: 3px;
|
||||
border-radius: 999px;
|
||||
background: oklch(20% 0.04 210 / 0.38);
|
||||
background: oklch(20% 0.04 var(--hue) / 0.38);
|
||||
backdrop-filter: blur(12px) saturate(1.6);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(1.6);
|
||||
border: 1px solid oklch(100% 0 0 / 0.14);
|
||||
box-shadow: inset 0 1px 2px oklch(10% 0.04 210 / 0.35);
|
||||
box-shadow: inset 0 1px 2px oklch(10% 0.04 var(--hue) / 0.35);
|
||||
}
|
||||
|
||||
.m-segment {
|
||||
@@ -179,7 +179,7 @@ body {
|
||||
min-width: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: oklch(95% 0.01 210 / 0.85);
|
||||
color: oklch(95% 0.01 var(--hue) / 0.85);
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
@@ -195,7 +195,7 @@ body {
|
||||
.m-segment[data-active="true"] {
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
box-shadow: 0 3px 10px oklch(10% 0.04 210 / 0.35);
|
||||
box-shadow: 0 3px 10px oklch(10% 0.04 var(--hue) / 0.35);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ grids -- */
|
||||
@@ -243,7 +243,7 @@ body {
|
||||
}
|
||||
|
||||
.m-album-cover {
|
||||
box-shadow: 0 6px 16px oklch(10% 0.04 210 / 0.45);
|
||||
box-shadow: 0 6px 16px oklch(10% 0.04 var(--hue) / 0.45);
|
||||
transition: transform 0.12s ease;
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ body {
|
||||
|
||||
.m-song:active,
|
||||
.m-song[data-current="true"] {
|
||||
background: oklch(97% 0.01 210 / 0.14);
|
||||
background: oklch(97% 0.01 var(--hue) / 0.14);
|
||||
}
|
||||
|
||||
.m-song-cover {
|
||||
@@ -323,12 +323,12 @@ body {
|
||||
.m-song-sub {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: oklch(88% 0.02 210 / 0.65);
|
||||
color: oklch(88% 0.02 var(--hue) / 0.65);
|
||||
}
|
||||
|
||||
.m-song-time {
|
||||
font: 700 12px ui-monospace, Menlo, monospace;
|
||||
color: oklch(88% 0.02 210 / 0.6);
|
||||
color: oklch(88% 0.02 var(--hue) / 0.6);
|
||||
}
|
||||
|
||||
.m-hint {
|
||||
@@ -337,7 +337,7 @@ body {
|
||||
padding: 0 var(--m-gutter);
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
color: oklch(92% 0.02 210 / 0.8);
|
||||
color: oklch(92% 0.02 var(--hue) / 0.8);
|
||||
}
|
||||
|
||||
.m-hint img {
|
||||
@@ -422,14 +422,14 @@ body {
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
color: oklch(95% 0.01 210 / 0.8);
|
||||
color: oklch(95% 0.01 var(--hue) / 0.8);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.m-tab[data-active="true"] {
|
||||
background: oklch(97% 0.01 210 / 0.2);
|
||||
background: oklch(97% 0.01 var(--hue) / 0.2);
|
||||
color: var(--paper);
|
||||
box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.2);
|
||||
}
|
||||
@@ -483,7 +483,7 @@ body {
|
||||
}
|
||||
|
||||
.m-search input::placeholder {
|
||||
color: oklch(95% 0.01 210 / 0.55);
|
||||
color: oklch(95% 0.01 var(--hue) / 0.55);
|
||||
}
|
||||
|
||||
.m-search input::-webkit-search-cancel-button {
|
||||
@@ -494,7 +494,7 @@ body {
|
||||
cursor: pointer;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: oklch(95% 0.01 210 / 0.7);
|
||||
color: oklch(95% 0.01 var(--hue) / 0.7);
|
||||
}
|
||||
|
||||
.m-search-clear {
|
||||
@@ -506,7 +506,7 @@ body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: oklch(97% 0.01 210 / 0.3);
|
||||
background: oklch(97% 0.01 var(--hue) / 0.3);
|
||||
color: var(--sea-deep);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
@@ -545,7 +545,7 @@ body {
|
||||
height: 44px;
|
||||
flex: none;
|
||||
display: flex;
|
||||
box-shadow: 0 3px 10px oklch(10% 0.04 210 / 0.4);
|
||||
box-shadow: 0 3px 10px oklch(10% 0.04 var(--hue) / 0.4);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@@ -568,7 +568,7 @@ body {
|
||||
.m-mini-sub {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: oklch(90% 0.02 210 / 0.7);
|
||||
color: oklch(90% 0.02 var(--hue) / 0.7);
|
||||
}
|
||||
|
||||
.m-mini-button {
|
||||
@@ -587,7 +587,7 @@ body {
|
||||
}
|
||||
|
||||
.m-mini-button:active {
|
||||
background: oklch(97% 0.01 210 / 0.15);
|
||||
background: oklch(97% 0.01 var(--hue) / 0.15);
|
||||
}
|
||||
|
||||
.m-mini-progress {
|
||||
|
||||
Reference in New Issue
Block a user