mirror of
https://github.com/seigler/aurebesh
synced 2025-07-26 01:06:12 +00:00
break components into files
This commit is contained in:
parent
df6c850b3f
commit
6e15a05126
10 changed files with 8714 additions and 8453 deletions
16503
package-lock.json
generated
16503
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -8,6 +8,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@preact/signals": "^1.2.3",
|
"@preact/signals": "^1.2.3",
|
||||||
|
"localforage": "^1.10.0",
|
||||||
"preact": "^10.13.1",
|
"preact": "^10.13.1",
|
||||||
"preact-iso": "^2.6.2",
|
"preact-iso": "^2.6.2",
|
||||||
"preact-render-to-string": "^6.4.1",
|
"preact-render-to-string": "^6.4.1",
|
||||||
|
|
42
src/components/DualText.tsx
Normal file
42
src/components/DualText.tsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { ligatures } from "./store";
|
||||||
|
|
||||||
|
export default function DualText({ children }: { children: string }) {
|
||||||
|
const words: string[] = [];
|
||||||
|
children.split(/\n/).forEach((line, index, lines) => {
|
||||||
|
line.split(/\b(?=\w)/).forEach((word) => {
|
||||||
|
words.push(word);
|
||||||
|
});
|
||||||
|
if (index < lines.length - 1) {
|
||||||
|
words.push("\n");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{words.map((word) => {
|
||||||
|
if (word === "\n") {
|
||||||
|
return <br />;
|
||||||
|
}
|
||||||
|
const letters: string[] = [];
|
||||||
|
for (let i = 0; i < word.length; i += 1) {
|
||||||
|
const nextTwoCharacters = word.slice(i, i + 2);
|
||||||
|
if (ligatures.value.includes(nextTwoCharacters.toLowerCase())) {
|
||||||
|
letters.push(nextTwoCharacters);
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
letters.push(nextTwoCharacters[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span class="dualtext-word">
|
||||||
|
<span class="aurebesh">{word}</span>
|
||||||
|
<div className="dualtext-help">
|
||||||
|
{letters.map((character) => {
|
||||||
|
return <span data-character={character} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
22
src/components/FontPicker.tsx
Normal file
22
src/components/FontPicker.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import localforage from "localforage";
|
||||||
|
import { fontNames, selectedFont } from "./store";
|
||||||
|
|
||||||
|
export default function FontPicker() {
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
onChange={(event) => {
|
||||||
|
const newValue = event.currentTarget.value;
|
||||||
|
selectedFont.value = newValue;
|
||||||
|
localforage.setItem("aurebesh-font", newValue);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{fontNames.map((font) => {
|
||||||
|
return (
|
||||||
|
<option value={font} selected={font === selectedFont.value}>
|
||||||
|
{font}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
202
src/components/Main.tsx
Normal file
202
src/components/Main.tsx
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
import { computed, effect, signal } from "@preact/signals";
|
||||||
|
import localforage from "localforage";
|
||||||
|
import { useEffect } from "preact/hooks";
|
||||||
|
import ReadingBox from "./ReadingBox";
|
||||||
|
|
||||||
|
const fonts: Record<string, { ligatures: string[]; lowercase: boolean }> = {
|
||||||
|
"AB-Equinox": {
|
||||||
|
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"AurebeshAF-Canon": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"AurebeshAF-CanonTech": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"AurebeshAF-Legends": {
|
||||||
|
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"AurebeshAF-LegendsTech": {
|
||||||
|
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"Aurebesh_Rodian-Oblique": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"Aurebesh_Rodian-OblqOutline": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
Aurebesh_Rodian: {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"Aurebesh_Rodian-Outline": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"AurebeshTypewriter-Light": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: true,
|
||||||
|
},
|
||||||
|
"AurebeshTypewriter-Regular": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: true,
|
||||||
|
},
|
||||||
|
Droidobesh: {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
LaptiNekAF: {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
Maulobesh: {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
Nirvanabesh: {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
Skyhook: {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fontNames = Object.keys(fonts);
|
||||||
|
|
||||||
|
const selectedFont = signal("AurebeshAF-Legends");
|
||||||
|
effect(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--font-aurebesh",
|
||||||
|
selectedFont.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const ligatures = computed(() => {
|
||||||
|
return fonts[selectedFont.value].ligatures;
|
||||||
|
});
|
||||||
|
const lowercase = computed(() => {
|
||||||
|
return fonts[selectedFont.value].lowercase;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function Main() {
|
||||||
|
useEffect(() => {
|
||||||
|
localforage
|
||||||
|
.getItem("aurebesh-font")
|
||||||
|
.then((value) => {
|
||||||
|
if (value != null) {
|
||||||
|
selectedFont.value = `${value}`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn("Couldn't fetch font preference: ", error);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header>
|
||||||
|
<h1>
|
||||||
|
<DualText>Learn Aurebesh</DualText>
|
||||||
|
</h1>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div class="content">
|
||||||
|
<FontPicker />
|
||||||
|
<ReadingBox />
|
||||||
|
</div>
|
||||||
|
<Reference />
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FontPicker() {
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
onChange={(event) => {
|
||||||
|
const newValue = event.currentTarget.value;
|
||||||
|
selectedFont.value = newValue;
|
||||||
|
localforage.setItem("aurebesh-font", newValue);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{fontNames.map((font) => {
|
||||||
|
return (
|
||||||
|
<option value={font} selected={font === selectedFont.value}>
|
||||||
|
{font}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DualText({ children }: { children: string }) {
|
||||||
|
const words: string[] = [];
|
||||||
|
children.split(/\n/).forEach((line, index, lines) => {
|
||||||
|
line.split(/\b(?=\w)/).forEach((word) => {
|
||||||
|
words.push(word);
|
||||||
|
});
|
||||||
|
if (index < lines.length - 1) {
|
||||||
|
words.push("\n");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{words.map((word) => {
|
||||||
|
if (word === "\n") {
|
||||||
|
return <br />;
|
||||||
|
}
|
||||||
|
const letters: string[] = [];
|
||||||
|
for (let i = 0; i < word.length; i += 1) {
|
||||||
|
const nextTwoCharacters = word.slice(i, i + 2);
|
||||||
|
if (ligatures.value.includes(nextTwoCharacters.toLowerCase())) {
|
||||||
|
letters.push(nextTwoCharacters);
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
letters.push(nextTwoCharacters[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span class="dualtext-word">
|
||||||
|
<span class="aurebesh">{word}</span>
|
||||||
|
<div className="dualtext-help">
|
||||||
|
{letters.map((character) => {
|
||||||
|
return <span data-character={character} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Reference() {
|
||||||
|
return (
|
||||||
|
<aside class="reference">
|
||||||
|
<DualText>
|
||||||
|
{lowercase.value ? "Aa Bb Cc Dd Ee Ff Gg Hh Ii" : "A B C D E F G H I"}
|
||||||
|
</DualText>
|
||||||
|
<DualText>
|
||||||
|
{lowercase.value ? "Jj Kk Ll Mm Nn Oo Pp Qq Rr" : "J K L M N O P Q R"}
|
||||||
|
</DualText>
|
||||||
|
<DualText>
|
||||||
|
{lowercase.value ? "Ss Tt Uu Vv Ww Xx Yy Zz" : "S T U V W X Y Z"}
|
||||||
|
</DualText>
|
||||||
|
<DualText>0 1 2 3 4 5 6 7 8 9</DualText>
|
||||||
|
{ligatures.value.length > 0 && (
|
||||||
|
<DualText>{ligatures.value.join(" ")}</DualText>
|
||||||
|
)}
|
||||||
|
<DualText>{`, . ? ! : ; ' " ( )`}</DualText>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
58
src/components/ReadingBox.tsx
Normal file
58
src/components/ReadingBox.tsx
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { useSignal } from "@preact/signals";
|
||||||
|
import localforage from "localforage";
|
||||||
|
import { useEffect } from "preact/hooks";
|
||||||
|
import DualText from "./DualText";
|
||||||
|
|
||||||
|
export default function ReadingBox() {
|
||||||
|
const isEditing = useSignal(false);
|
||||||
|
const contents =
|
||||||
|
useSignal(`It is a period of civil war. Rebel spaceships, striking from a hidden base, have won their first victory against the evil Galactic Empire.
|
||||||
|
|
||||||
|
During the battle, Rebel spies managed to steal secret plans to the Empire's ultimate weapon, the DEATH STAR, an armored space station with enough power to destroy an entire planet.
|
||||||
|
|
||||||
|
Pursued by the Empire's sinister agents, Princess Leia races home aboard her starship, custodian of the stolen plans that can save her people and restore freedom to the galaxy...`);
|
||||||
|
useEffect(() => {
|
||||||
|
localforage
|
||||||
|
.getItem("aurebesh-text")
|
||||||
|
.then((value) => {
|
||||||
|
if (value != null) {
|
||||||
|
contents.value = `${value}`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn("Couldn't fetch stored text: ", error);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div class="readingbox">
|
||||||
|
<span>
|
||||||
|
<label htmlFor="edit-toggle">Edit</label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="edit-toggle"
|
||||||
|
selected={isEditing}
|
||||||
|
onChange={(event) => {
|
||||||
|
isEditing.value = event.currentTarget.checked;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
{isEditing.value ? (
|
||||||
|
<textarea
|
||||||
|
class="readingbox-textarea"
|
||||||
|
id="reading-material"
|
||||||
|
value={contents}
|
||||||
|
onChange={(event) => {
|
||||||
|
const newValue = event.currentTarget.value;
|
||||||
|
contents.value = newValue;
|
||||||
|
localforage.setItem("aurebesh-text", newValue);
|
||||||
|
}}
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div class="readingbox-text aurebesh">
|
||||||
|
<DualText>{contents.value}</DualText>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
23
src/components/Reference.tsx
Normal file
23
src/components/Reference.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import DualText from "./DualText";
|
||||||
|
import { ligatures, lowercase } from "./store";
|
||||||
|
|
||||||
|
export default function Reference() {
|
||||||
|
return (
|
||||||
|
<aside class="reference">
|
||||||
|
<DualText>
|
||||||
|
{lowercase.value ? "Aa Bb Cc Dd Ee Ff Gg Hh Ii" : "A B C D E F G H I"}
|
||||||
|
</DualText>
|
||||||
|
<DualText>
|
||||||
|
{lowercase.value ? "Jj Kk Ll Mm Nn Oo Pp Qq Rr" : "J K L M N O P Q R"}
|
||||||
|
</DualText>
|
||||||
|
<DualText>
|
||||||
|
{lowercase.value ? "Ss Tt Uu Vv Ww Xx Yy Zz" : "S T U V W X Y Z"}
|
||||||
|
</DualText>
|
||||||
|
<DualText>0 1 2 3 4 5 6 7 8 9</DualText>
|
||||||
|
{ligatures.value.length > 0 && (
|
||||||
|
<DualText>{ligatures.value.join(" ")}</DualText>
|
||||||
|
)}
|
||||||
|
<DualText>{`, . ? ! : ; ' " ( )`}</DualText>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
89
src/components/store.ts
Normal file
89
src/components/store.ts
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import { computed, effect, signal } from "@preact/signals";
|
||||||
|
|
||||||
|
export const fonts: Record<
|
||||||
|
string,
|
||||||
|
{ ligatures: string[]; lowercase: boolean }
|
||||||
|
> = {
|
||||||
|
"AB-Equinox": {
|
||||||
|
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"AurebeshAF-Canon": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"AurebeshAF-CanonTech": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"AurebeshAF-Legends": {
|
||||||
|
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"AurebeshAF-LegendsTech": {
|
||||||
|
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"Aurebesh_Rodian-Oblique": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"Aurebesh_Rodian-OblqOutline": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
Aurebesh_Rodian: {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"Aurebesh_Rodian-Outline": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
"AurebeshTypewriter-Light": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: true,
|
||||||
|
},
|
||||||
|
"AurebeshTypewriter-Regular": {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: true,
|
||||||
|
},
|
||||||
|
Droidobesh: {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
LaptiNekAF: {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
Maulobesh: {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
Nirvanabesh: {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
Skyhook: {
|
||||||
|
ligatures: [],
|
||||||
|
lowercase: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fontNames = Object.keys(fonts);
|
||||||
|
|
||||||
|
export const selectedFont = signal("AurebeshAF-Legends");
|
||||||
|
effect(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--font-aurebesh",
|
||||||
|
selectedFont.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export const ligatures = computed(() => {
|
||||||
|
return fonts[selectedFont.value].ligatures;
|
||||||
|
});
|
||||||
|
export const lowercase = computed(() => {
|
||||||
|
return fonts[selectedFont.value].lowercase;
|
||||||
|
});
|
224
src/index.tsx
224
src/index.tsx
|
@ -1,230 +1,10 @@
|
||||||
import { hydrate, prerender as ssr } from "preact-iso";
|
import { hydrate, prerender as ssr } from "preact-iso";
|
||||||
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
import { computed, effect, signal, useSignal } from "@preact/signals";
|
import Main from "./components/Main";
|
||||||
|
|
||||||
const fonts: Record<string, { ligatures: string[]; lowercase: boolean }> = {
|
|
||||||
"AB-Equinox": {
|
|
||||||
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
"AurebeshAF-Canon": {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
"AurebeshAF-CanonTech": {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
"AurebeshAF-Legends": {
|
|
||||||
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
"AurebeshAF-LegendsTech": {
|
|
||||||
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
"Aurebesh_Rodian-Oblique": {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
"Aurebesh_Rodian-OblqOutline": {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
Aurebesh_Rodian: {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
"Aurebesh_Rodian-Outline": {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
"AurebeshTypewriter-Light": {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: true,
|
|
||||||
},
|
|
||||||
"AurebeshTypewriter-Regular": {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: true,
|
|
||||||
},
|
|
||||||
Droidobesh: {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
LaptiNekAF: {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
Maulobesh: {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
Nirvanabesh: {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
Skyhook: {
|
|
||||||
ligatures: [],
|
|
||||||
lowercase: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const fontNames = Object.keys(fonts);
|
|
||||||
|
|
||||||
const selectedFont = signal("AurebeshAF-Legends");
|
|
||||||
effect(() => {
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
document.documentElement.style.setProperty(
|
|
||||||
"--font-aurebesh",
|
|
||||||
selectedFont.value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const ligatures = computed(() => {
|
|
||||||
return fonts[selectedFont.value].ligatures;
|
|
||||||
});
|
|
||||||
const lowercase = computed(() => {
|
|
||||||
return fonts[selectedFont.value].lowercase;
|
|
||||||
});
|
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
return (
|
return <Main />;
|
||||||
<>
|
|
||||||
<header>
|
|
||||||
<h1>
|
|
||||||
<DualText>Learn Aurebesh</DualText>
|
|
||||||
</h1>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<div class="content">
|
|
||||||
<FontPicker />
|
|
||||||
<ReadingBox />
|
|
||||||
</div>
|
|
||||||
<Reference />
|
|
||||||
</main>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ReadingBox() {
|
|
||||||
const isEditing = useSignal(false);
|
|
||||||
const contents =
|
|
||||||
useSignal(`It is a period of civil war. Rebel spaceships, striking from a hidden base, have won their first victory against the evil Galactic Empire.
|
|
||||||
|
|
||||||
During the battle, Rebel spies managed to steal secret plans to the Empire's ultimate weapon, the DEATH STAR, an armored space station with enough power to destroy an entire planet.
|
|
||||||
|
|
||||||
Pursued by the Empire's sinister agents, Princess Leia races home aboard her starship, custodian of the stolen plans that can save her people and restore freedom to the galaxy...`);
|
|
||||||
return (
|
|
||||||
<div class="readingbox">
|
|
||||||
<span>
|
|
||||||
<label htmlFor="edit-toggle">Edit</label>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="edit-toggle"
|
|
||||||
selected={isEditing}
|
|
||||||
onChange={(event) => {
|
|
||||||
isEditing.value = event.currentTarget.checked;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
{isEditing.value ? (
|
|
||||||
<textarea
|
|
||||||
class="readingbox-textarea"
|
|
||||||
id="reading-material"
|
|
||||||
value={contents}
|
|
||||||
onChange={(event) => {
|
|
||||||
contents.value = event.currentTarget.value;
|
|
||||||
}}
|
|
||||||
spellCheck={false}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div class="readingbox-text aurebesh">
|
|
||||||
<DualText>{contents.value}</DualText>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function FontPicker() {
|
|
||||||
return (
|
|
||||||
<select
|
|
||||||
onChange={(event) => {
|
|
||||||
selectedFont.value = event.currentTarget.value;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{fontNames.map((font) => {
|
|
||||||
return (
|
|
||||||
<option value={font} selected={font === selectedFont.value}>
|
|
||||||
{font}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function DualText({ children }: { children: string }) {
|
|
||||||
const words: string[] = [];
|
|
||||||
children.split(/\n/).forEach((line, index, lines) => {
|
|
||||||
line.split(/\b(?=\w)/).forEach((word) => {
|
|
||||||
words.push(word);
|
|
||||||
});
|
|
||||||
if (index < lines.length - 1) {
|
|
||||||
words.push("\n");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
{words.map((word) => {
|
|
||||||
if (word === "\n") {
|
|
||||||
return <br />;
|
|
||||||
}
|
|
||||||
const letters: string[] = [];
|
|
||||||
for (let i = 0; i < word.length; i += 1) {
|
|
||||||
const nextTwoCharacters = word.slice(i, i + 2);
|
|
||||||
if (ligatures.value.includes(nextTwoCharacters.toLowerCase())) {
|
|
||||||
letters.push(nextTwoCharacters);
|
|
||||||
i += 1;
|
|
||||||
} else {
|
|
||||||
letters.push(nextTwoCharacters[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<span class="dualtext-word">
|
|
||||||
<span class="aurebesh">{word}</span>
|
|
||||||
<div className="dualtext-help">
|
|
||||||
{letters.map((character) => {
|
|
||||||
return <span data-character={character} />;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Reference() {
|
|
||||||
return (
|
|
||||||
<aside class="reference">
|
|
||||||
<DualText>
|
|
||||||
{lowercase.value ? "Aa Bb Cc Dd Ee Ff Gg Hh Ii" : "A B C D E F G H I"}
|
|
||||||
</DualText>
|
|
||||||
<DualText>
|
|
||||||
{lowercase.value ? "Jj Kk Ll Mm Nn Oo Pp Qq Rr" : "J K L M N O P Q R"}
|
|
||||||
</DualText>
|
|
||||||
<DualText>
|
|
||||||
{lowercase.value ? "Ss Tt Uu Vv Ww Xx Yy Zz" : "S T U V W X Y Z"}
|
|
||||||
</DualText>
|
|
||||||
<DualText>0 1 2 3 4 5 6 7 8 9</DualText>
|
|
||||||
{ligatures.value.length > 0 && (
|
|
||||||
<DualText>{ligatures.value.join(" ")}</DualText>
|
|
||||||
)}
|
|
||||||
<DualText>{`, . ? ! : ; ' " ( )`}</DualText>
|
|
||||||
</aside>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
--font-aurebesh: Droidobesh;
|
--font-aurebesh: Droidobesh;
|
||||||
--font-standard: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
--font-standard: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
|
||||||
font-size: clamp(100%, 1rem + 2vw, 24px);
|
font-size: clamp(100%, 1.5rem + 3vw, 350%);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,6 +172,7 @@ h1 {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
font-family: var(--font-standard);
|
font-family: var(--font-standard);
|
||||||
font-size: clamp(0.75rem, 0.25em, 1rem);
|
font-size: clamp(0.75rem, 0.25em, 1rem);
|
||||||
|
line-height: 1;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue