feat: add Berlin Clock layout and styling with Orbitron font

This commit is contained in:
Loic Coenen
2026-06-25 10:31:10 +02:00
committed by Loic Coenen (aider)
parent abf8b13fc7
commit 3fed6dc168
7 changed files with 80 additions and 36 deletions

View File

@@ -182,3 +182,24 @@
border-right-color: var(--border); border-right-color: var(--border);
} }
} }
.berlin-clock {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.seconds-lamp-container {
margin-bottom: 4px;
display: flex;
justify-content: center;
}
.lamp-row {
display: flex;
justify-content: center;
width: 100%;
max-width: 330px;
margin: 0 auto;
}

View File

@@ -3,7 +3,15 @@ import "./App.css";
function App() { function App() {
return ( return (
<div> <div className="clock-container">
<h1>Berlin Clock</h1>
<p>
The Berlin Clock (also known as the Mengenlehreuhr) uses lamps to represent the time.
The top lamp blinks every second. The next row of four red lamps each represents five hours.
The following row of four red lamps each represents one hour.
Then the next row of eleven lamps shows fiveminute blocks (every third lamp is red to
mark quarter hours). The last row shows the remaining minutes one by one.
</p>
<TimeConverter /> <TimeConverter />
</div> </div>
); );

View File

@@ -14,23 +14,17 @@ function getBackgroundColor(state: LampState): string {
} }
function getBorderRadius(variant?: LampProps["variant"]): string { function getBorderRadius(variant?: LampProps["variant"]): string {
switch (variant) { if (variant === "alone") return "50%";
case "alone": if (variant === "first") return "16px 0 0 16px";
return "50%"; if (variant === "last") return "0 16px 16px 0";
case "first":
return "50% 0 0 50%";
case "last":
return "0 50% 50% 0";
default:
return "0"; return "0";
}
} }
export function Lamp({ state, id, variant }: LampProps) { export function Lamp({ state, id, variant }: LampProps) {
const style: React.CSSProperties = { const style: React.CSSProperties = {
display: "inline-block", display: "block",
width: 20, width: variant === "alone" ? 50 : "100%",
height: 20, height: 50,
backgroundColor: getBackgroundColor(state), backgroundColor: getBackgroundColor(state),
borderRadius: getBorderRadius(variant), borderRadius: getBorderRadius(variant),
margin: 1, margin: 1,

View File

@@ -11,7 +11,7 @@ export function LampLine({ states, rowTestId }: LampLineProps) {
return ( return (
<div <div
data-testid={rowTestId ?? "lamp-line"} data-testid={rowTestId ?? "lamp-line"}
style={{ display: "flex", gap: 2, marginBottom: 5 }} style={{ display: "flex", marginBottom: 10, width: "100%", justifyContent: "space-evenly" }}
> >
{states.map((state, index) => { {states.map((state, index) => {
let variant: "alone" | "first" | "last" | "middle" | undefined; let variant: "alone" | "first" | "last" | "middle" | undefined;
@@ -25,12 +25,16 @@ export function LampLine({ states, rowTestId }: LampLineProps) {
variant = "middle"; variant = "middle";
} }
return ( return (
<Lamp <div
key={index} key={index}
style={{ flex: 1, display: "flex" }}
>
<Lamp
state={state} state={state}
id={`lamp-${index}`} id={`lamp-${index}`}
variant={variant} variant={variant}
/> />
</div>
); );
})} })}
</div> </div>

View File

@@ -11,17 +11,22 @@ export function BerlinClock({ enabled, clock }: BerlinClockProps) {
if (!enabled) return null; if (!enabled) return null;
return ( return (
<div> <div className="berlin-clock" style={{ display: "flex", flexDirection: "column", alignItems: "center", width: "100%" }}>
<div <div className="seconds-lamp-container" data-testid="seconds-row" style={{ width: "100%", display: "flex", justifyContent: "center" }}>
data-testid="seconds-row" <Lamp state={clock.secondsRow[0]} variant="alone" id="seconds-lamp" />
style={{ display: "flex", gap: 2, marginBottom: 5 }}
>
<Lamp state={clock.secondsRow[0]} id="seconds-lamp" variant="alone" />
</div> </div>
<div className="lamp-row" style={{ width: "100%" }}>
<LampLine states={clock.fiveHours} rowTestId="five-hours-row" /> <LampLine states={clock.fiveHours} rowTestId="five-hours-row" />
</div>
<div className="lamp-row" style={{ width: "100%" }}>
<LampLine states={clock.oneHour} rowTestId="single-hours-row" /> <LampLine states={clock.oneHour} rowTestId="single-hours-row" />
</div>
<div className="lamp-row" style={{ width: "100%" }}>
<LampLine states={clock.fiveMinutes} rowTestId="five-minutes-row" /> <LampLine states={clock.fiveMinutes} rowTestId="five-minutes-row" />
</div>
<div className="lamp-row" style={{ width: "100%" }}>
<LampLine states={clock.oneMinute} rowTestId="single-minutes-row" /> <LampLine states={clock.oneMinute} rowTestId="single-minutes-row" />
</div> </div>
</div>
); );
} }

View File

@@ -19,6 +19,12 @@ export function TimeConverter() {
return ( return (
<div> <div>
<BerlinClock enabled={true} clock={berlinClock} />
<DigitalClock
currentTime={currentTime}
onTimeChange={setCurrentTime}
disabled={useSystemTime}
/>
<label> <label>
<input <input
type="checkbox" type="checkbox"
@@ -33,12 +39,6 @@ export function TimeConverter() {
/> />
Use system time Use system time
</label> </label>
<BerlinClock enabled={true} clock={berlinClock} />
<DigitalClock
currentTime={currentTime}
onTimeChange={setCurrentTime}
disabled={useSystemTime}
/>
</div> </div>
); );
} }

View File

@@ -1,3 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700&display=swap');
:root { :root {
--text: #6b6375; --text: #6b6375;
--text-h: #08060d; --text-h: #08060d;
@@ -59,6 +61,8 @@
min-height: 100svh; min-height: 100svh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center;
align-items: center;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -109,3 +113,11 @@ code {
padding: 4px 8px; padding: 4px 8px;
background: var(--code-bg); background: var(--code-bg);
} }
.clock-container {
font-family: 'Orbitron', monospace;
}
.clock-container input {
font-family: inherit;
}