Make the lesson map the floor screen of the typing tab
The aquarium was a separate landing screen in front of the map, showing the pets collected so far and a "Weiter üben" button. But the pets already swim behind every screen in the tab, so the screen mostly restated what was visible anyway, at the cost of one extra step between opening the tab and typing. The map absorbs what was worth keeping: the "Weiter üben" button now floats over it, and the pets, streak and best animal move into the header. One Escape from the map leaves the tab entirely, where it used to go back a screen first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,13 @@
|
|||||||
/** The typing game, as a tab of the music player rather than its own app.
|
/** The typing game, as a tab of the music player rather than its own app.
|
||||||
*
|
*
|
||||||
* Ported from the standalone tippen app's App.tsx - same shape (one state object, one
|
* Ported from the standalone tippen app's App.tsx - same shape (one state object, one
|
||||||
* keydown listener for navigation, four screens) - with two changes: curriculum and
|
* keydown listener for navigation, screens) - with two changes: curriculum and
|
||||||
* progress are now fetched from the backend instead of a build-time YAML import and
|
* progress are now fetched from the backend instead of a build-time YAML import and
|
||||||
* localStorage (see hooks/useTippenCurriculum.ts and hooks/useTippenProgress.ts), and
|
* localStorage (see hooks/useTippenCurriculum.ts and hooks/useTippenProgress.ts), and
|
||||||
* `onExit` is the new base case for "back" - Escape/the map's own navigation peel one
|
* `onExit` is the new base case for "back". The lesson map is the floor screen - the
|
||||||
* layer at a time, same as before, but the aquarium screen is no longer the floor: one
|
* swimming aquarium creatures already show behind it, so there is no separate landing
|
||||||
* more Escape leaves the tab entirely, back to the music player.
|
* screen in front of it - and one Escape from the map leaves the tab entirely, back to
|
||||||
|
* the music player.
|
||||||
*
|
*
|
||||||
* The rule that still matters most: **while a run is going, every key belongs to the
|
* The rule that still matters most: **while a run is going, every key belongs to the
|
||||||
* run**. This component's own keydown listener only ever intercepts Escape and F1 - the
|
* run**. This component's own keydown listener only ever intercepts Escape and F1 - the
|
||||||
@@ -18,7 +19,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|||||||
|
|
||||||
import { useTippenCurriculum } from "../hooks/useTippenCurriculum";
|
import { useTippenCurriculum } from "../hooks/useTippenCurriculum";
|
||||||
import { useTippenProgress } from "../hooks/useTippenProgress";
|
import { useTippenProgress } from "../hooks/useTippenProgress";
|
||||||
import { Aquarium } from "./tippen/Aquarium";
|
|
||||||
import { AppHeader } from "./tippen/AppHeader";
|
import { AppHeader } from "./tippen/AppHeader";
|
||||||
import { HelpOverlay } from "./tippen/HelpOverlay";
|
import { HelpOverlay } from "./tippen/HelpOverlay";
|
||||||
import { LessonMap } from "./tippen/LessonMap";
|
import { LessonMap } from "./tippen/LessonMap";
|
||||||
@@ -29,16 +29,18 @@ import { DiveRun } from "./tippen/modes/DiveRun";
|
|||||||
import { FeedRun } from "./tippen/modes/FeedRun";
|
import { FeedRun } from "./tippen/modes/FeedRun";
|
||||||
import { RaceRun } from "./tippen/modes/RaceRun";
|
import { RaceRun } from "./tippen/modes/RaceRun";
|
||||||
import type { CreatureId } from "../lib/tippen/aquarium";
|
import type { CreatureId } from "../lib/tippen/aquarium";
|
||||||
|
import { creatureById } from "../lib/tippen/aquarium";
|
||||||
import type { Lesson, ModeId } from "../lib/tippen/curriculum";
|
import type { Lesson, ModeId } from "../lib/tippen/curriculum";
|
||||||
import { lessonById, nextLesson } from "../lib/tippen/curriculum";
|
import { lessonById, nextLesson } from "../lib/tippen/curriculum";
|
||||||
import { letterStream, lineFor, lineText, mulberry32 } from "../lib/tippen/generator";
|
import { letterStream, lineFor, lineText, mulberry32 } from "../lib/tippen/generator";
|
||||||
|
import { animalById } from "../lib/tippen/grading";
|
||||||
import type { RunResult } from "../lib/tippen/grading";
|
import type { RunResult } from "../lib/tippen/grading";
|
||||||
import { playFanfare, playPop } from "../lib/tippen/pop";
|
import { playFanfare, playPop } from "../lib/tippen/pop";
|
||||||
import { focusKeyFor, overallBestAnimal } from "../lib/tippen/progress";
|
import { focusKeyFor, overallBestAnimal } from "../lib/tippen/progress";
|
||||||
import type { UnlockedReward } from "../lib/tippen/progress";
|
import type { UnlockedReward } from "../lib/tippen/progress";
|
||||||
import { bubbleCountFor } from "../lib/tippen/theme";
|
import { bubbleCountFor } from "../lib/tippen/theme";
|
||||||
|
|
||||||
type Screen = "aquarium" | "map" | "run";
|
type Screen = "map" | "run";
|
||||||
|
|
||||||
/** What a mode is handed to draw - see the original App.tsx for why there are only two
|
/** What a mode is handed to draw - see the original App.tsx for why there are only two
|
||||||
* shapes for four modes. */
|
* shapes for four modes. */
|
||||||
@@ -67,7 +69,7 @@ export function TippenApp({ onExit }: Props) {
|
|||||||
const configured = curriculum !== null;
|
const configured = curriculum !== null;
|
||||||
const { progress, loading: progressLoading, recordRun, saveSettings } = useTippenProgress(configured);
|
const { progress, loading: progressLoading, recordRun, saveSettings } = useTippenProgress(configured);
|
||||||
|
|
||||||
const [screen, setScreen] = useState<Screen>("aquarium");
|
const [screen, setScreen] = useState<Screen>("map");
|
||||||
const [lessonId, setLessonId] = useState<string | null>(null);
|
const [lessonId, setLessonId] = useState<string | null>(null);
|
||||||
const [mode, setMode] = useState<ModeId>("dive");
|
const [mode, setMode] = useState<ModeId>("dive");
|
||||||
const [outcome, setOutcome] = useState<Outcome | null>(null);
|
const [outcome, setOutcome] = useState<Outcome | null>(null);
|
||||||
@@ -170,8 +172,7 @@ export function TippenApp({ onExit }: Props) {
|
|||||||
const goBack = useCallback(() => {
|
const goBack = useCallback(() => {
|
||||||
if (outcome) return setOutcome(null);
|
if (outcome) return setOutcome(null);
|
||||||
if (screen === "run") return setScreen("map");
|
if (screen === "run") return setScreen("map");
|
||||||
if (screen === "map") return setScreen("aquarium");
|
if (screen === "map") return onExit();
|
||||||
if (screen === "aquarium") return onExit();
|
|
||||||
}, [outcome, screen, onExit]);
|
}, [outcome, screen, onExit]);
|
||||||
|
|
||||||
// --- navigation keys -----------------------------------------------------
|
// --- navigation keys -----------------------------------------------------
|
||||||
@@ -211,12 +212,8 @@ export function TippenApp({ onExit }: Props) {
|
|||||||
|
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (current.screen === "aquarium") {
|
|
||||||
if (current.nextUp) current.start(current.nextUp);
|
|
||||||
} else {
|
|
||||||
const lesson = current.curriculum.lessons[current.selected];
|
const lesson = current.curriculum.lessons[current.selected];
|
||||||
if (lesson) current.start(lesson);
|
if (lesson) current.start(lesson);
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +270,7 @@ export function TippenApp({ onExit }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const next = lessonId ? nextLesson(curriculum, lessonId) : null;
|
const next = lessonId ? nextLesson(curriculum, lessonId) : null;
|
||||||
|
const bestAnimal = overallBestAnimal(progress);
|
||||||
|
|
||||||
// Every mode takes the same bundle; only the drawing differs.
|
// Every mode takes the same bundle; only the drawing differs.
|
||||||
const shared = { activeKeys: lesson?.activeKeys ?? [], progress, paused: outcome !== null, onFinished };
|
const shared = { activeKeys: lesson?.activeKeys ?? [], progress, paused: outcome !== null, onFinished };
|
||||||
@@ -291,25 +289,54 @@ export function TippenApp({ onExit }: Props) {
|
|||||||
compact={screen === "run"}
|
compact={screen === "run"}
|
||||||
status={
|
status={
|
||||||
<div style={{ display: "flex", gap: 12, alignItems: "center", color: "var(--paper)", fontWeight: 800 }}>
|
<div style={{ display: "flex", gap: 12, alignItems: "center", color: "var(--paper)", fontWeight: 800 }}>
|
||||||
|
{screen === "map" && (
|
||||||
|
<>
|
||||||
|
<div className="tp-map-creatures">
|
||||||
|
{curriculum.worlds.map((world) => {
|
||||||
|
const creature = creatureById(world.reward);
|
||||||
|
const owned = progress.aquarium.includes(creature.id);
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
key={creature.id}
|
||||||
|
src={creature.image}
|
||||||
|
alt=""
|
||||||
|
title={owned ? creature.name : `Welt ${world.number}`}
|
||||||
|
style={{
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
objectFit: "contain",
|
||||||
|
filter: owned ? "none" : "brightness(0) invert(1)",
|
||||||
|
opacity: owned ? 1 : 0.3,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<span title="Tage am Stück">🔥 {progress.streak.days}</span>
|
||||||
|
{bestAnimal && (
|
||||||
|
<span title="Schnellstes Tier">
|
||||||
|
{animalById(bestAnimal).emoji} {animalById(bestAnimal).name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{!progress.settings.sound && <span title="Ton aus">🔇</span>}
|
{!progress.settings.sound && <span title="Ton aus">🔇</span>}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{screen === "aquarium" && (
|
{screen === "map" && (
|
||||||
<Aquarium
|
<LessonMap
|
||||||
worlds={curriculum.worlds}
|
worlds={curriculum.worlds}
|
||||||
|
lessons={curriculum.lessons}
|
||||||
progress={progress}
|
progress={progress}
|
||||||
|
selected={selected}
|
||||||
|
onPick={start}
|
||||||
nextLesson={nextUp}
|
nextLesson={nextUp}
|
||||||
onContinue={() => nextUp && start(nextUp)}
|
onContinue={() => nextUp && start(nextUp)}
|
||||||
onOpenMap={() => setScreen("map")}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{screen === "map" && (
|
|
||||||
<LessonMap worlds={curriculum.worlds} lessons={curriculum.lessons} progress={progress} selected={selected} onPick={start} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{screen === "run" && lesson && run && (
|
{screen === "run" && lesson && run && (
|
||||||
<>
|
<>
|
||||||
{run.kind === "letters" ? (
|
{run.kind === "letters" ? (
|
||||||
@@ -331,7 +358,7 @@ export function TippenApp({ onExit }: Props) {
|
|||||||
newCreature={outcome.newCreature}
|
newCreature={outcome.newCreature}
|
||||||
unlockedReward={outcome.unlockedReward}
|
unlockedReward={outcome.unlockedReward}
|
||||||
isNewBest={outcome.isNewBest}
|
isNewBest={outcome.isNewBest}
|
||||||
bestEver={overallBestAnimal(progress)}
|
bestEver={bestAnimal}
|
||||||
bonusModes={outcome.result.passed ? lesson.bonusModes : []}
|
bonusModes={outcome.result.passed ? lesson.bonusModes : []}
|
||||||
onPlayBonus={playBonus}
|
onPlayBonus={playBonus}
|
||||||
onRetry={retry}
|
onRetry={retry}
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
/** The home screen: what she has collected, before she is asked to do anything.
|
|
||||||
*
|
|
||||||
* Deliberately the first thing on opening the app. The reward for finishing a world is a
|
|
||||||
* pet that moves in for good - it swims behind every screen from then on (see
|
|
||||||
* AquariumCreatures.tsx) - and a reward you can see before you start is worth more than
|
|
||||||
* one you are told about afterwards.
|
|
||||||
*
|
|
||||||
* The panel shows every pet there is to earn: the ones at home in colour, the rest as
|
|
||||||
* pale outlines. The same idea as the animal ladder - the next thing has to be visible to
|
|
||||||
* be worth aiming at - while the outline alone keeps a little surprise for the arrival. */
|
|
||||||
|
|
||||||
import { creatureById } from "../../lib/tippen/aquarium";
|
|
||||||
import type { Lesson, World } from "../../lib/tippen/curriculum";
|
|
||||||
import { animalById } from "../../lib/tippen/grading";
|
|
||||||
import { overallBestAnimal } from "../../lib/tippen/progress";
|
|
||||||
import type { Progress } from "../../lib/tippen/progress";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
worlds: readonly World[];
|
|
||||||
progress: Progress;
|
|
||||||
/** The lesson the "Weiter üben" button jumps to - the first unfinished one. */
|
|
||||||
nextLesson: Lesson | null;
|
|
||||||
onContinue: () => void;
|
|
||||||
onOpenMap: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Aquarium({ worlds, progress, nextLesson, onContinue, onOpenMap }: Props) {
|
|
||||||
const bestAnimal = overallBestAnimal(progress);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="view-enter"
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
gap: 22,
|
|
||||||
padding: "0 32px 32px",
|
|
||||||
minHeight: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="tp-glass-panel"
|
|
||||||
style={{ padding: "26px 34px", width: "min(680px, 100%)", textAlign: "center" }}
|
|
||||||
>
|
|
||||||
<div style={{ fontSize: 15, fontWeight: 900, color: "var(--paper)", opacity: 0.8 }}>
|
|
||||||
Dein Aquarium
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
aria-label="Deine Tiere"
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
gap: 18,
|
|
||||||
margin: "16px 0 6px",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{worlds.map((world) => {
|
|
||||||
const creature = creatureById(world.reward);
|
|
||||||
const owned = progress.aquarium.includes(creature.id);
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
key={creature.id}
|
|
||||||
src={creature.image}
|
|
||||||
alt={owned ? creature.name : `Noch nicht da - Welt ${world.number}`}
|
|
||||||
title={owned ? creature.name : `Welt ${world.number}`}
|
|
||||||
style={{
|
|
||||||
width: 62,
|
|
||||||
height: 62,
|
|
||||||
objectFit: "contain",
|
|
||||||
// Not yet earned: a pale outline of the shape, no colours given away.
|
|
||||||
filter: owned ? "none" : "brightness(0) invert(1)",
|
|
||||||
opacity: owned ? 1 : 0.2,
|
|
||||||
animation: owned ? `dolphinBob ${3 + world.number * 0.4}s ease-in-out infinite` : undefined,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{progress.aquarium.length === 0 && (
|
|
||||||
<div style={{ fontSize: 15, fontWeight: 800, color: "var(--paper)", opacity: 0.65 }}>
|
|
||||||
Schaffe eine ganze Welt und dein erstes Tier zieht ein!
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div style={{ display: "flex", justifyContent: "center", gap: 30, marginTop: 14 }}>
|
|
||||||
<Stat label="Tage am Stück" value={`🔥 ${progress.streak.days}`} />
|
|
||||||
<Stat
|
|
||||||
label="Schnellstes Tier"
|
|
||||||
value={bestAnimal ? `${animalById(bestAnimal).emoji} ${animalById(bestAnimal).name}` : "—"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ display: "flex", gap: 12 }}>
|
|
||||||
{nextLesson && (
|
|
||||||
<button
|
|
||||||
onClick={onContinue}
|
|
||||||
style={{
|
|
||||||
border: "none",
|
|
||||||
borderRadius: 999,
|
|
||||||
padding: "15px 32px",
|
|
||||||
fontSize: 19,
|
|
||||||
fontWeight: 900,
|
|
||||||
cursor: "pointer",
|
|
||||||
background: "var(--accent)",
|
|
||||||
color: "var(--paper)",
|
|
||||||
boxShadow: "0 8px 24px var(--shadow)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
▶ {nextLesson.title}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button className="tp-pill" onClick={onOpenMap} style={{ fontSize: 16, padding: "15px 26px" }}>
|
|
||||||
🗺️ Alle Lektionen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Stat({ label, value }: { label: string; value: string }) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div style={{ fontSize: 19, fontWeight: 900, color: "var(--paper)" }}>{value}</div>
|
|
||||||
<div style={{ fontSize: 11, fontWeight: 800, color: "var(--paper)", opacity: 0.6 }}>{label}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
/** The map: a single winding path, one world section at a time.
|
/** The map: a single winding path, one world section at a time.
|
||||||
|
*
|
||||||
|
* The floor screen of the tippen tab - the swimming aquarium creatures already show
|
||||||
|
* behind it, so there is no separate landing screen in front of this one anymore.
|
||||||
*
|
*
|
||||||
* Locked lessons are dimmed rather than hidden - seeing that "Große Buchstaben" is
|
* Locked lessons are dimmed rather than hidden - seeing that "Große Buchstaben" is
|
||||||
* waiting is half the reason to finish the world that is open. Each node carries its own
|
* waiting is half the reason to finish the world that is open. Each node carries its own
|
||||||
* best animal, star count and a badge for which game it plays, so the map doubles as
|
* best animal, star count and a badge for which game it plays, so the map doubles as
|
||||||
* both a path forward and a trophy cabinet. */
|
* both a path forward and a trophy cabinet. A second badge marks a lesson that unlocks
|
||||||
|
* real music or an audiobook chapter, shown whether or not the lesson itself is locked
|
||||||
|
* yet - same reasoning as the dimmed-not-hidden lessons: seeing it coming is the point. */
|
||||||
|
|
||||||
import type { Lesson, World } from "../../lib/tippen/curriculum";
|
import type { Lesson, World } from "../../lib/tippen/curriculum";
|
||||||
import { animalById } from "../../lib/tippen/grading";
|
import { animalById } from "../../lib/tippen/grading";
|
||||||
@@ -18,6 +23,9 @@ interface Props {
|
|||||||
/** Which card the keyboard selection is on. */
|
/** Which card the keyboard selection is on. */
|
||||||
selected: number;
|
selected: number;
|
||||||
onPick: (lesson: Lesson) => void;
|
onPick: (lesson: Lesson) => void;
|
||||||
|
/** The lesson the floating "Weiter üben" button jumps to - the first unfinished one. */
|
||||||
|
nextLesson: Lesson | null;
|
||||||
|
onContinue: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NODE_SIZE = 88;
|
const NODE_SIZE = 88;
|
||||||
@@ -32,7 +40,7 @@ const CONSOLIDATION_LABEL: Record<"fragments" | "words" | "sentences", string> =
|
|||||||
sentences: "Sätze",
|
sentences: "Sätze",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function LessonMap({ worlds, lessons, progress, selected, onPick }: Props) {
|
export function LessonMap({ worlds, lessons, progress, selected, onPick, nextLesson, onContinue }: Props) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="view-enter"
|
className="view-enter"
|
||||||
@@ -128,6 +136,16 @@ export function LessonMap({ worlds, lessons, progress, selected, onPick }: Props
|
|||||||
{modeInfo.emoji}
|
{modeInfo.emoji}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
{lesson.reward.resolved && (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
style={{ position: "absolute", top: -4, left: -4, fontSize: 15, opacity: 0.9 }}
|
||||||
|
title="Schaltet neue Musik frei!"
|
||||||
|
>
|
||||||
|
🎁
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
<span style={{ fontSize: 24 }}>{locked ? "🔒" : (animal?.emoji ?? "·")}</span>
|
<span style={{ fontSize: 24 }}>{locked ? "🔒" : (animal?.emoji ?? "·")}</span>
|
||||||
|
|
||||||
{/* The keys themselves: for a pre-reader this is the real label, the
|
{/* The keys themselves: for a pre-reader this is the real label, the
|
||||||
@@ -167,6 +185,29 @@ export function LessonMap({ worlds, lessons, progress, selected, onPick }: Props
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{nextLesson && (
|
||||||
|
<button
|
||||||
|
onClick={onContinue}
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
bottom: 24,
|
||||||
|
left: "50%",
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: 999,
|
||||||
|
padding: "15px 32px",
|
||||||
|
fontSize: 19,
|
||||||
|
fontWeight: 900,
|
||||||
|
cursor: "pointer",
|
||||||
|
background: "var(--accent)",
|
||||||
|
color: "var(--paper)",
|
||||||
|
boxShadow: "0 8px 24px var(--shadow)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
▶ {nextLesson.title}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,3 +294,19 @@
|
|||||||
.tp-target-char[data-blank="true"] {
|
.tp-target-char[data-blank="true"] {
|
||||||
min-width: 0.9em;
|
min-width: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The world-reward pets, shown small in the map's header: one per world, in colour once
|
||||||
|
it has moved in and as a white silhouette until then. The same "the next thing has to
|
||||||
|
be visible to be worth aiming at" rule as the animal ladder on the result sheet. */
|
||||||
|
.tp-map-creatures {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
/* The streak and the best animal say more per pixel on a narrow screen. */
|
||||||
|
.tp-map-creatures {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user