Add retro LED footer marquee
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
type RetroLedMarqueeProps = {
|
||||
text: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const MATRIX_ROWS = 7;
|
||||
const MATRIX_COLUMNS = 56;
|
||||
const REPEAT_GAP_COLUMNS = 12;
|
||||
|
||||
const GLYPHS: Record<string, string[]> = {
|
||||
A: ["01110", "10001", "10001", "11111", "10001", "10001", "10001"],
|
||||
B: ["11110", "10001", "10001", "11110", "10001", "10001", "11110"],
|
||||
D: ["11110", "10001", "10001", "10001", "10001", "10001", "11110"],
|
||||
E: ["11111", "10000", "10000", "11110", "10000", "10000", "11111"],
|
||||
G: ["01111", "10000", "10000", "10111", "10001", "10001", "01111"],
|
||||
H: ["10001", "10001", "10001", "11111", "10001", "10001", "10001"],
|
||||
I: ["11111", "00100", "00100", "00100", "00100", "00100", "11111"],
|
||||
M: ["10001", "11011", "10101", "10101", "10001", "10001", "10001"],
|
||||
N: ["10001", "11001", "10101", "10011", "10001", "10001", "10001"],
|
||||
O: ["01110", "10001", "10001", "10001", "10001", "10001", "01110"],
|
||||
R: ["11110", "10001", "10001", "11110", "10100", "10010", "10001"],
|
||||
T: ["11111", "00100", "00100", "00100", "00100", "00100", "00100"],
|
||||
Y: ["10001", "10001", "01010", "00100", "00100", "00100", "00100"],
|
||||
" ": ["000", "000", "000", "000", "000", "000", "000"],
|
||||
};
|
||||
|
||||
function getGlyph(character: string) {
|
||||
return GLYPHS[character] ?? GLYPHS[" "];
|
||||
}
|
||||
|
||||
function getMessageColumns(text: string) {
|
||||
return text
|
||||
.toUpperCase()
|
||||
.split("")
|
||||
.reduce((total, character, index, characters) => {
|
||||
const glyph = getGlyph(character);
|
||||
const glyphWidth = glyph[0]?.length ?? 0;
|
||||
const spacer = index === characters.length - 1 ? 0 : 1;
|
||||
|
||||
return total + glyphWidth + spacer;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function buildLitDots(text: string) {
|
||||
const dots: Array<{ key: string; row: number; column: number }> = [];
|
||||
let columnOffset = 0;
|
||||
|
||||
text
|
||||
.toUpperCase()
|
||||
.split("")
|
||||
.forEach((character, characterIndex, characters) => {
|
||||
const glyph = getGlyph(character);
|
||||
const glyphWidth = glyph[0]?.length ?? 0;
|
||||
|
||||
glyph.forEach((row, rowIndex) => {
|
||||
row.split("").forEach((cell, columnIndex) => {
|
||||
if (cell !== "1") {
|
||||
return;
|
||||
}
|
||||
|
||||
dots.push({
|
||||
key: `${characterIndex}-${rowIndex}-${columnIndex}`,
|
||||
row: rowIndex + 1,
|
||||
column: columnOffset + columnIndex + 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
columnOffset += glyphWidth;
|
||||
|
||||
if (characterIndex < characters.length - 1) {
|
||||
columnOffset += 1;
|
||||
}
|
||||
});
|
||||
|
||||
return dots;
|
||||
}
|
||||
|
||||
function RetroLedMessage({ text, hidden = false }: { text: string; hidden?: boolean }) {
|
||||
const litDots = buildLitDots(text);
|
||||
|
||||
return (
|
||||
<div className="retro-led-message" aria-hidden={hidden}>
|
||||
{litDots.map((dot) => (
|
||||
<span
|
||||
key={dot.key}
|
||||
className="retro-led-dot retro-led-active-dot"
|
||||
style={{
|
||||
gridRow: dot.row,
|
||||
gridColumn: dot.column,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RetroLedMarquee({ text, className }: RetroLedMarqueeProps) {
|
||||
const backgroundDots = Array.from({ length: MATRIX_COLUMNS * MATRIX_ROWS }, (_, index) => index);
|
||||
const messageColumns = getMessageColumns(text);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={["retro-led-panel", className].filter(Boolean).join(" ")}
|
||||
aria-label={text}
|
||||
style={
|
||||
{
|
||||
"--retro-led-visible-columns": MATRIX_COLUMNS,
|
||||
"--retro-led-visible-rows": MATRIX_ROWS,
|
||||
"--retro-led-message-columns": messageColumns,
|
||||
"--retro-led-repeat-gap-columns": REPEAT_GAP_COLUMNS,
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
<div className="retro-led-viewport">
|
||||
<div className="retro-led-background" aria-hidden="true">
|
||||
{backgroundDots.map((dot) => (
|
||||
<span key={dot} className="retro-led-dot retro-led-background-dot" />
|
||||
))}
|
||||
</div>
|
||||
<div className="retro-led-track">
|
||||
<RetroLedMessage text={text} />
|
||||
<div className="retro-led-spacer" aria-hidden="true" />
|
||||
<RetroLedMessage text={text} hidden />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user