diff --git a/app.js b/app.js index 97d6909..a5bf17f 100644 --- a/app.js +++ b/app.js @@ -31,6 +31,7 @@ const appRuntime = window.TarotAppRuntime || {}; const statusEl = document.getElementById("status"); const monthStripEl = document.getElementById("month-strip"); const calendarEl = document.getElementById("calendar"); +const timelineSectionEl = document.getElementById("timeline-section"); const calendarSectionEl = document.getElementById("calendar-section"); const holidaySectionEl = document.getElementById("holiday-section"); const tarotSectionEl = document.getElementById("tarot-section"); @@ -44,12 +45,15 @@ const kabbalahSectionEl = document.getElementById("kabbalah-section"); 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 numbersSectionEl = document.getElementById("numbers-section"); const zodiacSectionEl = document.getElementById("zodiac-section"); const quizSectionEl = document.getElementById("quiz-section"); const godsSectionEl = document.getElementById("gods-section"); const enochianSectionEl = document.getElementById("enochian-section"); +const openHomeEl = document.getElementById("open-home"); const openCalendarEl = document.getElementById("open-calendar"); +const openCalendarTimelineEl = document.getElementById("open-calendar-timeline"); const openCalendarMonthsEl = document.getElementById("open-calendar-months"); const openHolidaysEl = document.getElementById("open-holidays"); const openTarotEl = document.getElementById("open-tarot"); @@ -62,6 +66,7 @@ const openKabbalahEl = document.getElementById("open-kabbalah"); 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 openNumbersEl = document.getElementById("open-numbers"); const openZodiacEl = document.getElementById("open-zodiac"); const openNatalEl = document.getElementById("open-natal"); @@ -72,6 +77,7 @@ const latEl = document.getElementById("lat"); const lngEl = document.getElementById("lng"); const nowSkyLayerEl = document.getElementById("now-sky-layer"); const nowPanelEl = document.getElementById("now-panel"); +const nowOverlayToggleEl = document.getElementById("now-overlay-toggle"); const connectionGateEl = document.getElementById("connection-gate"); const connectionGateBaseUrlEl = document.getElementById("connection-gate-base-url"); const connectionGateApiKeyEl = document.getElementById("connection-gate-api-key"); @@ -386,6 +392,7 @@ sectionStateUi.init?.({ calendarEl, monthStripEl, nowPanelEl, + timelineSectionEl, calendarSectionEl, holidaySectionEl, tarotSectionEl, @@ -399,12 +406,15 @@ sectionStateUi.init?.({ kabbalahTreeSectionEl, cubeSectionEl, alphabetSectionEl, + alphabetLettersSectionEl, numbersSectionEl, zodiacSectionEl, quizSectionEl, godsSectionEl, enochianSectionEl, + openHomeEl, openCalendarEl, + openCalendarTimelineEl, openCalendarMonthsEl, openHolidaysEl, openTarotEl, @@ -417,6 +427,7 @@ sectionStateUi.init?.({ openKabbalahTreeEl, openKabbalahCubeEl, openAlphabetEl, + openAlphabetLettersEl, openNumbersEl, openZodiacEl, openNatalEl, @@ -475,6 +486,16 @@ homeUi.init?.({ nowPanelEl, getCurrentGeo: () => appRuntime.getCurrentGeo?.() || null }); + +if (nowOverlayToggleEl && nowPanelEl) { + const syncNowOverlayVisibility = () => { + nowPanelEl.classList.toggle("is-overlay-hidden", !nowOverlayToggleEl.checked); + }; + + nowOverlayToggleEl.addEventListener("change", syncNowOverlayVisibility); + syncNowOverlayVisibility(); +} + navigationUi.init?.({ tarotSpreadUi, getActiveSection: () => sectionStateUi.getActiveSection?.() || "home", @@ -484,7 +505,9 @@ navigationUi.init?.({ normalizeNumberValue, selectNumberEntry, elements: { + openHomeEl, openCalendarEl, + openCalendarTimelineEl, openCalendarMonthsEl, openHolidaysEl, openTarotEl, @@ -497,6 +520,7 @@ navigationUi.init?.({ openKabbalahTreeEl, openKabbalahCubeEl, openAlphabetEl, + openAlphabetLettersEl, openNumbersEl, openZodiacEl, openNatalEl, diff --git a/app/data-service.js b/app/data-service.js index 92aa64c..450d072 100644 --- a/app/data-service.js +++ b/app/data-service.js @@ -366,6 +366,12 @@ })); } + async function loadGematriaWordsByValue(value) { + return fetchJson(buildApiUrl("/api/v1/gematria/words", { + value + })); + } + async function loadDeckOptions(forceRefresh = false) { if (!forceRefresh && deckOptionsCache) { return deckOptionsCache; @@ -495,6 +501,7 @@ isApiEnabled, loadDeckManifest, loadDeckOptions, + loadGematriaWordsByValue, loadQuizCategories, loadQuizTemplates, loadTarotCards, diff --git a/app/styles.css b/app/styles.css index bdc2445..e125a68 100644 --- a/app/styles.css +++ b/app/styles.css @@ -10,6 +10,7 @@ font-family: system-ui, -apple-system, "Segoe UI", Roboto, var(--font-script-main); background: #0f0f14; color: #f4f4f5; + overflow-x: hidden; } .topbar { padding: 12px 16px; @@ -19,11 +20,39 @@ gap: 10px; border-bottom: 1px solid #27272a; background: #18181b; + min-width: 0; + overflow: hidden; + } + .topbar-home-button { + padding: 0; + border: 0; + background: transparent; + color: #f4f4f5; + cursor: pointer; + font-size: 18px; + font-weight: 700; + letter-spacing: 0.01em; + flex: 0 0 auto; + } + .topbar-home-button:hover { + color: #fbbf24; + } + .topbar-home-button[aria-pressed="true"] { + color: #fbbf24; } .topbar-actions { display: flex; align-items: center; gap: 8px; + flex: 1 1 auto; + min-width: 0; + justify-content: flex-start; + overflow-x: auto; + overflow-y: hidden; + padding-bottom: 2px; + } + .topbar-actions::-webkit-scrollbar { + height: 6px; } .topbar-dropdown { position: relative; @@ -138,19 +167,19 @@ color: #86efac; } .connection-gate-status[data-tone="pending"] { - color: #fcd34d; + color: #fde68a; } .connection-gate-actions { display: flex; - flex-wrap: wrap; justify-content: flex-end; - gap: 10px; - margin-top: 18px; + gap: 12px; + margin-top: 16px; } .connection-gate-actions button { - padding: 9px 14px; - border-radius: 8px; + appearance: none; border: 1px solid #3f3f46; + border-radius: 10px; + padding: 10px 14px; background: #27272a; color: #f4f4f5; cursor: pointer; @@ -159,13 +188,12 @@ background: #3f3f46; } #connection-gate-connect { - border-color: #d97706; - background: linear-gradient(180deg, #f59e0b, #d97706); + border-color: #f59e0b; color: #111827; - font-weight: 700; + background: linear-gradient(180deg, #fbbf24, #ea580c); } #connection-gate-connect:hover { - background: linear-gradient(180deg, #fbbf24, #ea580c); + background: linear-gradient(180deg, #f59e0b, #d97706); } #tarot-section { height: calc(100vh - 61px); @@ -199,6 +227,14 @@ box-sizing: border-box; overflow: hidden; } + #timeline-section { + height: calc(100vh - 61px); + background: #18181b; + box-sizing: border-box; + overflow: hidden; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + } #holiday-section { height: calc(100vh - 61px); background: #18181b; @@ -220,6 +256,9 @@ #calendar-section[hidden] { display: none; } + #timeline-section[hidden] { + display: none; + } #holiday-section[hidden] { display: none; } @@ -2243,31 +2282,64 @@ } /* ── Alphabet section ────────────────────────────────────────────────── */ - #alphabet-section { + #alphabet-section, + #alphabet-letters-section { height: calc(100vh - 61px); background: #18181b; box-sizing: border-box; overflow: hidden; - display: grid; - grid-template-rows: auto minmax(0, 1fr); } - #alphabet-section[hidden] { display: none; } + + #alphabet-section { + overflow: auto; + } + + #alphabet-section[hidden], + #alphabet-letters-section[hidden] { display: none; } + + .alpha-lookup-shell { + min-height: 100%; + padding: 18px 16px 24px; + box-sizing: border-box; + display: grid; + align-items: start; + } .alpha-special-top { - padding: 10px 12px 8px; - border-bottom: 1px solid #27272a; - overflow: auto; - background: #151520; + width: min(1120px, 100%); + margin: 0 auto; + padding: 14px; + border: 1px solid #27272a; + border-radius: 18px; + overflow: visible; + background: linear-gradient(180deg, #171726 0%, #12121a 100%); + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.28); } .alpha-gematria-card { display: grid; + gap: 10px; + } + + .alpha-gematria-toolbar { + display: flex; + justify-content: flex-end; + } + + .alpha-gematria-toggle { + display: inline-flex; + align-items: center; gap: 8px; + color: #d4d4d8; + font-size: 12px; + cursor: pointer; + -webkit-user-select: none; + user-select: none; } .alpha-gematria-controls { display: grid; - grid-template-columns: minmax(200px, 260px) minmax(0, 1fr); + grid-template-columns: minmax(220px, 260px) minmax(0, 1fr); gap: 8px; align-items: start; } @@ -2278,6 +2350,10 @@ min-width: 0; } + .alpha-gematria-field.is-disabled { + opacity: 0.55; + } + .alpha-gematria-field > span { color: #a1a1aa; font-size: 11px; @@ -2316,6 +2392,70 @@ word-break: break-word; } + .alpha-gematria-matches { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 10px; + max-height: min(52vh, 560px); + overflow: auto; + padding-right: 4px; + } + + .alpha-gematria-matches[hidden] { + display: none; + } + + .alpha-gematria-match { + display: grid; + gap: 8px; + padding: 12px; + border: 1px solid #2f2f39; + border-radius: 12px; + background: #0c0c12; + } + + .alpha-gematria-match-word { + color: #f4f4f5; + font-size: 15px; + font-weight: 700; + line-height: 1.2; + } + + .alpha-gematria-match-definition { + color: #d4d4d8; + font-size: 12px; + line-height: 1.45; + } + + .alpha-gematria-match-ciphers { + display: flex; + flex-wrap: wrap; + gap: 6px; + } + + .alpha-gematria-match-cipher { + display: inline-flex; + align-items: center; + padding: 4px 8px; + border: 1px solid #3f3f46; + border-radius: 999px; + background: #18181b; + color: #c4b5fd; + font-size: 11px; + line-height: 1; + } + + .alpha-gematria-match-empty { + grid-column: 1 / -1; + padding: 14px; + border: 1px dashed #3f3f46; + border-radius: 12px; + background: #101018; + color: #a1a1aa; + font-size: 12px; + line-height: 1.45; + } + .alpha-tabs { display: flex; flex-wrap: wrap; @@ -3675,17 +3815,17 @@ border-left: 0; } #now-panel { + --now-square-size: min(85vmin, calc(100vw - 172px), calc(100svh - 92px)); position: relative; overflow: hidden; - padding: 38px 24px 76px; - min-height: clamp(740px, 90vh, 1140px); - background: #1e1e24; + height: calc(100svh - 88px); + min-height: calc(100svh - 88px); + padding: 10px clamp(68px, 14vw, 220px); + background: #090c16; border-bottom: 1px solid #27272a; display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0; - align-items: start; - align-content: start; + place-items: center; + isolation: isolate; } #now-panel[hidden] { @@ -3694,85 +3834,140 @@ #now-sky-layer { position: absolute; - width: calc(100% + 1360px); - height: calc(100% + 380px); - top: -200px; - left: -830px; + width: max(calc(var(--now-square-size) * 3.25), calc(100% + 420px)); + height: max(calc(var(--now-square-size) * 2.28), calc(100% + 250px)); + top: 50.5%; + left: 44%; + transform: translate(-50%, -50%); + transform-origin: center center; z-index: 0; pointer-events: none; opacity: 1; border: none; - filter: none; + filter: saturate(1.04); background-color: #000; } #now-panel::before { content: ""; - grid-column: 1 / -1; - grid-row: 3; - height: clamp(250px, 31vh, 430px); + position: absolute; + inset: 0; + z-index: 1; pointer-events: none; + background: + radial-gradient(circle at 50% 57%, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.04) 22%, rgba(7, 11, 24, 0) 48%), + radial-gradient(circle at 50% 57%, rgba(6, 10, 22, 0) 0%, rgba(6, 10, 22, 0) 38%, rgba(6, 10, 22, 0.38) 72%, rgba(3, 5, 12, 0.66) 100%); } #now-panel::after { content: ""; position: absolute; inset: 0; - z-index: 1; - background: transparent; + z-index: 2; + background: linear-gradient(180deg, rgba(3, 5, 12, 0.34) 0%, rgba(3, 5, 12, 0.08) 20%, rgba(3, 5, 12, 0.18) 100%); + pointer-events: none; + } + #now-panel.is-overlay-hidden::before, + #now-panel.is-overlay-hidden::after { + opacity: 0; + } + .now-panel-controls { + position: absolute; + top: 14px; + right: clamp(18px, 4vw, 44px); + z-index: 5; + display: flex; + justify-content: flex-end; + } + .now-panel-toggle { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + border: 1px solid rgba(255, 255, 255, 0.72); + border-radius: 999px; + background: rgba(0, 0, 0, 0.82); + color: #f8fafc; + font-size: 11px; + letter-spacing: 0.04em; + text-transform: uppercase; + -webkit-user-select: none; + user-select: none; + } + .now-panel-toggle input { + margin: 0; + accent-color: #ffffff; + } + .now-panel-square { + position: relative; + z-index: 3; + box-sizing: border-box; + width: var(--now-square-size); + max-width: 780px; + aspect-ratio: 1; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-rows: minmax(0, 1.08fr) minmax(0, 0.92fr); + gap: clamp(8px, 1.3vmin, 14px); + padding: clamp(18px, 2.6vmin, 28px) clamp(12px, 1.8vmin, 18px) clamp(14px, 2vmin, 20px); + border-radius: 28px; + background: #000000; + border: 2px solid rgba(255, 255, 255, 0.88); + box-shadow: none; + overflow: hidden; + } + #now-panel.is-overlay-hidden .now-panel-square { + opacity: 0; + visibility: hidden; pointer-events: none; } .now-section { position: relative; - z-index: 2; + z-index: 1; min-width: 0; + min-height: 0; box-sizing: border-box; - padding: 10px 12px 12px; + padding: clamp(8px, 1.3vmin, 12px) clamp(8px, 1.4vmin, 12px) 0; display: flex; flex-direction: column; align-items: center; + justify-content: flex-start; text-align: center; - border-radius: 14px; + border-radius: 0; overflow: hidden; isolation: isolate; border: 0; - box-shadow: 0 8px 18px rgba(0, 0, 0, 0.24); - transition: color 180ms ease, box-shadow 180ms ease; + box-shadow: none; + transition: color 180ms ease, box-shadow 180ms ease, transform 180ms ease; } .now-section::before { - content: ""; - position: absolute; - width: clamp(210px, 56%, 340px); - height: 35%; - left: 50%; - bottom: 6px; - transform: translateX(-50%); - z-index: 0; - pointer-events: none; - opacity: 0.74; - border-radius: 999px; - background: - radial-gradient(56% 58% at 24% 44%, rgba(26, 32, 47, 0.64) 0%, rgba(26, 32, 47, 0.24) 62%, transparent 100%), - radial-gradient(52% 56% at 78% 40%, rgba(2, 6, 23, 0.58) 0%, rgba(2, 6, 23, 0.22) 62%, transparent 100%), - radial-gradient(64% 70% at 50% 72%, rgba(8, 12, 28, 0.54) 0%, rgba(8, 12, 28, 0.2) 64%, transparent 100%); - filter: saturate(0.92); + display: none; } .now-section > * { position: relative; z-index: 1; } + .now-section-hour { + grid-area: 1 / 1; + } + .now-section-moon { + grid-area: 1 / 2; + } + .now-section-decan { + grid-area: 1 / 3; + } #now-panel.is-day .now-section { - color: #111827; - box-shadow: 0 7px 16px rgba(15, 23, 42, 0.14); + color: #f8fafc; + background: transparent; + border-color: transparent; + box-shadow: none; } #now-panel.is-day .now-section::before { - background: - radial-gradient(56% 58% at 24% 44%, rgba(255, 255, 255, 0.86) 0%, rgba(255, 255, 255, 0.34) 62%, transparent 100%), - radial-gradient(52% 56% at 78% 40%, rgba(255, 255, 255, 0.82) 0%, rgba(255, 255, 255, 0.3) 62%, transparent 100%), - radial-gradient(64% 70% at 50% 72%, rgba(241, 245, 249, 0.74) 0%, rgba(226, 232, 240, 0.28) 64%, transparent 100%); - filter: saturate(1.02); + display: none; } #now-panel.is-night .now-section { color: #f8fafc; - box-shadow: 0 9px 20px rgba(0, 0, 0, 0.34); + background: transparent; + border-color: transparent; + box-shadow: none; } #now-panel.is-day .now-title, #now-panel.is-day .now-tarot, @@ -3780,8 +3975,8 @@ #now-panel.is-day .now-countdown-next, #now-panel.is-day .now-countdown-value, #now-panel.is-day .now-primary { - color: #111827; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3); + color: #f8fafc; + text-shadow: 0 1px 3px rgba(2, 6, 23, 0.75); } #now-panel.is-day .now-primary-hour { color: #581c87; @@ -3805,11 +4000,12 @@ text-shadow: 0 1px 3px rgba(2, 6, 23, 0.75); } .now-card { - margin: 6px 0 10px 0; - width: 150px; - height: 225px; + margin: 2px 0 6px 0; + width: min(64%, 144px); + height: auto; + aspect-ratio: 2 / 3; object-fit: cover; - border-radius: 6px; + border-radius: 8px; border: 1px solid #3f3f46; display: none; } @@ -3822,14 +4018,15 @@ box-shadow: 0 10px 22px rgba(2, 6, 23, 0.58); } .now-countdown-row { - margin-top: 4px; - width: min(100%, 340px); + margin-top: 2px; + width: min(100%, 220px); display: grid; - grid-template-columns: 130px 1fr; - column-gap: 10px; + grid-template-columns: auto minmax(0, 1fr); + column-gap: 6px; align-items: center; - color: #a1a1aa; - font-size: 12px; + color: #cbd5e1; + font-size: 14px; + line-height: 1.18; text-align: left; } .now-countdown-value { @@ -3839,72 +4036,85 @@ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; } .now-countdown-next { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + white-space: normal; + overflow: visible; + text-overflow: clip; } .now-title { - font-size: 11px; - color: #a1a1aa; + font-size: 12px; + color: #cbd5e1; text-transform: uppercase; letter-spacing: 0.05em; - margin-bottom: 4px; + line-height: 1.12; + margin-bottom: 0; } .now-stats-section { position: relative; - z-index: 2; + z-index: 1; grid-column: 1 / -1; grid-row: 2; - margin-top: 10px; - border-radius: 14px; - padding: 12px 14px; - background: rgba(15, 23, 42, 0.42); - border: 1px solid rgba(148, 163, 184, 0.35); - box-shadow: 0 8px 18px rgba(0, 0, 0, 0.24); + min-width: 0; + min-height: 0; + border-radius: 0; + padding: clamp(8px, 1.25vmin, 12px) clamp(12px, 1.8vmin, 18px) 0; + background: transparent; + border: 0; + box-shadow: none; display: grid; - gap: 8px; + grid-template-columns: minmax(0, 1fr); + grid-template-rows: auto auto auto; + gap: 4px; + overflow: auto; } .now-stats-title { - font-size: 11px; - color: #cbd5e1; + font-size: 14px; + line-height: 1.08; + color: #e2e8f0; text-transform: uppercase; - letter-spacing: 0.06em; + letter-spacing: 0.07em; font-weight: 700; } .now-stats-sabian { - font-size: 14px; - font-weight: 600; - line-height: 1.4; + font-size: clamp(17px, 2.5vmin, 23px); + font-weight: 550; + line-height: 1.32; color: #f8fafc; white-space: pre-line; + overflow: visible; + max-height: none; } .now-stats-planets { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 6px 10px; - font-size: 13px; - line-height: 1.35; + gap: 4px 12px; + font-size: clamp(15px, 1.9vmin, 17px); + line-height: 1.3; color: #e2e8f0; + align-content: start; + min-height: 0; + overflow: visible; } .now-stats-planet { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + white-space: normal; + overflow: visible; + text-overflow: clip; + overflow-wrap: anywhere; + line-height: 1.28; font-variant-numeric: tabular-nums; } #now-panel.is-day .now-stats-section { - background: rgba(255, 255, 255, 0.58); - border-color: rgba(15, 23, 42, 0.2); - box-shadow: 0 7px 16px rgba(15, 23, 42, 0.12); + background: transparent; + border-color: transparent; + box-shadow: none; } #now-panel.is-day .now-stats-title { - color: #475569; - text-shadow: none; + color: #e2e8f0; + text-shadow: 0 1px 3px rgba(2, 6, 23, 0.75); } #now-panel.is-day .now-stats-sabian, #now-panel.is-day .now-stats-planets { - color: #0f172a; - text-shadow: none; + color: #f8fafc; + text-shadow: 0 1px 3px rgba(2, 6, 23, 0.75); } #now-panel.is-night .now-stats-title, #now-panel.is-night .now-stats-sabian, @@ -3912,8 +4122,9 @@ text-shadow: 0 1px 3px rgba(2, 6, 23, 0.75); } .now-primary { - font-size: 20px; + font-size: clamp(16px, 2.4vmin, 24px); font-weight: 600; + line-height: 1.15; } .now-primary-hour { color: #a855f7; @@ -3925,9 +4136,10 @@ color: #f97316; } .now-tarot { - font-size: 13px; + font-size: clamp(12px, 1.7vmin, 16px); color: #d4d4d8; - margin-top: 2px; + line-height: 1.12; + margin-top: 1px; } .toastui-calendar-timegrid-time-column .toastui-calendar-timegrid-current-time { @@ -4214,9 +4426,37 @@ opacity: 0; } - .toastui-calendar-timegrid { - height: 240%; - min-height: 1200px; + .toastui-calendar-layout.toastui-calendar-week-view { + height: 100% !important; + min-height: 0; + display: grid; + grid-template-rows: 52px minmax(0, 1fr); + } + + .toastui-calendar-layout.toastui-calendar-week-view > .toastui-calendar-panel.toastui-calendar-allday, + .toastui-calendar-layout.toastui-calendar-week-view > :has(> .toastui-calendar-panel-resizer) { + display: none !important; + } + + .toastui-calendar-allday-panel, + .toastui-calendar-panel.toastui-calendar-allday, + .toastui-calendar-panel.toastui-calendar-allday .toastui-calendar-panel-title, + .toastui-calendar-panel.toastui-calendar-allday .toastui-calendar-panel-grid-wrapper, + .toastui-calendar-panel.toastui-calendar-allday .toastui-calendar-panel-allday-events { + display: none !important; + } + + .toastui-calendar-layout.toastui-calendar-week-view > .toastui-calendar-panel.toastui-calendar-time { + grid-row: 2; + height: auto !important; + min-height: 0; + overflow: hidden !important; + } + + .toastui-calendar-timegrid, + .toastui-calendar-timegrid-scroll-area { + height: 100% !important; + min-height: 0 !important; } .toastui-calendar-event-time { @@ -4268,6 +4508,19 @@ } @media (max-width: 900px) { + #now-panel { + --now-square-size: min(calc(100vw - 110px), calc(100svh - 94px)); + padding: 8px clamp(30px, 8vw, 82px); + } + .now-panel-square { + border-radius: 22px; + } + .now-card { + width: min(64%, 122px); + } + .now-stats-planets { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } .planet-layout { grid-template-columns: minmax(0, 1fr); } @@ -4283,6 +4536,114 @@ } } - #calendar { - height: calc(100vh - 96px); + @media (max-width: 640px) { + #now-panel { + --now-square-size: min(calc(100vw - 74px), calc(100svh - 90px)); + padding: 6px clamp(20px, 6vw, 44px); + } + .now-panel-square { + gap: 6px; + padding: 6px; + } + .now-section { + padding: 7px; + } + .now-card { + width: min(60%, 96px); + } + .now-primary { + font-size: clamp(12px, 3vw, 16px); + } + .now-countdown-row { + width: 100%; + grid-template-columns: minmax(0, 1fr); + gap: 2px; + font-size: 10px; + text-align: center; + } + .now-countdown-value, + .now-countdown-next { + text-align: center; + } + .now-stats-planets { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + } + + @media (max-width: 480px) { + #now-panel { + --now-square-size: min(calc(100vw - 46px), calc(100svh - 86px)); + } + .now-panel-square { + border-radius: 18px; + } + .now-section, + .now-stats-section { + border-radius: 16px; + padding: 7px; + } + .now-title, + .now-stats-title { + font-size: 9px; + } + .now-tarot, + .now-stats-sabian, + .now-stats-planets { + font-size: 10px; + } + .now-stats-planets { + grid-template-columns: minmax(0, 1fr); + } + } + + @media (max-height: 520px) { + #now-panel { + --now-square-size: min(76vmin, calc(100vw - 78px), calc(100svh - 108px)); + height: calc(100svh - 101px); + min-height: calc(100svh - 101px); + padding: 6px clamp(28px, 9vw, 72px); + } + .now-panel-square { + grid-template-rows: minmax(0, 0.92fr) minmax(0, 1.08fr); + gap: 6px; + padding: 16px 6px 8px; + border-radius: 18px; + } + .now-section, + .now-stats-section { + border-radius: 16px; + padding: 6px; + } + .now-card { + width: min(66%, 92px); + margin-bottom: 6px; + } + .now-title { + font-size: 8px; + } + .now-primary { + font-size: 15px; + } + .now-stats-title { + font-size: 11px; + } + .now-stats-sabian { + font-size: 11px; + line-height: 1.42; + } + .now-stats-planets { + font-size: 11px; + line-height: 1.42; + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .now-tarot, + .now-countdown-row { + display: none; + } + } + + #calendar { + height: 100%; + min-height: 0; + overflow: hidden; } diff --git a/app/ui-alphabet-gematria.js b/app/ui-alphabet-gematria.js index 2ba1a69..0d6ef9d 100644 --- a/app/ui-alphabet-gematria.js +++ b/app/ui-alphabet-gematria.js @@ -8,7 +8,11 @@ cipherEl: null, inputEl: null, resultEl: null, - breakdownEl: null + breakdownEl: null, + modeToggleEl: null, + matchesEl: null, + inputLabelEl: null, + cipherLabelEl: null }) }; @@ -17,8 +21,12 @@ db: null, listenersBound: false, activeCipherId: "", - inputText: "", - scriptCharMap: new Map() + forwardInputText: "", + reverseInputText: "", + activeMode: "forward", + scriptCharMap: new Map(), + reverseLookupCache: new Map(), + reverseRequestId: 0 }; function getAlphabets() { @@ -34,10 +42,32 @@ cipherEl: null, inputEl: null, resultEl: null, - breakdownEl: null + breakdownEl: null, + modeToggleEl: null, + matchesEl: null, + inputLabelEl: null, + cipherLabelEl: null }; } + function isReverseMode() { + return state.activeMode === "reverse"; + } + + function getCurrentInputText() { + return isReverseMode() + ? state.reverseInputText + : state.forwardInputText; + } + + function formatCount(value) { + const numericValue = Number(value); + if (!Number.isFinite(numericValue)) { + return "0"; + } + return numericValue.toLocaleString(); + } + function getFallbackGematriaDb() { return { baseAlphabet: "abcdefghijklmnopqrstuvwxyz", @@ -47,6 +77,12 @@ name: "Simple Ordinal", description: "A=1 ... Z=26", values: Array.from({ length: 26 }, (_, index) => index + 1) + }, + { + id: "decadic-cipher", + name: "Decadic Cipher", + description: "A=1 ... I=9, J=10 ... R=90, S=100 ... Z=800", + values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800] } ] }; @@ -238,6 +274,221 @@ cipherEl.value = state.activeCipherId; } + function setMatchesMessage(matchesEl, message) { + if (!matchesEl) { + return; + } + + matchesEl.replaceChildren(); + const emptyEl = document.createElement("div"); + emptyEl.className = "alpha-gematria-match-empty"; + emptyEl.textContent = message; + matchesEl.appendChild(emptyEl); + } + + function clearReverseMatches(matchesEl) { + if (!matchesEl) { + return; + } + + matchesEl.replaceChildren(); + matchesEl.hidden = true; + } + + function updateModeUi() { + const { + cipherEl, + inputEl, + modeToggleEl, + matchesEl, + inputLabelEl, + cipherLabelEl + } = getElements(); + + const reverseMode = isReverseMode(); + + if (modeToggleEl) { + modeToggleEl.checked = reverseMode; + } + + if (inputLabelEl) { + inputLabelEl.textContent = reverseMode ? "Value" : "Text"; + } + + if (cipherLabelEl) { + cipherLabelEl.textContent = reverseMode ? "Cipher (disabled in reverse mode)" : "Cipher"; + } + + if (cipherEl) { + cipherEl.disabled = reverseMode; + cipherEl.closest(".alpha-gematria-field")?.classList.toggle("is-disabled", reverseMode); + } + + if (inputEl) { + inputEl.placeholder = reverseMode ? "Enter a whole number, e.g. 33" : "Type or paste text"; + inputEl.inputMode = reverseMode ? "numeric" : "text"; + inputEl.spellcheck = !reverseMode; + + const nextValue = getCurrentInputText(); + if (inputEl.value !== nextValue) { + inputEl.value = nextValue; + } + } + + if (!reverseMode) { + clearReverseMatches(matchesEl); + } + } + + function parseReverseLookupValue(rawValue) { + const normalizedValue = String(rawValue || "").trim(); + if (!normalizedValue) { + return null; + } + + if (!/^\d+$/.test(normalizedValue)) { + return Number.NaN; + } + + const numericValue = Number(normalizedValue); + if (!Number.isSafeInteger(numericValue)) { + return Number.NaN; + } + + return numericValue; + } + + async function loadReverseLookup(value) { + const cacheKey = String(value); + if (state.reverseLookupCache.has(cacheKey)) { + return state.reverseLookupCache.get(cacheKey); + } + + const payload = await window.TarotDataService?.loadGematriaWordsByValue?.(value); + state.reverseLookupCache.set(cacheKey, payload); + return payload; + } + + function renderReverseLookupMatches(payload, numericValue) { + const { resultEl, breakdownEl, matchesEl } = getElements(); + if (!resultEl || !breakdownEl || !matchesEl) { + return; + } + + const matches = Array.isArray(payload?.matches) ? payload.matches : []; + const count = Number(payload?.count); + const displayCount = Number.isFinite(count) ? count : matches.length; + const ciphers = Array.isArray(payload?.ciphers) ? payload.ciphers : []; + const cipherCount = Number(payload?.cipherCount); + const displayCipherCount = Number.isFinite(cipherCount) ? cipherCount : ciphers.length; + const visibleMatches = matches.slice(0, 120); + + resultEl.textContent = `Value: ${formatCount(numericValue)}`; + + if (!displayCount) { + breakdownEl.textContent = "No words matched this reverse gematria value."; + matchesEl.hidden = false; + setMatchesMessage(matchesEl, "No matches found in the reverse gematria index."); + return; + } + + const topCipherSummary = ciphers + .slice(0, 6) + .map((cipher) => `${String(cipher?.name || cipher?.id || "Unknown")} ${formatCount(cipher?.count)}`) + .join(" · "); + + breakdownEl.textContent = `Found ${formatCount(displayCount)} matches across ${formatCount(displayCipherCount)} ciphers.${topCipherSummary ? ` Top ciphers: ${topCipherSummary}.` : ""}${displayCount > visibleMatches.length ? ` Showing first ${formatCount(visibleMatches.length)}.` : ""}`; + + const fragment = document.createDocumentFragment(); + visibleMatches.forEach((match) => { + const cardEl = document.createElement("article"); + cardEl.className = "alpha-gematria-match"; + + const wordEl = document.createElement("div"); + wordEl.className = "alpha-gematria-match-word"; + wordEl.textContent = String(match?.word || "--"); + cardEl.appendChild(wordEl); + + const definition = String(match?.definition || "").trim(); + if (definition) { + const definitionEl = document.createElement("div"); + definitionEl.className = "alpha-gematria-match-definition"; + definitionEl.textContent = definition; + cardEl.appendChild(definitionEl); + } + + const ciphersEl = document.createElement("div"); + ciphersEl.className = "alpha-gematria-match-ciphers"; + const matchCiphers = Array.isArray(match?.ciphers) ? match.ciphers : []; + matchCiphers.slice(0, 6).forEach((cipher) => { + const chipEl = document.createElement("span"); + chipEl.className = "alpha-gematria-match-cipher"; + chipEl.textContent = String(cipher?.name || cipher?.id || "Unknown"); + ciphersEl.appendChild(chipEl); + }); + if (matchCiphers.length > 6) { + const extraEl = document.createElement("span"); + extraEl.className = "alpha-gematria-match-cipher"; + extraEl.textContent = `+${matchCiphers.length - 6} more`; + ciphersEl.appendChild(extraEl); + } + cardEl.appendChild(ciphersEl); + + fragment.appendChild(cardEl); + }); + + matchesEl.replaceChildren(fragment); + matchesEl.hidden = false; + } + + async function renderReverseLookupResult() { + const { resultEl, breakdownEl, matchesEl } = getElements(); + if (!resultEl || !breakdownEl || !matchesEl) { + return; + } + + const rawValue = state.reverseInputText; + if (!String(rawValue || "").trim()) { + resultEl.textContent = "Value: --"; + breakdownEl.textContent = "Enter a whole number to find words across all available ciphers."; + matchesEl.hidden = false; + setMatchesMessage(matchesEl, "Reverse lookup searches the API-backed gematria word index."); + return; + } + + const numericValue = parseReverseLookupValue(rawValue); + if (!Number.isFinite(numericValue)) { + resultEl.textContent = "Value: --"; + breakdownEl.textContent = "Enter digits only to search the reverse gematria index."; + matchesEl.hidden = false; + setMatchesMessage(matchesEl, "Use a whole number such as 33 or 418."); + return; + } + + const requestId = state.reverseRequestId + 1; + state.reverseRequestId = requestId; + resultEl.textContent = `Value: ${formatCount(numericValue)}`; + breakdownEl.textContent = "Searching reverse gematria index..."; + matchesEl.hidden = false; + setMatchesMessage(matchesEl, "Loading matching words..."); + + try { + const payload = await loadReverseLookup(numericValue); + if (requestId !== state.reverseRequestId || !isReverseMode()) { + return; + } + renderReverseLookupMatches(payload, numericValue); + } catch { + if (requestId !== state.reverseRequestId || !isReverseMode()) { + return; + } + resultEl.textContent = `Value: ${formatCount(numericValue)}`; + breakdownEl.textContent = "Reverse lookup is unavailable right now."; + matchesEl.hidden = false; + setMatchesMessage(matchesEl, "Unable to load reverse gematria words from the API."); + } + } + function computeGematria(text, cipher, baseAlphabet) { const normalizedInput = normalizeGematriaText(text); const scriptMap = state.scriptCharMap instanceof Map @@ -281,7 +532,7 @@ }; } - function renderGematriaResult() { + function renderForwardGematriaResult() { const { resultEl, breakdownEl } = getElements(); if (!resultEl || !breakdownEl) { return; @@ -299,7 +550,7 @@ return; } - const { total, count, breakdown } = computeGematria(state.inputText, cipher, db.baseAlphabet); + const { total, count, breakdown } = computeGematria(state.forwardInputText, cipher, db.baseAlphabet); resultEl.textContent = `Total: ${total}`; if (!count) { @@ -310,8 +561,19 @@ breakdownEl.textContent = `${cipher.name} · ${count} letters · ${breakdown} = ${total}`; } + function renderGematriaResult() { + updateModeUi(); + + if (isReverseMode()) { + void renderReverseLookupResult(); + return; + } + + renderForwardGematriaResult(); + } + function bindGematriaListeners() { - const { cipherEl, inputEl } = getElements(); + const { cipherEl, inputEl, modeToggleEl } = getElements(); if (state.listenersBound || !cipherEl || !inputEl) { return; } @@ -322,7 +584,17 @@ }); inputEl.addEventListener("input", () => { - state.inputText = inputEl.value || ""; + if (isReverseMode()) { + state.reverseInputText = inputEl.value || ""; + } else { + state.forwardInputText = inputEl.value || ""; + } + renderGematriaResult(); + }); + + modeToggleEl?.addEventListener("change", () => { + state.activeMode = modeToggleEl.checked ? "reverse" : "forward"; + updateModeUi(); renderGematriaResult(); }); @@ -336,10 +608,7 @@ } bindGematriaListeners(); - - if (inputEl.value !== state.inputText) { - inputEl.value = state.inputText; - } + updateModeUi(); void loadGematriaDb().then(() => { refreshScriptMap((state.db || getFallbackGematriaDb()).baseAlphabet); diff --git a/app/ui-alphabet.js b/app/ui-alphabet.js index d3fe898..32ecda6 100644 --- a/app/ui-alphabet.js +++ b/app/ui-alphabet.js @@ -52,6 +52,7 @@ let tabAll, tabHebrew, tabGreek, tabEnglish, tabArabic, tabEnochian; let searchInputEl, searchClearEl, typeFilterEl; let gematriaCipherEl, gematriaInputEl, gematriaResultEl, gematriaBreakdownEl; + let gematriaReverseEl, gematriaMatchesEl, gematriaInputLabelEl, gematriaCipherLabelEl; function getElements() { listEl = document.getElementById("alpha-letter-list"); @@ -72,6 +73,10 @@ gematriaInputEl = document.getElementById("alpha-gematria-input"); gematriaResultEl = document.getElementById("alpha-gematria-result"); gematriaBreakdownEl = document.getElementById("alpha-gematria-breakdown"); + gematriaReverseEl = document.getElementById("alpha-gematria-reverse"); + gematriaMatchesEl = document.getElementById("alpha-gematria-matches"); + gematriaInputLabelEl = document.getElementById("alpha-gematria-input-label"); + gematriaCipherLabelEl = document.getElementById("alpha-gematria-cipher-label"); } function getGematriaElements() { @@ -80,7 +85,11 @@ cipherEl: gematriaCipherEl, inputEl: gematriaInputEl, resultEl: gematriaResultEl, - breakdownEl: gematriaBreakdownEl + breakdownEl: gematriaBreakdownEl, + modeToggleEl: gematriaReverseEl, + matchesEl: gematriaMatchesEl, + inputLabelEl: gematriaInputLabelEl, + cipherLabelEl: gematriaCipherLabelEl }; } diff --git a/app/ui-navigation.js b/app/ui-navigation.js index a588d00..bd1a6db 100644 --- a/app/ui-navigation.js +++ b/app/ui-navigation.js @@ -33,6 +33,10 @@ function bindTopLevelNavButtons() { const elements = config.elements || {}; + bindClick(elements.openHomeEl, () => { + setActiveSection("home"); + }); + bindClick(elements.openTarotEl, () => { if (getActiveSection() === "tarot") { setActiveSection("home"); @@ -78,6 +82,10 @@ setActiveSection(getActiveSection() === "alphabet" ? "home" : "alphabet"); }); + bindClick(elements.openAlphabetLettersEl, () => { + setActiveSection(getActiveSection() === "alphabet-letters" ? "home" : "alphabet-letters"); + }); + bindClick(elements.openNumbersEl, () => { setActiveSection(getActiveSection() === "numbers" ? "home" : "numbers"); }); @@ -104,8 +112,12 @@ bindClick(elements.openCalendarEl, () => { const activeSection = getActiveSection(); - const isCalendarMenuActive = activeSection === "calendar" || activeSection === "holidays"; - setActiveSection(isCalendarMenuActive ? "home" : "calendar"); + const isCalendarMenuActive = activeSection === "timeline" || activeSection === "calendar" || activeSection === "holidays"; + setActiveSection(isCalendarMenuActive ? "home" : "timeline"); + }); + + bindClick(elements.openCalendarTimelineEl, () => { + setActiveSection(getActiveSection() === "timeline" ? "home" : "timeline"); }); bindClick(elements.openCalendarMonthsEl, () => { @@ -160,7 +172,7 @@ if (typeof ensure.ensureAlphabetSection === "function" && magickDataset) { ensure.ensureAlphabetSection(magickDataset, referenceData); } - setActiveSection("alphabet"); + setActiveSection("alphabet-letters"); const alphabet = event?.detail?.alphabet; const hebrewLetterId = event?.detail?.hebrewLetterId; diff --git a/app/ui-section-state.js b/app/ui-section-state.js index a02bc2d..dc2bf59 100644 --- a/app/ui-section-state.js +++ b/app/ui-section-state.js @@ -3,6 +3,7 @@ const VALID_SECTIONS = new Set([ "home", + "timeline", "calendar", "holidays", "tarot", @@ -16,6 +17,7 @@ "kabbalah-tree", "cube", "alphabet", + "alphabet-letters", "numbers", "zodiac", "quiz", @@ -80,9 +82,10 @@ const magickDataset = getMagickDataset(); const isHomeOpen = activeSection === "home"; + const isTimelineOpen = activeSection === "timeline"; const isCalendarOpen = activeSection === "calendar"; const isHolidaysOpen = activeSection === "holidays"; - const isCalendarMenuOpen = isCalendarOpen || isHolidaysOpen; + const isCalendarMenuOpen = isTimelineOpen || isCalendarOpen || isHolidaysOpen; const isTarotOpen = activeSection === "tarot"; const isAstronomyOpen = activeSection === "astronomy"; const isPlanetOpen = activeSection === "planets"; @@ -97,11 +100,14 @@ const isCubeOpen = activeSection === "cube"; const isKabbalahMenuOpen = isKabbalahOpen || isKabbalahTreeOpen || isCubeOpen; const isAlphabetOpen = activeSection === "alphabet"; + const isAlphabetLettersOpen = activeSection === "alphabet-letters"; + const isAlphabetMenuOpen = isAlphabetOpen || isAlphabetLettersOpen; const isNumbersOpen = activeSection === "numbers"; const isQuizOpen = activeSection === "quiz"; const isGodsOpen = activeSection === "gods"; const isEnochianOpen = activeSection === "enochian"; + setHidden(elements.timelineSectionEl, !isTimelineOpen); setHidden(elements.calendarSectionEl, !isCalendarOpen); setHidden(elements.holidaySectionEl, !isHolidaysOpen); setHidden(elements.tarotSectionEl, !isTarotOpen); @@ -115,16 +121,17 @@ setHidden(elements.kabbalahTreeSectionEl, !isKabbalahTreeOpen); setHidden(elements.cubeSectionEl, !isCubeOpen); setHidden(elements.alphabetSectionEl, !isAlphabetOpen); + setHidden(elements.alphabetLettersSectionEl, !isAlphabetLettersOpen); setHidden(elements.numbersSectionEl, !isNumbersOpen); setHidden(elements.zodiacSectionEl, !isZodiacOpen); setHidden(elements.quizSectionEl, !isQuizOpen); setHidden(elements.godsSectionEl, !isGodsOpen); setHidden(elements.enochianSectionEl, !isEnochianOpen); setHidden(elements.nowPanelEl, !isHomeOpen); - setHidden(elements.monthStripEl, !isHomeOpen); - setHidden(elements.calendarEl, !isHomeOpen); + setPressed(elements.openHomeEl, isHomeOpen); setPressed(elements.openCalendarEl, isCalendarMenuOpen); + toggleActive(elements.openCalendarTimelineEl, isTimelineOpen); toggleActive(elements.openCalendarMonthsEl, isCalendarOpen); toggleActive(elements.openHolidaysEl, isHolidaysOpen); setPressed(elements.openTarotEl, isTarotOpen); @@ -137,7 +144,8 @@ setPressed(elements.openKabbalahEl, isKabbalahMenuOpen); toggleActive(elements.openKabbalahTreeEl, isKabbalahTreeOpen); toggleActive(elements.openKabbalahCubeEl, isCubeOpen); - setPressed(elements.openAlphabetEl, isAlphabetOpen); + setPressed(elements.openAlphabetEl, isAlphabetMenuOpen); + toggleActive(elements.openAlphabetLettersEl, isAlphabetLettersOpen); setPressed(elements.openNumbersEl, isNumbersOpen); toggleActive(elements.openZodiacEl, isZodiacOpen); toggleActive(elements.openNatalEl, isNatalOpen); @@ -149,6 +157,11 @@ config.settingsUi?.closeSettingsPopup?.(); } + if (isTimelineOpen) { + renderHomeFallback(); + return; + } + if (isCalendarOpen) { ensure.ensureCalendarSection?.(referenceData, magickDataset); return; @@ -198,7 +211,7 @@ return; } - if (isAlphabetOpen) { + if (isAlphabetOpen || isAlphabetLettersOpen) { ensure.ensureAlphabetSection?.(magickDataset, referenceData); return; } @@ -233,7 +246,7 @@ return; } - renderHomeFallback(); + config.homeUi?.syncNowPanelTheme?.(new Date()); } function getActiveSection() { diff --git a/index.html b/index.html index 428494f..2f0b2e1 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,7 @@