updating text reader - wip

This commit is contained in:
2026-03-09 23:27:03 -07:00
parent 9c6438d10e
commit 3da850325e
7 changed files with 2075 additions and 8 deletions

8
app.js
View File

@@ -10,6 +10,7 @@ const { ensureIChingSection } = window.IChingSectionUi || {};
const { ensureKabbalahSection } = window.KabbalahSectionUi || {};
const { ensureCubeSection } = window.CubeSectionUi || {};
const { ensureAlphabetSection } = window.AlphabetSectionUi || {};
const { ensureAlphabetTextSection } = window.AlphabetTextUi || {};
const { ensureZodiacSection } = window.ZodiacSectionUi || {};
const { ensureQuizSection } = window.QuizSectionUi || {};
const { ensureGodsSection } = window.GodsSectionUi || {};
@@ -46,6 +47,7 @@ const kabbalahTreeSectionEl = document.getElementById("kabbalah-tree-section");
const cubeSectionEl = document.getElementById("cube-section");
const alphabetSectionEl = document.getElementById("alphabet-section");
const alphabetLettersSectionEl = document.getElementById("alphabet-letters-section");
const alphabetTextSectionEl = document.getElementById("alphabet-text-section");
const numbersSectionEl = document.getElementById("numbers-section");
const zodiacSectionEl = document.getElementById("zodiac-section");
const quizSectionEl = document.getElementById("quiz-section");
@@ -67,6 +69,7 @@ const openKabbalahTreeEl = document.getElementById("open-kabbalah-tree");
const openKabbalahCubeEl = document.getElementById("open-kabbalah-cube");
const openAlphabetEl = document.getElementById("open-alphabet");
const openAlphabetLettersEl = document.getElementById("open-alphabet-letters");
const openAlphabetTextEl = document.getElementById("open-alphabet-text");
const openNumbersEl = document.getElementById("open-numbers");
const openZodiacEl = document.getElementById("open-zodiac");
const openNatalEl = document.getElementById("open-natal");
@@ -407,6 +410,7 @@ sectionStateUi.init?.({
cubeSectionEl,
alphabetSectionEl,
alphabetLettersSectionEl,
alphabetTextSectionEl,
numbersSectionEl,
zodiacSectionEl,
quizSectionEl,
@@ -428,6 +432,7 @@ sectionStateUi.init?.({
openKabbalahCubeEl,
openAlphabetEl,
openAlphabetLettersEl,
openAlphabetTextEl,
openNumbersEl,
openZodiacEl,
openNatalEl,
@@ -444,6 +449,7 @@ sectionStateUi.init?.({
ensureKabbalahSection,
ensureCubeSection,
ensureAlphabetSection,
ensureAlphabetTextSection,
ensureZodiacSection,
ensureQuizSection,
ensureGodsSection,
@@ -521,6 +527,7 @@ navigationUi.init?.({
openKabbalahCubeEl,
openAlphabetEl,
openAlphabetLettersEl,
openAlphabetTextEl,
openNumbersEl,
openZodiacEl,
openNatalEl,
@@ -537,6 +544,7 @@ navigationUi.init?.({
ensureKabbalahSection,
ensureCubeSection,
ensureAlphabetSection,
ensureAlphabetTextSection,
ensureZodiacSection,
ensureGodsSection,
ensureCalendarSection

View File

@@ -5,6 +5,12 @@
const deckManifestCache = new Map();
let quizCategoriesCache = null;
const quizTemplatesCache = new Map();
let textLibraryCache = null;
const textSourceCache = new Map();
const textSectionCache = new Map();
const textLexiconCache = new Map();
const textLexiconOccurrencesCache = new Map();
const textSearchCache = new Map();
const DATA_ROOT = "data";
const MAGICK_ROOT = DATA_ROOT;
@@ -219,8 +225,14 @@
magickDataCache = null;
deckOptionsCache = null;
quizCategoriesCache = null;
textLibraryCache = null;
deckManifestCache.clear();
quizTemplatesCache.clear();
textSourceCache.clear();
textSectionCache.clear();
textLexiconCache.clear();
textLexiconOccurrencesCache.clear();
textSearchCache.clear();
}
function normalizeTarotName(value) {
@@ -378,6 +390,127 @@
}));
}
async function loadTextLibrary(forceRefresh = false) {
if (!forceRefresh && textLibraryCache) {
return textLibraryCache;
}
textLibraryCache = await fetchJson(buildApiUrl("/api/v1/texts"));
return textLibraryCache;
}
async function loadTextSource(sourceId, forceRefresh = false) {
const normalizedSourceId = String(sourceId || "").trim().toLowerCase();
if (!normalizedSourceId) {
return null;
}
if (!forceRefresh && textSourceCache.has(normalizedSourceId)) {
return textSourceCache.get(normalizedSourceId);
}
const payload = await fetchJson(buildApiUrl(`/api/v1/texts/${encodeURIComponent(normalizedSourceId)}`));
textSourceCache.set(normalizedSourceId, payload);
return payload;
}
async function loadTextSection(sourceId, workId, sectionId, forceRefresh = false) {
const normalizedSourceId = String(sourceId || "").trim().toLowerCase();
const normalizedWorkId = String(workId || "").trim().toLowerCase();
const normalizedSectionId = String(sectionId || "").trim().toLowerCase();
if (!normalizedSourceId || !normalizedWorkId || !normalizedSectionId) {
return null;
}
const cacheKey = `${normalizedSourceId}::${normalizedWorkId}::${normalizedSectionId}`;
if (!forceRefresh && textSectionCache.has(cacheKey)) {
return textSectionCache.get(cacheKey);
}
const payload = await fetchJson(buildApiUrl(
`/api/v1/texts/${encodeURIComponent(normalizedSourceId)}/works/${encodeURIComponent(normalizedWorkId)}/sections/${encodeURIComponent(normalizedSectionId)}`
));
textSectionCache.set(cacheKey, payload);
return payload;
}
async function loadTextLexiconEntry(lexiconId, entryId, forceRefresh = false) {
const normalizedLexiconId = String(lexiconId || "").trim().toLowerCase();
const normalizedEntryId = String(entryId || "").trim().toUpperCase();
if (!normalizedLexiconId || !normalizedEntryId) {
return null;
}
const cacheKey = `${normalizedLexiconId}::${normalizedEntryId}`;
if (!forceRefresh && textLexiconCache.has(cacheKey)) {
return textLexiconCache.get(cacheKey);
}
const payload = await fetchJson(buildApiUrl(
`/api/v1/texts/lexicons/${encodeURIComponent(normalizedLexiconId)}/entries/${encodeURIComponent(normalizedEntryId)}`
));
textLexiconCache.set(cacheKey, payload);
return payload;
}
async function loadTextLexiconOccurrences(lexiconId, entryId, options = {}, forceRefresh = false) {
const normalizedLexiconId = String(lexiconId || "").trim().toLowerCase();
const normalizedEntryId = String(entryId || "").trim().toUpperCase();
const normalizedLimit = Number.parseInt(options?.limit, 10);
const limit = Number.isFinite(normalizedLimit) ? normalizedLimit : 100;
if (!normalizedLexiconId || !normalizedEntryId) {
return null;
}
const cacheKey = `${normalizedLexiconId}::${normalizedEntryId}::${limit}`;
if (!forceRefresh && textLexiconOccurrencesCache.has(cacheKey)) {
return textLexiconOccurrencesCache.get(cacheKey);
}
const payload = await fetchJson(buildApiUrl(
`/api/v1/texts/lexicons/${encodeURIComponent(normalizedLexiconId)}/entries/${encodeURIComponent(normalizedEntryId)}/occurrences`,
{ limit }
));
textLexiconOccurrencesCache.set(cacheKey, payload);
return payload;
}
async function searchTextLibrary(query, options = {}, forceRefresh = false) {
const normalizedQuery = String(query || "").trim();
const normalizedSourceId = String(options?.sourceId || "").trim().toLowerCase();
const normalizedLimit = Number.parseInt(options?.limit, 10);
const limit = Number.isFinite(normalizedLimit) ? normalizedLimit : 50;
if (!normalizedQuery) {
return {
query: "",
normalizedQuery: "",
scope: normalizedSourceId ? { type: "source", sourceId: normalizedSourceId } : { type: "global" },
limit,
totalMatches: 0,
resultCount: 0,
truncated: false,
results: []
};
}
const cacheKey = `${normalizedSourceId || "global"}::${limit}::${normalizedQuery.toLowerCase()}`;
if (!forceRefresh && textSearchCache.has(cacheKey)) {
return textSearchCache.get(cacheKey);
}
const path = normalizedSourceId
? `/api/v1/texts/${encodeURIComponent(normalizedSourceId)}/search`
: "/api/v1/texts/search";
const payload = await fetchJson(buildApiUrl(path, {
q: normalizedQuery,
limit
}));
textSearchCache.set(cacheKey, payload);
return payload;
}
async function loadDeckOptions(forceRefresh = false) {
if (!forceRefresh && deckOptionsCache) {
return deckOptionsCache;
@@ -515,6 +648,12 @@
loadReferenceData,
loadMagickManifest,
loadMagickDataset,
loadTextLibrary,
loadTextSource,
searchTextLibrary,
loadTextSection,
loadTextLexiconEntry,
loadTextLexiconOccurrences,
probeConnection,
pullQuizQuestion,
pullTarotSpread,

View File

@@ -21,7 +21,9 @@
border-bottom: 1px solid #27272a;
background: #18181b;
min-width: 0;
overflow: hidden;
overflow: visible;
position: relative;
z-index: 40;
}
.topbar-home-button {
padding: 0;
@@ -49,7 +51,12 @@
justify-content: flex-start;
overflow-x: auto;
overflow-y: hidden;
padding-bottom: 2px;
padding-bottom: 132px;
margin-bottom: -130px;
pointer-events: none;
}
.topbar-actions > * {
pointer-events: auto;
}
.topbar-actions::-webkit-scrollbar {
height: 6px;
@@ -77,6 +84,10 @@
.topbar-dropdown.is-open .topbar-dropdown-menu {
display: grid;
}
.topbar-dropdown:hover .topbar-dropdown-menu,
.topbar-dropdown:focus-within .topbar-dropdown-menu {
display: grid;
}
.topbar-sub-trigger {
width: 100%;
text-align: left;
@@ -1095,6 +1106,8 @@
align-items: baseline;
justify-content: space-between;
gap: 8px;
position: relative;
z-index: 1;
}
.planet-list-count {
color: #a1a1aa;
@@ -1144,6 +1157,10 @@
gap: 16px;
background: #18181b;
}
.planet-detail-heading {
position: relative;
z-index: 1;
}
.planet-detail-heading h2 {
margin: 0;
font-size: 24px;
@@ -1979,11 +1996,17 @@
.sidebar-toggle-inline {
margin-left: auto;
align-self: center;
position: relative;
z-index: 2;
pointer-events: auto;
}
.detail-toggle-inline {
margin-left: auto;
align-self: center;
position: relative;
z-index: 2;
pointer-events: auto;
}
.sidebar-popout-open {
@@ -3072,6 +3095,16 @@
border-color: #818cf8;
color: #e0e7ff;
}
.alpha-nav-btn--ghost {
background: transparent;
border-color: #3f3f46;
color: #d4d4d8;
}
.alpha-nav-btn--ghost:hover {
background: #18181b;
border-color: #71717a;
color: #fafafa;
}
.alpha-nav-btn.is-selected,
.alpha-nav-btn[aria-pressed="true"] {
background: #4338ca;
@@ -3126,6 +3159,507 @@
color: #a1a1aa;
}
#alphabet-text-section {
height: calc(100vh - 61px);
background: #18181b;
box-sizing: border-box;
overflow: hidden;
}
#alphabet-text-section[hidden] {
display: none;
}
.alpha-text-controls {
display: grid;
gap: 10px;
padding: 12px;
border-top: 1px solid #27272a;
background: #101018;
}
.alpha-text-search-controls {
display: grid;
gap: 10px;
}
.alpha-text-search-controls--sidebar {
padding: 12px;
border-bottom: 1px solid #27272a;
background:
linear-gradient(180deg, rgba(30, 27, 75, 0.22), rgba(16, 16, 24, 0.96));
}
.alpha-text-search-controls--detail {
padding: 14px;
border: 1px solid #2f2f39;
border-radius: 14px;
background:
linear-gradient(180deg, rgba(24, 24, 38, 0.98), rgba(12, 12, 18, 0.98));
box-shadow: inset 0 0 0 1px rgba(99, 102, 241, 0.08);
}
.alpha-text-control {
display: grid;
gap: 4px;
}
.alpha-text-control > span {
color: #a1a1aa;
font-size: 11px;
letter-spacing: 0.02em;
}
.alpha-text-select {
width: 100%;
padding: 7px 8px;
border-radius: 6px;
border: 1px solid #3f3f46;
background: #09090b;
color: #f4f4f5;
box-sizing: border-box;
font-size: 13px;
}
.alpha-text-search-input {
width: 100%;
padding: 9px 10px;
border-radius: 8px;
border: 1px solid #4338ca;
background: #09090b;
color: #f4f4f5;
box-sizing: border-box;
font-size: 13px;
}
.alpha-text-search-input::placeholder {
color: #71717a;
}
.alpha-text-search-actions {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.alpha-text-search-controls--sidebar .alpha-text-search-actions .alpha-nav-btn {
flex: 1 1 0;
min-height: 34px;
}
.alpha-text-search-controls--detail .alpha-text-search-actions .alpha-nav-btn {
min-height: 34px;
padding-inline: 12px;
}
.alpha-text-source-btn {
align-items: flex-start;
}
.alpha-text-source-meta {
font-size: 12px;
line-height: 1.4;
color: #a1a1aa;
}
.alpha-text-detail-body {
display: grid;
gap: 12px;
min-width: 0;
}
.alpha-text-meta-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
align-items: start;
}
.alpha-text-toolbar {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
margin-bottom: 8px;
}
.alpha-text-reader {
display: grid;
gap: 0;
}
.alpha-text-search-summary {
margin: 0 0 10px;
color: #a1a1aa;
font-size: 12px;
line-height: 1.5;
}
.alpha-text-search-results {
display: grid;
gap: 10px;
}
.alpha-text-search-result {
display: grid;
gap: 8px;
width: 100%;
padding: 12px;
border: 1px solid #2f2f39;
border-radius: 12px;
background: #0c0c12;
color: #f4f4f5;
text-align: left;
cursor: pointer;
box-sizing: border-box;
}
.alpha-text-search-result:hover {
border-color: #6366f1;
background: #131325;
}
.alpha-text-search-result.is-active {
border-color: #a5b4fc;
background: #1e1b4b;
box-shadow: inset 0 0 0 1px rgba(199, 210, 254, 0.18);
}
.alpha-text-search-result-head {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 8px;
align-items: baseline;
}
.alpha-text-search-reference {
color: #eef2ff;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.01em;
}
.alpha-text-search-location {
color: #a5b4fc;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.alpha-text-search-preview {
margin: 0;
color: #e4e4e7;
font-size: 14px;
line-height: 1.6;
}
.alpha-text-mark {
padding: 0 2px;
border-radius: 4px;
background: rgba(251, 191, 36, 0.24);
color: #fef3c7;
}
.alpha-text-mark--lexicon {
background: rgba(129, 140, 248, 0.28);
color: #e0e7ff;
box-shadow: inset 0 0 0 1px rgba(165, 180, 252, 0.2);
}
.alpha-text-verse {
display: grid;
gap: 8px;
padding: 12px 0;
border-top: 1px solid #27272a;
}
.alpha-text-verse.is-highlighted {
margin: 0 -12px;
padding: 12px;
border-radius: 12px;
border-top-color: transparent;
background: rgba(67, 56, 202, 0.18);
box-shadow: inset 0 0 0 1px rgba(165, 180, 252, 0.22);
}
.alpha-text-verse:first-child {
border-top: none;
padding-top: 0;
}
.alpha-text-verse-head {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: baseline;
}
.alpha-text-verse-reference {
color: #c4b5fd;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.03em;
}
.alpha-text-verse-text {
margin: 0;
color: #e4e4e7;
font-size: 15px;
line-height: 1.65;
}
.alpha-text-token-grid {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.alpha-text-token {
display: grid;
gap: 3px;
min-width: 96px;
padding: 8px 10px;
border: 1px solid #2f2f39;
border-radius: 10px;
background: #0c0c12;
color: #f4f4f5;
text-align: left;
box-sizing: border-box;
}
.alpha-text-token--interactive {
cursor: pointer;
}
.alpha-text-token--interactive:hover {
background: #141427;
border-color: #6366f1;
}
.alpha-text-token-gloss {
color: #f4f4f5;
font-size: 12px;
font-weight: 600;
line-height: 1.3;
}
.alpha-text-token-original {
color: #c4b5fd;
font-size: 15px;
font-family: var(--font-script-display);
line-height: 1.25;
}
.alpha-text-token-strongs {
color: #a1a1aa;
font-size: 10px;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.alpha-text-lexicon-term {
display: grid;
gap: 8px;
}
.alpha-text-lexicon-head {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.alpha-text-lexicon-id {
display: inline-flex;
align-items: center;
padding: 4px 8px;
border: 1px solid #4f46e5;
border-radius: 999px;
background: #1e1b4b;
color: #c7d2fe;
font-size: 11px;
line-height: 1;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.alpha-text-lexicon-id--button {
cursor: pointer;
}
.alpha-text-lexicon-id--button:hover {
background: #312e81;
border-color: #818cf8;
color: #e0e7ff;
}
.alpha-text-lexicon-id--button[aria-expanded="true"] {
background: #312e81;
border-color: #a5b4fc;
color: #eef2ff;
}
.alpha-text-lexicon-hint {
margin: 0;
color: #a1a1aa;
font-size: 12px;
line-height: 1.5;
}
.alpha-text-lexicon-occurrences {
display: grid;
gap: 10px;
padding-top: 6px;
border-top: 1px solid #27272a;
}
.alpha-text-lexicon-occurrence-list {
display: grid;
gap: 8px;
}
.alpha-text-lexicon-occurrence {
display: grid;
gap: 6px;
width: 100%;
padding: 10px 12px;
border: 1px solid #2f2f39;
border-radius: 10px;
background: #0c0c12;
color: #f4f4f5;
text-align: left;
cursor: pointer;
box-sizing: border-box;
}
.alpha-text-lexicon-occurrence:hover {
background: #141427;
border-color: #6366f1;
}
.alpha-text-search-preview--compact {
font-size: 13px;
line-height: 1.5;
}
.alpha-text-lexicon-popup {
position: fixed;
inset: 0;
z-index: 80;
display: grid;
place-items: center;
padding: 24px;
box-sizing: border-box;
}
.alpha-text-lexicon-popup[hidden] {
display: none;
}
.alpha-text-lexicon-popup-backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.68);
-webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
}
.alpha-text-lexicon-popup-card {
position: relative;
z-index: 1;
width: min(720px, calc(100vw - 32px));
max-height: calc(100vh - 48px);
overflow: auto;
padding: 18px;
border: 1px solid #3f3f46;
border-radius: 16px;
background: linear-gradient(180deg, rgba(24, 24, 27, 0.98), rgba(9, 9, 11, 0.98));
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.6);
box-sizing: border-box;
}
.alpha-text-lexicon-popup-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 14px;
}
.alpha-text-lexicon-popup-heading {
display: grid;
gap: 4px;
}
.alpha-text-lexicon-popup-heading h3 {
margin: 0;
color: #f4f4f5;
font-size: 22px;
line-height: 1.2;
}
.alpha-text-lexicon-popup-subtitle {
margin: 0;
color: #a1a1aa;
font-size: 13px;
line-height: 1.5;
}
.alpha-text-lexicon-popup-close {
padding: 8px 12px;
border: 1px solid #3f3f46;
border-radius: 8px;
background: #18181b;
color: #f4f4f5;
cursor: pointer;
}
.alpha-text-lexicon-popup-close:hover {
background: #27272a;
}
.alpha-text-lexicon-popup-body {
display: grid;
gap: 14px;
}
.alpha-text-lexicon-popup-body .alpha-dl {
margin: 0;
}
.alpha-text-empty {
color: #a1a1aa;
font-size: 13px;
line-height: 1.5;
}
@media (max-width: 720px) {
.alpha-text-meta-grid {
grid-template-columns: 1fr;
}
.alpha-text-token {
min-width: 100%;
}
.alpha-text-lexicon-popup {
padding: 16px;
}
.alpha-text-lexicon-popup-card {
width: min(100vw - 20px, 100%);
max-height: calc(100vh - 32px);
padding: 16px;
}
.alpha-text-lexicon-popup-header {
flex-direction: column;
align-items: stretch;
}
}
/* ── Zodiac section ──────────────────────────────────────────────────── */
#zodiac-section {
height: calc(100vh - 61px);

1322
app/ui-alphabet-text.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -86,6 +86,10 @@
setActiveSection(getActiveSection() === "alphabet-letters" ? "home" : "alphabet-letters");
});
bindClick(elements.openAlphabetTextEl, () => {
setActiveSection(getActiveSection() === "alphabet-text" ? "home" : "alphabet-text");
});
bindClick(elements.openNumbersEl, () => {
setActiveSection(getActiveSection() === "numbers" ? "home" : "numbers");
});

View File

@@ -18,6 +18,7 @@
"cube",
"alphabet",
"alphabet-letters",
"alphabet-text",
"numbers",
"zodiac",
"quiz",
@@ -101,7 +102,8 @@
const isKabbalahMenuOpen = isKabbalahOpen || isKabbalahTreeOpen || isCubeOpen;
const isAlphabetOpen = activeSection === "alphabet";
const isAlphabetLettersOpen = activeSection === "alphabet-letters";
const isAlphabetMenuOpen = isAlphabetOpen || isAlphabetLettersOpen;
const isAlphabetTextOpen = activeSection === "alphabet-text";
const isAlphabetMenuOpen = isAlphabetOpen || isAlphabetLettersOpen || isAlphabetTextOpen;
const isNumbersOpen = activeSection === "numbers";
const isQuizOpen = activeSection === "quiz";
const isGodsOpen = activeSection === "gods";
@@ -122,6 +124,7 @@
setHidden(elements.cubeSectionEl, !isCubeOpen);
setHidden(elements.alphabetSectionEl, !isAlphabetOpen);
setHidden(elements.alphabetLettersSectionEl, !isAlphabetLettersOpen);
setHidden(elements.alphabetTextSectionEl, !isAlphabetTextOpen);
setHidden(elements.numbersSectionEl, !isNumbersOpen);
setHidden(elements.zodiacSectionEl, !isZodiacOpen);
setHidden(elements.quizSectionEl, !isQuizOpen);
@@ -146,6 +149,7 @@
toggleActive(elements.openKabbalahCubeEl, isCubeOpen);
setPressed(elements.openAlphabetEl, isAlphabetMenuOpen);
toggleActive(elements.openAlphabetLettersEl, isAlphabetLettersOpen);
toggleActive(elements.openAlphabetTextEl, isAlphabetTextOpen);
setPressed(elements.openNumbersEl, isNumbersOpen);
toggleActive(elements.openZodiacEl, isZodiacOpen);
toggleActive(elements.openNatalEl, isNatalOpen);
@@ -216,6 +220,11 @@
return;
}
if (isAlphabetTextOpen) {
ensure.ensureAlphabetTextSection?.();
return;
}
if (isNumbersOpen) {
ensure.ensureNumbersSection?.();
return;

View File

@@ -16,7 +16,7 @@
<link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-400.css">
<link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-700.css">
<link rel="stylesheet" href="node_modules/@fontsource/noto-naskh-arabic/arabic-400.css">
<link rel="stylesheet" href="app/styles.css?v=20260309-alphabet-reverse-01">
<link rel="stylesheet" href="app/styles.css?v=20260310-text-search-split-05">
</head>
<body>
<div class="topbar">
@@ -58,6 +58,7 @@
<div class="topbar-dropdown" aria-label="Alphabet menu">
<button id="open-alphabet" class="settings-trigger" type="button" aria-pressed="false" aria-haspopup="menu" aria-controls="alphabet-subpages" aria-expanded="false">Alphabet ▾</button>
<div id="alphabet-subpages" class="topbar-dropdown-menu" role="menu" aria-label="Alphabet subpages">
<button id="open-alphabet-text" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Text</button>
<button id="open-alphabet-letters" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Letter Page</button>
</div>
</div>
@@ -703,6 +704,55 @@
</div>
</section>
<section id="alphabet-text-section" hidden>
<div class="planet-layout">
<aside class="planet-list-panel">
<div class="planet-list-header">
<strong>Alphabet &gt; Text</strong>
<span id="alpha-text-source-count" class="planet-list-count">--</span>
</div>
<form id="alpha-text-global-search-form" class="alpha-text-search-controls alpha-text-search-controls--sidebar">
<label class="alpha-text-control" for="alpha-text-global-search-input">
<span>Search All Texts</span>
<input id="alpha-text-global-search-input" class="alpha-text-search-input" type="search" placeholder="Search across every text source" aria-label="Search all texts">
</label>
<div class="alpha-text-search-actions">
<button id="alpha-text-global-search-submit" class="alpha-nav-btn" type="submit">Search All</button>
<button id="alpha-text-global-search-clear" class="alpha-nav-btn alpha-nav-btn--ghost" type="button" disabled>Clear</button>
</div>
</form>
<div id="alpha-text-source-list" class="planet-card-list" role="listbox" aria-label="Text sources"></div>
<div class="alpha-text-controls">
<label class="alpha-text-control" for="alpha-text-work-select">
<span>Work</span>
<select id="alpha-text-work-select" class="alpha-text-select" aria-label="Select text work"></select>
</label>
<label class="alpha-text-control" for="alpha-text-section-select">
<span>Section</span>
<select id="alpha-text-section-select" class="alpha-text-select" aria-label="Select text section"></select>
</label>
</div>
</aside>
<section class="planet-detail-panel" aria-live="polite">
<div class="planet-detail-heading">
<h2 id="alpha-text-detail-name">--</h2>
<div id="alpha-text-detail-sub" class="planet-detail-type">Select a text source to begin reading</div>
</div>
<form id="alpha-text-local-search-form" class="alpha-text-search-controls alpha-text-search-controls--detail">
<label class="alpha-text-control" for="alpha-text-local-search-input">
<span>Search This Source</span>
<input id="alpha-text-local-search-input" class="alpha-text-search-input" type="search" placeholder="Search within the selected source" aria-label="Search current text source">
</label>
<div class="alpha-text-search-actions">
<button id="alpha-text-local-search-submit" class="alpha-nav-btn" type="submit">Search Source</button>
<button id="alpha-text-local-search-clear" class="alpha-nav-btn alpha-nav-btn--ghost" type="button" disabled>Clear</button>
</div>
</form>
<div id="alpha-text-detail-body" class="alpha-text-detail-body"></div>
</section>
</div>
</section>
<section id="cube-section" hidden>
<div class="kab-layout">
<aside class="kab-tree-panel">
@@ -898,7 +948,7 @@
<script src="node_modules/astronomy-engine/astronomy.browser.min.js"></script>
<script src="app/astro-calcs.js"></script>
<script src="app/app-config.js?v=20260309-gate"></script>
<script src="app/data-service.js?v=20260309-gate"></script>
<script src="app/data-service.js?v=20260310-text-search-split-04"></script>
<script src="app/calendar-events.js"></script>
<script src="app/card-images.js?v=20260309-gate"></script>
<script src="app/ui-tarot-lightbox.js?v=20260307b"></script>
@@ -943,6 +993,7 @@
<script src="app/ui-alphabet-detail.js?v=20260309-enochian-api"></script>
<script src="app/ui-alphabet-kabbalah.js"></script>
<script src="app/ui-alphabet.js?v=20260308b"></script>
<script src="app/ui-alphabet-text.js?v=20260310-text-search-split-06"></script>
<script src="app/ui-zodiac-references.js"></script>
<script src="app/ui-zodiac.js"></script>
<script src="app/ui-quiz-bank-builtins-domains.js"></script>
@@ -960,12 +1011,12 @@
<script src="app/ui-tarot-spread.js"></script>
<script src="app/ui-settings.js?v=20260309-gate"></script>
<script src="app/ui-chrome.js"></script>
<script src="app/ui-navigation.js?v=20260309-home-button"></script>
<script src="app/ui-navigation.js?v=20260309-alphabet-text-01"></script>
<script src="app/ui-calendar-formatting.js?v=20260307b"></script>
<script src="app/ui-calendar-visuals.js?v=20260307b"></script>
<script src="app/ui-home-calendar.js"></script>
<script src="app/ui-section-state.js?v=20260309-home-button"></script>
<script src="app/ui-section-state.js?v=20260309-alphabet-text-01"></script>
<script src="app/app-runtime.js?v=20260309-gate"></script>
<script src="app.js?v=20260309-now-toggle"></script>
<script src="app.js?v=20260309-alphabet-text-01"></script>
</body>
</html>