Skip to content

Commit 9ae325a

Browse files
committed
feat(site): 404 page + QuickBox inline transliteration widget
## 404 page (TODO 50) - src/pages/404.astro - Suggestions for common destinations (home, demo, maps, docs, blog) - Link to file issue if user thinks it's a bug ## QuickBox component (TODO 57) - src/components/QuickBox.vue - Compact inline transliteration for blog/docs - Same dynamic-load pattern as MapExplorer - Curated 5-system selector - Optional `compact` prop for tighter layout - Usage: `<QuickBox client:visible default-system="bgnpcgn-ukr-Cyrl-Latn-2019" />` Build now produces 8 pages. All 15 vitest tests pass.
1 parent c9f0961 commit 9ae325a

2 files changed

Lines changed: 187 additions & 0 deletions

File tree

src/components/QuickBox.vue

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<script setup lang="ts">
2+
/**
3+
* Quick transliteration widget — compact inline island for blog posts,
4+
* doc pages, etc. Picks a system from a small curated list and shows
5+
* the result below the input.
6+
*
7+
* Loading interscript-ts dynamically keeps the page bundle small.
8+
*/
9+
import { ref, computed, onMounted } from "vue"
10+
11+
interface Props {
12+
/** Optional initial system code. */
13+
defaultSystem?: string
14+
/** Optional initial input text. */
15+
defaultInput?: string
16+
/** Compact layout (no header). */
17+
compact?: boolean
18+
}
19+
20+
const props = withDefaults(defineProps<Props>(), {
21+
defaultSystem: "bgnpcgn-ukr-Cyrl-Latn-2019",
22+
defaultInput: "Антон",
23+
compact: false,
24+
})
25+
26+
const systems = [
27+
{ code: "bgnpcgn-ukr-Cyrl-Latn-2019", label: "BGN/PCGN Ukrainian (2019)" },
28+
{ code: "bgnpcgn-deu-Latn-Latn-2000", label: "BGN/PCGN German (2000)" },
29+
{ code: "odni-rus-Cyrl-Latn-2015", label: "ODNI Russian (2015)" },
30+
{ code: "alalc-amh-Ethi-Latn-2011", label: "ALA-LC Amharic (2011)" },
31+
{ code: "un-tam-Taml-Latn-1972", label: "UN Tamil (1972)" },
32+
]
33+
34+
const selected = ref(props.defaultSystem)
35+
const input = ref(props.defaultInput)
36+
const ready = ref(false)
37+
let transliterateFn: ((code: string, input: string) => string) | null = null
38+
39+
async function ensureEngine() {
40+
if (transliterateFn) return
41+
try {
42+
const mod = await import("interscript-ts")
43+
const modules = import.meta.glob("/maps/*.json", { eager: true, as: "raw" })
44+
const maps: Record<string, unknown> = {}
45+
for (const [path, raw] of Object.entries(modules)) {
46+
const code = path.match(/\/maps\/(.+)\.json$/)?.[1]
47+
if (code) maps[code] = JSON.parse(raw as string)
48+
}
49+
mod.reset()
50+
mod.configure({ strategies: [mod.bundledStrategy(maps)] })
51+
transliterateFn = mod.transliterate
52+
ready.value = true
53+
} catch (e) {
54+
console.error("interscript-ts load failed:", e)
55+
}
56+
}
57+
58+
const output = computed(() => {
59+
if (!transliterateFn) return ""
60+
try {
61+
return transliterateFn(selected.value, input.value)
62+
} catch {
63+
return ""
64+
}
65+
})
66+
67+
onMounted(ensureEngine)
68+
</script>
69+
70+
<template>
71+
<div class="quick" :class="{ compact }">
72+
<div v-if="!compact" class="header">
73+
<label for="qs">Quick transliteration</label>
74+
</div>
75+
<select id="qs" v-model="selected" class="select">
76+
<option v-for="s in systems" :key="s.code" :value="s.code">{{ s.label }}</option>
77+
</select>
78+
<input
79+
v-model="input"
80+
type="text"
81+
class="input"
82+
placeholder="Type text…"
83+
:disabled="!ready"
84+
/>
85+
<div class="output" :class="{ placeholder: !output }">
86+
{{ output || (ready ? '—' : 'Loading…') }}
87+
</div>
88+
</div>
89+
</template>
90+
91+
<style scoped>
92+
.quick {
93+
border: 1px solid var(--border, #ddd);
94+
border-radius: 6px;
95+
padding: 0.75rem;
96+
background: var(--surface, #f8f8f8);
97+
display: grid;
98+
grid-template-columns: 1fr;
99+
gap: 0.4rem;
100+
}
101+
.quick.compact {
102+
padding: 0.5rem;
103+
}
104+
.header {
105+
font-size: 0.85rem;
106+
color: var(--muted, #666);
107+
text-transform: uppercase;
108+
letter-spacing: 0.05em;
109+
}
110+
.select,
111+
.input {
112+
padding: 0.4rem 0.5rem;
113+
border: 1px solid var(--border, #ddd);
114+
border-radius: 4px;
115+
background: white;
116+
font-family: inherit;
117+
font-size: 0.95rem;
118+
}
119+
.output {
120+
padding: 0.4rem 0.5rem;
121+
font-family: monospace;
122+
background: white;
123+
border: 1px solid var(--border, #ddd);
124+
border-radius: 4px;
125+
min-height: 1.8rem;
126+
}
127+
.output.placeholder {
128+
color: var(--muted, #999);
129+
}
130+
</style>

src/pages/404.astro

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
import Base from "../layouts/Base.astro"
3+
---
4+
5+
<Base title="404 — Not found" description="The page you requested doesn't exist.">
6+
<section class="container">
7+
<h1>404</h1>
8+
<p class="lead">We couldn't find that page.</p>
9+
<p>It may have been renamed when we migrated the site.</p>
10+
<ul class="suggestions">
11+
<li>← <a href="/">Home</a></li>
12+
<li><a href="/demo">Try the live demo</a></li>
13+
<li><a href="/maps">Browse maps</a></li>
14+
<li><a href="/docs">Read the docs</a></li>
15+
<li><a href="/blog">Read the blog</a></li>
16+
</ul>
17+
<p class="search-hint">
18+
Or <a href="https://github.com/interscript/interscript.github.io/issues">file an issue</a>
19+
if you think this is a bug.
20+
</p>
21+
</section>
22+
</Base>
23+
24+
<style>
25+
.container {
26+
padding: 4rem 0;
27+
text-align: center;
28+
}
29+
h1 {
30+
font-size: clamp(4rem, 12vw, 8rem);
31+
margin: 0;
32+
color: var(--accent);
33+
letter-spacing: -0.05em;
34+
}
35+
.lead {
36+
font-size: 1.5rem;
37+
margin: 1rem 0 0.5rem;
38+
}
39+
.suggestions {
40+
list-style: none;
41+
padding: 0;
42+
margin: 2rem auto;
43+
max-width: 18rem;
44+
text-align: left;
45+
}
46+
.suggestions li {
47+
padding: 0.5rem 0;
48+
border-bottom: 1px solid var(--border);
49+
}
50+
.search-hint {
51+
color: var(--muted);
52+
margin-top: 2rem;
53+
}
54+
a {
55+
color: var(--accent);
56+
}
57+
</style>

0 commit comments

Comments
 (0)