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

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;