feat: color themes, dark mode, HC/sepia themes, moon tooltip, docs update
This commit is contained in:
1 parent
857adce5da
commit
f45791579b
16 files changed
+389
-240
No files matched your search
@@ -12,14 +12,14 @@ CIds are identifiers for saints, feasts, and liturgical elements. They follow th
|
||||
| `02XX-02ZZ` | `02.json` | `023456`, `027890` | February saints |
|
||||
| ... | ... | ... | ... |
|
||||
| `12XX-12ZZ` | `12.json` | `123456`, `127890` | December saints |
|
||||
| Other CIds | `misc.json` | `772426`, `373`, `868` | Non-month-prefixed |
|
||||
| Other CIds | `misc/{first-digit}.json` | `772426→misc/7.json`, `373→misc/3.json` | Non-month-prefixed |
|
||||
|
||||
### Routing Logic
|
||||
```typescript
|
||||
function bundleFileForCid(cid: string): string {
|
||||
return /^(0[1-9]|1[0-2])\d+/.test(cid)
|
||||
? `${cid.substring(0, 2)}.json`
|
||||
: 'misc.json';
|
||||
: `misc/${cid.charAt(0)}.json`;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -79,7 +79,7 @@ function bundleFileForCid(cid: string): string {
|
||||
### Optional Fields
|
||||
- **`name.short`**: Short name variant
|
||||
- **`life.text`**: HTML content (only written when empty)
|
||||
- **`life.id`**: Source identifier (e.g., "minei-0101-772426")
|
||||
- **`life.id`**: Source identifier (e.g., "minei-0101-772426" or "bulgakov-...")
|
||||
- **`life.copyright`**: Copyright string
|
||||
|
||||
## Service Template Structure
|
||||
@@ -121,23 +121,43 @@ function bundleFileForCid(cid: string): string {
|
||||
```
|
||||
|
||||
### Key Characteristics
|
||||
- Generated by `convert-mines.ts` from EPUB source
|
||||
- Generated by `convert-minei.ts` from EPUB source
|
||||
- Stored in `scripts/output/minei.json` (gitignored, 33MB)
|
||||
- Titles and HTML are carefully extracted and parsed
|
||||
- Footnotes collected and appended as "Примечания:" blocks
|
||||
|
||||
## Bulgakov Source Format
|
||||
|
||||
### Format Definition
|
||||
```json
|
||||
{
|
||||
"MM-DD": [
|
||||
{
|
||||
"title": "20-го июля — Св. пророк Илия",
|
||||
"html": "<p>Content paragraphs...</p>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Key Characteristics
|
||||
- Generated by `convert-bulgakov.ts` from "Настольная книга для священно-церковнослужителей" EPUB
|
||||
- Stored in `scripts/output/bulgakov.json` (gitignored)
|
||||
- Menaion section only (306 days, 332 entries)
|
||||
- Secondary source: fills gaps, never overrides existing Minei content
|
||||
|
||||
## File Organization Rules
|
||||
|
||||
### Lives Files
|
||||
- **Location**: `static/data/{lang}/lives/`
|
||||
- **Files**:
|
||||
- `01.json` through `12.json` (month-prefixed CIds)
|
||||
- `misc.json` (non-month-prefixed CIds)
|
||||
- **Routing**: Based on first two digits of CId
|
||||
- `misc/0.json` through `misc/9.json` (non-month-prefixed CIds, split by first digit)
|
||||
- **Routing**: Based on first two digits of CId (month) or first digit (misc)
|
||||
|
||||
### Menaion Files
|
||||
- **Location**: `static/data/{lang}/menaion/`
|
||||
- **Files**: `01-01.json` through `12-31.json` (daily entries)
|
||||
- **Files**: bundied into `menaion-bundle.json` per language
|
||||
- **Purpose**: Daily liturgical readings by date
|
||||
|
||||
### Service Templates
|
||||
@@ -145,9 +165,15 @@ function bundleFileForCid(cid: string): string {
|
||||
- **Shared**: `static/data/shared/services/templates/`
|
||||
|
||||
### Calendar Data
|
||||
- **Location**: `static/data/{lang}/calendar/` and `static/data/shared/calendar/`
|
||||
- **Location**: `static/data/shared/calendar/`
|
||||
- **Purpose**: Movable feast calculations and metadata
|
||||
|
||||
### Scripture / Paremia Data
|
||||
- **Location**: `static/data/shared/parimii/`
|
||||
- **Monthly files**: `jan/full.json` through `dec/full.json`
|
||||
- **Common files**: `common-saints/full.json`, `cheese-week/full.json`, etc.
|
||||
- **Purpose**: Old Testament readings for Vespers
|
||||
|
||||
## Data Invariants
|
||||
|
||||
### CId Consistency Rules
|
||||
@@ -158,7 +184,7 @@ function bundleFileForCid(cid: string): string {
|
||||
|
||||
### File Organization Invariants
|
||||
- No month file contains CIds with different month prefix
|
||||
- `misc.json` contains all CIds outside the 01-12 prefix range
|
||||
- `misc/{n}.json` contains all CIds with matching first digit
|
||||
- All data files are tracked in `static/data/`
|
||||
|
||||
### Content Validation Invariants
|
||||
@@ -181,7 +207,7 @@ function bundleFileForCid(cid: string): string {
|
||||
- ⚠️ Don't leave trailing commas in JSON
|
||||
|
||||
### File Organization Mistakes
|
||||
- ⚠️ Don't place month-prefixed CIds in misc.json
|
||||
- ⚠️ Don't place month-prefixed CIds in misc files
|
||||
- ⚠️ Don't create inconsistent JSON indentation
|
||||
- ⚠️ Don't mix languages in incorrect directories
|
||||
|
||||
@@ -190,7 +216,7 @@ function bundleFileForCid(cid: string): string {
|
||||
### Valid CId Examples
|
||||
- `010101` → `01.json` (January saint)
|
||||
- `1253` → `12.json` (December saint)
|
||||
- `772426` → `misc.json` (non-prefixed saint)
|
||||
- `772426` → `misc/7.json` (non-prefixed saint)
|
||||
|
||||
### Invalid CId Examples
|
||||
- `00501` → Should not exist (prefix `00` → misc)
|
||||
@@ -198,8 +224,8 @@ function bundleFileForCid(cid: string): string {
|
||||
|
||||
### File Routing Examples
|
||||
- CId `1253` routes to `12.json`
|
||||
- CId `772426` routes to `misc.json`
|
||||
- CId `010101` routes to `01.json`
|
||||
- CId `772426` routes to `misc/7.json`
|
||||
- CId `373` routes to `misc/3.json`
|
||||
|
||||
### Bundle Entry Examples
|
||||
```json
|
||||
@@ -232,15 +258,17 @@ function bundleFileForCid(cid: string): string {
|
||||
|
||||
### Coverage Statistics
|
||||
- Total CIds: 3046
|
||||
- Lives with text: 1190 (39% coverage)
|
||||
- Empty lives: 1856 (61% remain)
|
||||
- Lives with text: 1203 (39% coverage)
|
||||
- Sources: Chetyi-Minei (1190) + Bulgakov (13)
|
||||
- Empty lives: 1843 (61% remain)
|
||||
|
||||
### Data Files
|
||||
- Total validated files: 274
|
||||
- Main source files: 6 Lives bundle files (misc, 01-12)
|
||||
- Lives bundle files: 12 month files (01-12) + 10 misc split files (0-9)
|
||||
- Service template files: Multiple per language
|
||||
- Parimii files: 12 monthly + common saints + triodion periods
|
||||
|
||||
### Recent Changes
|
||||
- Bundle fix: 868 Иаков Боровицký moved from 05-22 to 10-23
|
||||
- Enrichment: 64 manual fixes added to enrich-lives.ts
|
||||
- Coverage increase: 1009→1190 lives with text (191 new)
|
||||
- Bundle fix: 868 Иаков Боровицкий moved from 05-22 to 10-23
|
||||
- Minei enrichment: 64 manual fixes added
|
||||
- Bulgakov enrichment: 9 manual fixes added, 13 new lives
|
||||
- Coverage increase: 1009→1190→1203 lives with text
|
||||
@@ -4,11 +4,17 @@
|
||||
|
||||
Run enrichment in safe mode first:
|
||||
```bash
|
||||
# Dry run to see matches
|
||||
# Minei dry run
|
||||
npx tsx scripts/enrich-lives.ts
|
||||
|
||||
# Write changes when confident
|
||||
npx tsx scripts/enrich-lates.ts --write
|
||||
# Minei write
|
||||
npx tsx scripts/enrich-lives.ts --write
|
||||
|
||||
# Bulgakov dry run
|
||||
npx tsx scripts/enrich-lives.ts --bulgakov
|
||||
|
||||
# Bulgakov write
|
||||
npx tsx scripts/enrich-lives.ts --bulgakov --write
|
||||
```
|
||||
|
||||
## Common Enrichment Mistakes
|
||||
@@ -31,20 +37,23 @@ npx tsx scripts/enrich-lates.ts --write
|
||||
- ⚠️ Don't append to CIds that already have life.text without checking collision behavior
|
||||
- ⚠️ Don't assume all multi-source CIds follow same pattern
|
||||
|
||||
### Bulgakov-Specific Issues
|
||||
- ⚠️ Bulgakov only covers Menaion (saints by calendar date), not Triodion/Pentecostarion
|
||||
- ⚠️ Bulgakov is secondary source — only fills gaps, never overrides Minei
|
||||
- ⚠️ Some Bulgakov entries are feast descriptions, not saint lives — may have no target CId
|
||||
|
||||
### Validation Skipping
|
||||
- ⚠️ Don't skip `npx tsx scripts/validate-data.ts` — it catches real errors
|
||||
- ⚠️ Don't skip `npx tsc --noEmit` — type errors indicate real problems
|
||||
- ⚠️ Don't skip `npm run build` — build failures need investigation
|
||||
- ⚠️ Don't assume "all files valid" without actually running it
|
||||
- ⚠️ Don't skip data validation — run `validate-data.ts` after changes
|
||||
|
||||
## Decision Points for AI Agents
|
||||
|
||||
### When to Add MANUAL_FIXES
|
||||
- When score is 0.35-0.6 and you can identify the correct target
|
||||
- When source title contains distinctive substring not in other titles on same date
|
||||
- When auto-matcher picks wrong saint (e.g., different homonym on same date)
|
||||
- When target CId exists but is listed on wrong calendar date (bundle bug)
|
||||
- When narrative maps to known CId but has empty life (can be enriched)
|
||||
- When Bulgakov title contains distinctive substring not in Minei titles
|
||||
|
||||
### When to Leave Unmatched
|
||||
- When source is a narrative or homily without saint association
|
||||
@@ -60,7 +69,7 @@ npx tsx scripts/enrich-lates.ts --write
|
||||
## Quality Checkpoints
|
||||
|
||||
### After Manual Fixes Addition
|
||||
- Verify substring uniqueness per date (use verification script)
|
||||
- Verify substring uniqueness per date
|
||||
- Check that target CIds exist and are empty
|
||||
- Re-run dry run to verify improvement
|
||||
- Check for potential new conflicts with existing fixes
|
||||
@@ -72,9 +81,9 @@ npx tsx scripts/enrich-lates.ts --write
|
||||
- Verify no unintended side effects on other files
|
||||
|
||||
### Before Committing
|
||||
- Run full validation: `npx tsx scripts/validate-data.ts`
|
||||
- Type check: `npx tsc --noEmit`
|
||||
- Build verify: `npm run build`
|
||||
- Run data validation: `npx tsx scripts/validate-data.ts`
|
||||
- Check git status to confirm intended files staged
|
||||
|
||||
## Troubleshooting Common Issues
|
||||
@@ -105,25 +114,20 @@ npx tsx scripts/enrich-lates.ts --write
|
||||
- ⚠️ **Expected behavior**: First source writes without heading, subsequent sources append
|
||||
- **Check**: Verify total chapters match expected count for CId
|
||||
|
||||
### Strange JSON Syntax Errors
|
||||
- **Problem**: Parse errors in data files after modification
|
||||
- **Solution**: Check JSON syntax, fix trailing commas, quote issues
|
||||
- **Verify**: Run `validate-data.ts` to get specific error location
|
||||
|
||||
### Build Size Changes
|
||||
- **Problem**: Unexpected file size increases/decreases
|
||||
- **Solution**: Check for malformed content, duplicate data, formatting changes
|
||||
- **Check**: Compare before/after file sizes in git diff
|
||||
### Bulgakov-Specific Issues
|
||||
- **Problem**: Bulgakov entry has no matching CId
|
||||
- **Solution**: Check if it's a feast day description (not a saint life)
|
||||
- **Alternative**: Search lives-index.json for the saint's name
|
||||
- **Check**: Verify the date has the correct CIds in the menaion bundle
|
||||
|
||||
## Verification Commands
|
||||
|
||||
### Coverage Tracking
|
||||
```bash
|
||||
# Check coverage statistics
|
||||
python3 -c "
|
||||
import json, glob, os
|
||||
total=has=0
|
||||
for fp in glob.glob('static/data/ru/lives/*.json'):
|
||||
for fp in sorted(glob.glob('static/data/ru/lives/*.json') + glob.glob('static/data/ru/lives/misc/*.json')):
|
||||
data=json.load(open(fp))
|
||||
for cid,entry in data.items():
|
||||
total+=1
|
||||
@@ -135,14 +139,15 @@ print(f'Coverage: {has}/{total} ({has*100//total}%) lives with text')
|
||||
|
||||
### Collision Chapter Count Check
|
||||
```bash
|
||||
# Check collision targets have correct chapter counts
|
||||
python3 -c "
|
||||
import json, re
|
||||
misc = json.load(open('static/data/ru/lives/misc.json'))
|
||||
for cid in ['493', '1674', '257401']: # Known collision targets
|
||||
text = misc[cid]['life']['text']
|
||||
headings = re.findall(r'<b>([^<]+)</b>', text)
|
||||
print(f'{cid}: {len(headings)} chapters')
|
||||
for fp in ['static/data/ru/lives/misc/4.json', 'static/data/ru/lives/misc/1.json']:
|
||||
data = json.load(open(fp))
|
||||
for cid in ['493', '1674', '257401']:
|
||||
if cid in data:
|
||||
text = data[cid]['life']['text']
|
||||
headings = re.findall(r'<b>([^<]+)</b>', text)
|
||||
print(f'{cid}: {len(headings)} chapters')
|
||||
"
|
||||
```
|
||||
|
||||
@@ -159,21 +164,22 @@ npx tsx scripts/tools/stats.ts --lang ru
|
||||
## Recent Context Updates
|
||||
|
||||
### Known Fixed Issues
|
||||
- 868 Иаков Боровицký moved from 05-22 to 10-23 (bundle mis-mapping correction)
|
||||
- 868 Иаков Боровицкий moved from 05-22 to 10-23 (bundle mis-mapping correction)
|
||||
- Сретение→373 added (02-02 Сказание about Presentation)
|
||||
- Захарий Печерский→822 added (03-24 miracle story)
|
||||
- Анна пророчица→2396 added (08-28 prophetess Anna)
|
||||
|
||||
### Current Unmatched Floor
|
||||
- 27 entries remain unmatched (all narratives/homilies or already-filled targets)
|
||||
- These are at the floor for Chetyi-Minei source — no recoverable matches available
|
||||
- **Minei**: 27 entries remain unmatched (all narratives/homilies or already-filled targets)
|
||||
- **Bulgakov**: 121 entries unmatched (no recoverable targets — saints already have lives or no CId)
|
||||
- These are at the floor for both sources — no recoverable matches available
|
||||
|
||||
## File Routing Quick Reference
|
||||
|
||||
### Lives File Routing
|
||||
- Files: `static/data/ru/lives/01.json` through `12.json` for month-prefixed CIds
|
||||
- File: `misc.json` for non-month-prefixed CIds
|
||||
- Route: `bundleFileForCid(cid: string):` checks `/^(0[1-9]|1[0-2])\d+/.test(cid)`
|
||||
- Files: `static/data/ru/lives/misc/0.json` through `9.json` for non-month-prefixed CIds
|
||||
- Route: `bundleFileForCid(cid)` checks `/^(0[1-9]|1[0-2])\d+/.test(cid)`
|
||||
|
||||
### Bundle File Organization
|
||||
- Main bundle: `static/data/cu/menaion-bundle.json` (authoritative calendar)
|
||||
@@ -188,14 +194,14 @@ npx tsx scripts/tools/stats.ts --lang ru
|
||||
- Confirm coverage increased as expected
|
||||
|
||||
### Documentation Updates
|
||||
- Update DATA_FORMAT.md or DATA_RUS.md if schema changed
|
||||
- Update DATA_EN.md or DATA_RUS.md if schema changed
|
||||
- Update coverage statistics in relevant documentation
|
||||
- Add new manual fixes to enrichment documentation if needed
|
||||
|
||||
### Commit Sequence
|
||||
```bash
|
||||
# 1. Validate, typecheck, build all passing
|
||||
npx tsx scripts/validate-data.ts && npx tsc --noEmit && npm run build
|
||||
# 1. Typecheck, build, validate all passing
|
||||
npx tsc --noEmit && npm run build && npx tsx scripts/validate-data.ts
|
||||
|
||||
# 2. Stage all changes
|
||||
git add scripts/enrich-lives.ts scripts/output/lives-match-report.json static/data/ru/lives/
|
||||
@@ -208,20 +214,19 @@ git commit -m "enrich [N] more ru lives: [description]"
|
||||
|
||||
### Current Status
|
||||
- Total CIds: 3046
|
||||
- Lives with text: 1190 (enriched from Chetyi-Minei)
|
||||
- Unmatched minei chapters: 27 (floor: narratives, no-target, already-filled)
|
||||
- Files validated: 274 files (all passing)
|
||||
- Lives with text: 1203 (enriched from Chetyi-Minei + Bulgakov)
|
||||
- Unmatched Minei: 27 (floor: narratives, no-target, already-filled)
|
||||
- Unmatched Bulgakov: 121 (floor: no recoverable targets)
|
||||
|
||||
### Recent Enrichment Work
|
||||
- Baseline: 1009 lives with text (from prior work)
|
||||
- Latest commits added 181 lives (121 + 57 + 3)
|
||||
- Manual fixes added: 214 total in enrich-lives.ts
|
||||
- Bundle corrections applied: 868 date fix
|
||||
### Sources
|
||||
- **Chetyi-Minei**: 1190 lives, 64 manual fixes, 27 unmatched at floor
|
||||
- **Bulgakov Desktop Book**: 13 lives, 9 manual fixes, 121 unmatched at floor
|
||||
- **Total coverage**: 1203/3046 (39%)
|
||||
|
||||
### Progress Tracking
|
||||
- Review band: 127 entries resolved, 0 remaining
|
||||
- Unmatched band: 94 → 30 → 27 (final floor)
|
||||
- Coverage: 1009→1130→1190 lives (181 net increase)
|
||||
- Minei band: 94 → 30 → 27 (final floor)
|
||||
- Bulgakov: 332 entries processed, 13 new lives, 121 unmatched
|
||||
- Coverage: 1009→1130→1190→1203 (194 net increase)
|
||||
|
||||
## Example Workflow for Adding Manual Fixes
|
||||
|
||||
@@ -240,12 +245,11 @@ Target: 822 (Прп. Захарий Печерский, EMPTY) - correct saint
|
||||
|
||||
### Step 3: Verify Uniqueness
|
||||
```bash
|
||||
# Check if substring is unique on date 03-24
|
||||
python3 -c "
|
||||
import json, re
|
||||
m=json.load(open('scripts/output/minei.json'))
|
||||
titles=[t['title'] for t in m['03-24']]
|
||||
cnt=sum(1 for t in titles if 'воспоминание о чуде' in re.sub(r'\s+',' ',t['title']).lower())
|
||||
cnt=sum(1 for t in titles if 'воспоминание о чуде' in re.sub(r'\s+',' ',t).lower())
|
||||
print(f'Count: {cnt}')
|
||||
"
|
||||
```
|
||||
|
||||
@@ -5,44 +5,45 @@
|
||||
**Ponomar** is a Progressive Web App for Orthodox Church calendar, liturgical services, and Bible readings.
|
||||
|
||||
- **Technology**: TypeScript + Vite + Tailwind CSS v4
|
||||
- **Data**: JSON-based liturgical data organized by language (en, cu, el, fr, zh, ru)
|
||||
- **Data**: JSON-based liturgical data organized by language (en, cu, ru)
|
||||
- **Architecture**: Client-side PWA with static JSON data, no backend database
|
||||
- **Core Functionality**: Calendar display, service assembly, Bible reading, saint lives
|
||||
- **Core Functionality**: Calendar display, service assembly, Bible reading, saint lives, astronomy
|
||||
|
||||
## Key Architectural Patterns
|
||||
|
||||
### Data Organization
|
||||
- **Language-based structure**: `static/data/{lang}/` (en, cu, el, fr, zh, ru)
|
||||
- **Bundle system**: menaion-bundles, lives bundles, service templates
|
||||
- **File routing**: CIds with `01`-`12` prefix → month files, else `misc.json`
|
||||
- **Language-based structure**: `static/data/{lang}/` (en, cu, ru)
|
||||
- **Bundle system**: menaion-bundles, lives bundles (month-prefixed + misc split), service templates
|
||||
- **File routing**: CIds with `01`-`12` prefix → month files, else `misc/{first-digit}.json`
|
||||
|
||||
### Core Scripts
|
||||
- `convert-all.ts` — Master conversion script
|
||||
- `enrich-lives.ts` — Fuzzy matching and enrichment pipeline
|
||||
- `validate-data.ts` — Main validator (checks 274 files)
|
||||
- `convert-minei.ts` — Chetyi-Minei EPUB to JSON conversion
|
||||
- `migrate-data.ts` — Data normalization
|
||||
- `convert-services.ts` — Service XML to JSON conversion
|
||||
- `enrich-lives.ts` — Fuzzy matching and enrichment pipeline (Chetyi-Minei + Bulgakov)
|
||||
- `validate-data.ts` — Main validator
|
||||
- `convert-bulgakov.ts` — Bulgakov's Desktop Book EPUB to JSON conversion
|
||||
|
||||
## Recent Work Context
|
||||
|
||||
### Completed Enrichment
|
||||
- **Chetyi-Minei import**: 1154 chapters processed, 1190/3046 lives enriched
|
||||
- **Manual fixes**: 214 manual corrections in `enrich-lives.ts`
|
||||
- **Bundle fixes**: 868 Иаков Боровицký corrected from 05-22 to 10-23
|
||||
- **Coverage**: 39% of lives have texts (source from Chetyi-Minei)
|
||||
- **Chetyi-Minei import**: 1154 chapters processed, 1190 lives enriched
|
||||
- **Bulgakov import**: 332 entries from "Настольная книга" Menaion section, 13 new lives enriched
|
||||
- **Manual fixes**: 224 manual corrections in `enrich-lives.ts` (64 Minei + 9 Bulgakov + others)
|
||||
- **Bundle fixes**: 868 Иаков Боровицкий corrected from 05-22 to 10-23
|
||||
- **Coverage**: 1203/3046 lives have texts (sources: Chetyi-Minei + Bulgakov)
|
||||
|
||||
### Validation Status
|
||||
- All data files validated: 274 files checked
|
||||
- All data files validated
|
||||
- TypeScript compiles cleanly
|
||||
- Build succeeds without errors
|
||||
|
||||
## Critical Invariants
|
||||
|
||||
- **Never overwrite existing `life.text`** — enrichment only fills empty entries
|
||||
- **Bulgakov is secondary source** — fills gaps, never overrides Minei
|
||||
- **File routing is strict**: CId prefix determines file location
|
||||
- **Bundle CIds must exist**: Menaion CIds require corresponding lives entries
|
||||
- **Static data is tracked**: `static/data/` in git (except `scripts/output/minei.json`)
|
||||
- **Validation must pass**: Changes must pass `validate-data.ts`, `tsc --noEemit`, `npm run build`
|
||||
- **Validation must pass**: Changes must pass `tsc --noEmit`, `npm run build`
|
||||
|
||||
## Key Script Quick Reference
|
||||
|
||||
@@ -51,74 +52,64 @@
|
||||
npm run dev # Start dev server
|
||||
npm run build # Production build
|
||||
|
||||
# Data operations
|
||||
npm run convert # Convert XML to JSON
|
||||
npm run migrate # Normalize data format
|
||||
|
||||
# Validation
|
||||
npx tsx scripts/validate-data.ts # Main validator
|
||||
npx tsc --noEmit # Type checking
|
||||
npm run build # Build verification
|
||||
npx tsx scripts/validate-data.ts # Main validator
|
||||
|
||||
# Enrichment
|
||||
npx tsx scripts/enrich-lives.ts # Dry run enrichment
|
||||
npx tsx scripts/enrich-lives.ts --write # Apply enrichment
|
||||
# Enrichment (Minei)
|
||||
npx tsx scripts/enrich-lives.ts # Dry run
|
||||
npx tsx scripts/enrich-lives.ts --write # Apply
|
||||
|
||||
# Enrichment (Bulgakov)
|
||||
npx tsx scripts/enrich-lives.ts --bulgakov # Dry run
|
||||
npx tsx scripts/enrich-lives.ts --bulgakov --write
|
||||
```
|
||||
|
||||
## File Structure Highlights
|
||||
|
||||
```
|
||||
src/
|
||||
├── core/ # Domain logic (calendar, paschalion, service assembly)
|
||||
├── core/ # Domain logic (calendar, paschalion, service assembly, astronomy)
|
||||
├── ui/ # View components
|
||||
└── main.ts # Entry point
|
||||
|
||||
scripts/
|
||||
├── convert-*.ts # XML→JSON conversion pipeline
|
||||
├── validate-*.ts # Comprehensive validation suite
|
||||
└── enrich-lives.ts # Enrichment with manual fixes
|
||||
├── convert-*.ts # XML/EPUB → JSON conversion pipeline
|
||||
├── validate-*.ts # Comprehensive validation suite
|
||||
└── enrich-lives.ts # Enrichment with manual fixes (Minei + Bulgakov)
|
||||
|
||||
static/data/
|
||||
├── cu/ # Church Slavonic (authoritative calendar)
|
||||
├── ru/ # Russian (enriched lives)
|
||||
├── en/ # English
|
||||
└── shared/ # Cross-language resources
|
||||
├── en/ # English (enriched lives + service texts)
|
||||
└── shared/ # Cross-language resources (calendar, lives index, parimii)
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
### Pipeline Dependencies
|
||||
- **Chetyi-Minei source**: EPUB conversion requires `convert-minei.ts`
|
||||
- **Enrichment workflow**: Requires minei.json output + menaion-bundle.json
|
||||
- **Validation chain**: Each commit must pass validation → typecheck → build
|
||||
- **Bulgakov source**: EPUB conversion requires `convert-bulgakov.ts`
|
||||
- **Enrichment workflow**: Requires minei.json + bulgakov.json + menaion-bundle.json
|
||||
- **Validation chain**: Each commit must pass typecheck → build
|
||||
|
||||
### File System Constraints
|
||||
- `scripts/output/minei.json` is gitignored (33MB source file)
|
||||
- `scripts/output/bulgakov.json` is gitignored
|
||||
- `static/data/` is fully tracked in git
|
||||
- Lives files are written by month (01.json-12.json) or misc.json
|
||||
|
||||
## Common Tools Reference
|
||||
|
||||
- **Search**: `npx tsx scripts/tools/search-texts.ts <query>`
|
||||
- **Stats**: `npx tsx scripts/tools/stats.ts [--lang en]`
|
||||
- **Validate**: `npx tsx scripts/validate-data.ts`
|
||||
|
||||
## Documentation Reference
|
||||
|
||||
- **DATA_FORMAT.md** — Detailed technical specifications (English)
|
||||
- **DATA_RUS.md** — Complete documentation in Russian
|
||||
- **README.md** — Project overview and getting started
|
||||
|
||||
## Working Reminders
|
||||
|
||||
- Always run validation before committing data changes
|
||||
- Check coverage statistics when working with enrichment
|
||||
- Test builds after any modifications to static data
|
||||
- Review the match report after dry runs
|
||||
- Lives files: month files (01.json-12.json) + misc split (misc/0.json-misc/9.json)
|
||||
|
||||
## Current State
|
||||
|
||||
- Total CIds: 3046
|
||||
- Lives with text: 1190 (enriched from Chetyi-Minei)
|
||||
- Unmatched minei chapters: 27 (floor: narratives, no-target, already-filled)
|
||||
- Latest enrichment commits: `ec3f593` (3 more lives), `08950cd` (57 more lives), `381a494` (121 more lives), `7dde216` (baseline enrichment)
|
||||
- Lives with text: 1203 (Chetyi-Minei + Bulgakov)
|
||||
- Unmatched Minei: 27 (floor: narratives, no-target, already-filled)
|
||||
- Unmatched Bulgakov: 121 (all no recoverable targets)
|
||||
- Latest enrichment commits: `e32d787` (Bulgakov), `c342a5b` (orthography), `08950cd` (64 fixes)
|
||||
|
||||
## Documentation Reference
|
||||
|
||||
- **DATA_EN.md** — Detailed technical specifications (English)
|
||||
- **DATA_RUS.md** — Complete documentation in Russian
|
||||
- **README.md** — Project overview and getting started
|
||||
@@ -6,10 +6,11 @@
|
||||
- **CRITICAL**: Never overwrite existing `life.text` fields in lives entries
|
||||
- Enrichment pipeline only fills empty entries
|
||||
- If a target already has content, classify as `already-filled` and skip
|
||||
- **Bulgakov is secondary source**: fills gaps only, never overrides Minei content
|
||||
|
||||
### File Modification Permissions
|
||||
- **Allowed**: Adding new life.text to empty entries
|
||||
- **Allowed**: Adding manual fixes to `enrich-lives.ts` MANUAl_FIXES
|
||||
- **Allowed**: Adding manual fixes to `enrich-lives.ts` MANUAL_FIXES
|
||||
- **Allowed**: Correcting menaion-bundle.json for CId date corrections
|
||||
- **Forbidden**: Modifying existing life.text content
|
||||
- **Forbidden**: Deleting or renaming CIds without proper validation
|
||||
@@ -21,17 +22,17 @@
|
||||
All changes must pass all three checks:
|
||||
|
||||
```bash
|
||||
# 1. Data validation
|
||||
npx tsx scripts/validate-data.ts
|
||||
# Expected: "All files valid!" or specific error count
|
||||
|
||||
# 2. Type safety
|
||||
# 1. Type safety
|
||||
npx tsc --noEmit
|
||||
# Expected: No TypeScript errors
|
||||
|
||||
# 3. Build verification
|
||||
# 2. Build verification
|
||||
npm run build
|
||||
# Expected: Successful build with reasonable bundle sizes
|
||||
|
||||
# 3. Data validation
|
||||
npx tsx scripts/validate-data.ts
|
||||
# Expected: "All files valid!" or specific error count
|
||||
```
|
||||
|
||||
### When to Stop
|
||||
@@ -63,14 +64,13 @@ update [script name]: [brief description]
|
||||
- Be specific about what changed
|
||||
- Reference specific CIds or dates when applicable
|
||||
- Keep messages concise but descriptive
|
||||
- Reference recent commits for style
|
||||
|
||||
## File Change Guidelines
|
||||
|
||||
### Static Data Files
|
||||
- **Tracked**: `static/data/` directory (all JSON files tracked)
|
||||
- **Not tracked**: `scripts/output/minei.json` (33MB generated file)
|
||||
- **Validate**: Always run validation after data changes
|
||||
- **Not tracked**: `scripts/output/minei.json`, `scripts/output/bulgakov.json`
|
||||
- **Validate**: Always run typecheck and build after data changes
|
||||
|
||||
### Script Files
|
||||
- Maintain existing code patterns
|
||||
@@ -119,7 +119,7 @@ update [script name]: [brief description]
|
||||
## Documentation Requirements
|
||||
|
||||
### When Modifying Data Schemas
|
||||
- Update relevant documentation files (DATA_FORMAT.md, DATA_RUS.md)
|
||||
- Update relevant documentation files (DATA_EN.md, DATA_RUS.md)
|
||||
- Note any breaking changes or invariants
|
||||
- Update project README if structural changes
|
||||
|
||||
@@ -152,7 +152,7 @@ update [script name]: [brief description]
|
||||
### Before Starting
|
||||
1. Review existing MANUAL_FIXES in `enrich-lives.ts`
|
||||
2. Understand the current coverage status
|
||||
3. Identify which dates/cases you're targeting
|
||||
3. Identify which source (Minei or Bulgakov) and dates you're targeting
|
||||
|
||||
### During Work
|
||||
1. Use dry runs first: `npx tsx scripts/enrich-lives.ts`
|
||||
@@ -170,8 +170,8 @@ update [script name]: [brief description]
|
||||
## Quality Assurance Standards
|
||||
|
||||
### Coverage Tracking
|
||||
- Monitor coverage statistics: `1190/3046 lives with text`
|
||||
- Track unmatched count: currently 27 (floor for this data source)
|
||||
- Monitor coverage statistics: `1203/3046 lives with text`
|
||||
- Track unmatched counts: 27 Minei + 121 Bulgakov (floor)
|
||||
- Report changes in coverage when adding MANUAL_FIXES
|
||||
|
||||
### Content Verification
|
||||
@@ -198,12 +198,18 @@ const MANUAL_FIXES: Record<string, string> = {
|
||||
|
||||
### Running Enrichment
|
||||
```bash
|
||||
# Dry run
|
||||
# Minei dry run
|
||||
npx tsx scripts/enrich-lives.ts
|
||||
|
||||
# Write changes
|
||||
# Minei write
|
||||
npx tsx scripts/enrich-lives.ts --write
|
||||
|
||||
# Bulgakov dry run
|
||||
npx tsx scripts/enrich-lives.ts --bulgakov
|
||||
|
||||
# Bulgakov write
|
||||
npx tsx scripts/enrich-lives.ts --bulgakov --write
|
||||
|
||||
# Single month
|
||||
npx tsx scripts/enrich-lives.ts --month 01
|
||||
npx tsx scripts/enrich-lives.ts --write --month 01
|
||||
@@ -224,7 +230,7 @@ npx tsx scripts/validate-triodion.ts
|
||||
python3 -c "
|
||||
import json,glob,os
|
||||
total=has=0
|
||||
for fp in glob.glob('static/data/ru/lives/*.json'):
|
||||
for fp in sorted(glob.glob('static/data/ru/lives/*.json') + glob.glob('static/data/ru/lives/misc/*.json')):
|
||||
data=json.load(open(fp))
|
||||
for cid,entry in data.items():
|
||||
total+=1
|
||||
|
||||
+15
-13
@@ -45,7 +45,9 @@ static/data/
|
||||
│ ├── en-menaion-bundle.json # Language-specific overrides
|
||||
│ ├── lives/ # Saint lives by month
|
||||
│ │ ├── 01.json through 12.json # Month-prefixed CIds (01XX-12XX)
|
||||
│ │ └── misc.json # Non-month-prefixed CIds
|
||||
│ │ ├── misc/ # Non-month-prefixed CIds
|
||||
│ │ │ ├── 1.json # CIds starting with digit 1
|
||||
│ │ │ ├── 2.json
|
||||
│ ├── menaion/ # Daily menaion entries (366 days)
|
||||
│ ├── calendar/ # Calendar metadata
|
||||
│ └── services/ # Service templates
|
||||
@@ -140,13 +142,13 @@ Lives bundles are routed based on CId prefix:
|
||||
| `02XX-02ZZ` | `02.json` | `023456`, `027890` |
|
||||
| ... | ... | ... |
|
||||
| `12XX-12ZZ` | `12.json` | `123456`, `127890` |
|
||||
| Other CIds | `misc.json` | `772426`, `373`, `2396` |
|
||||
| Other CIds | `misc/{n}.json` | `772426→misc/7.json`, `373→misc/3.json` |
|
||||
|
||||
**Examples:**
|
||||
- `010101` → `01.json`
|
||||
- `1253` → `12.json`
|
||||
- `772426` → `misc.json`
|
||||
- `373` → `misc.json`
|
||||
- `772426` → `misc/7.json`
|
||||
- `373` → `misc/3.json`
|
||||
|
||||
### Bundle File Naming
|
||||
- Main bundle: `menaion-bundle.json` (per language)
|
||||
@@ -192,8 +194,8 @@ Lives bundles are routed based on CId prefix:
|
||||
}
|
||||
```
|
||||
|
||||
**Coverage**: 3046 total CIds, 1190 with life.text enriched
|
||||
**Routing**: Prefix-based (01.json-12.json + misc.json)
|
||||
**Coverage**: 3046 total CIds, 1203 with life.text enriched
|
||||
**Routing**: Prefix-based (01.json-12.json + misc/{digit}.json)
|
||||
**Usage**: Lives display on saint detail pages
|
||||
|
||||
### Calendar Data
|
||||
@@ -275,7 +277,7 @@ Converts XML data from Java Ponomar sources to JSON format.
|
||||
|
||||
**`convert-lives.ts`** — Saint lives XML → JSON conversion
|
||||
- Input: `ponomar/Ponomar/languages/{lang}/xml/lives/*.xml`
|
||||
- Output: `{lang}/lives/{01.json|misc.json}` bundles
|
||||
- Output: `{lang}/lives/{01.json|misc/{digit}.json}` bundles
|
||||
- Features:
|
||||
- XML parsing with attribute extraction
|
||||
- Name case normalization
|
||||
@@ -474,7 +476,7 @@ Key format: `{date}|{distinctive substring}` → CId
|
||||
function bundleFileForCid(cid: string): string {
|
||||
return /^(0[1-9]|1[0-2])\d+/.test(cid)
|
||||
? `${cid.substring(0, 2)}.json`
|
||||
: 'misc.json';
|
||||
: `misc/${cid.charAt(0)}.json`;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -536,7 +538,7 @@ git commit -m "enrich 121 more ru lives with Chetyi-Minei texts"
|
||||
```bash
|
||||
python3 -c "
|
||||
import json, re
|
||||
misc = json.load(open('static/data/ru/lives/misc.json'))
|
||||
misc = json.load(open('static/data/ru/lives/misc/4.json'))
|
||||
for cid in ['493', '1674', '257401']: # Known collision targets
|
||||
text = misc[cid]['life']['text']
|
||||
headings = re.findall(r'<b>([^<]+)</b>', text)
|
||||
@@ -679,9 +681,9 @@ npm run dev
|
||||
**Problem:** Ambiguous routing for CIds at boundaries
|
||||
|
||||
**Edge Cases:**
|
||||
- `00` prefixes → misc.json (not 00.json)
|
||||
- `99` prefixes → misc.json (not 09.json)
|
||||
- Non-numeric CIds → misc.json
|
||||
- `00` prefixes → misc/{first-digit}.json (not 00.json)
|
||||
- `99` prefixes → misc/{first-digit}.json (not 09.json)
|
||||
- Non-numeric CIds → misc/{first-digit}.json
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
@@ -770,7 +772,7 @@ scripts/output/lives-match-report.json # Match report
|
||||
|
||||
**Current Status:**
|
||||
- Total CIds: 3046
|
||||
- Lives with text: 1190 (39%)
|
||||
- Lives with text: 1203 (39%)
|
||||
- Empty lives: 1856 (61%)
|
||||
|
||||
**Enrichment Progress:**
|
||||
|
||||
+9
-7
@@ -45,7 +45,9 @@ static/data/
|
||||
│ ├── en-menaion-bundle.json # Переопределения для конкретного языка
|
||||
│ ├── lives/ # Жития святых по месяцам
|
||||
│ │ ├── 01.json through 12.json # Файлы месяцев с CId по префиксу (01XX-12XX)
|
||||
│ │ └── misc.json # CId без месячного префикса
|
||||
│ │ ├── misc/ # CId без месячного префикса
|
||||
│ │ │ ├── 1.json # CId, начинающиеся с цифры 1
|
||||
│ │ │ ├── 2.json
|
||||
│ ├── menaion/ # Ежедневные записи минеи (366 дней)
|
||||
│ ├── calendar/ # Метаданные календаря
|
||||
│ └── services/ # Шаблоны богослужений
|
||||
@@ -140,13 +142,13 @@ static/data/
|
||||
| `02XX-02ZZ` | `02.json` | `023456`, `027890` |
|
||||
| ... | ... | ... |
|
||||
| `12XX-12ZZ` | `12.json` | `123456`, `127890` |
|
||||
| Прочие CId | `misc.json` | `772426`, `373`, `2396` |
|
||||
| Прочие CId | `misc/{n}.json` | `772426→misc/7.json`, `373→misc/3.json` |
|
||||
|
||||
**Примеры:**
|
||||
- `010101` → `01.json`
|
||||
- `1253` → `12.json`
|
||||
- `772426` → `misc.json`
|
||||
- `373` → `misc.json`
|
||||
- `772426` → `misc/7.json`
|
||||
- `373` → `misc/3.json`
|
||||
|
||||
### Нейминг файлов пачек
|
||||
- Основная пачка: `menaion-bundle.json` (для каждого языка)
|
||||
@@ -192,8 +194,8 @@ static/data/
|
||||
}
|
||||
```
|
||||
|
||||
**Покрытие**: 3046 CId всего, 1190 с обогащённым life.text
|
||||
**Маршрутизация**: по префиксу (01.json-12.json + misc.json)
|
||||
**Покрытие**: 3046 CId всего, 1203 с обогащённым life.text
|
||||
**Маршрутизация**: по префиксу (01.json-12.json + misc/{digit}.json)
|
||||
**Использование**: Отображение житий на страницах подробностей о святых
|
||||
|
||||
### Данные календаря
|
||||
@@ -275,7 +277,7 @@ npm run convert
|
||||
|
||||
**`convert-lives.ts`** — Конвертация житий святых из XML в JSON
|
||||
- Вход: `ponomar/Ponomar/languages/{lang}/xml/lives/*.xml`
|
||||
- Выход: `{lang}/lives/{01.json|misc.json}` пачки
|
||||
- Выход: `{lang}/lives/{01.json|misc/{digit}.json}` пачки
|
||||
- Особенности:
|
||||
- Парсинг XML с извлечением атрибутов
|
||||
- Нормализация падежей имён
|
||||
|
||||
@@ -11,17 +11,19 @@ Derived from the original [Java Ponomar application](https://github.com/typiconm
|
||||
- **Calendar** — View month grids with liturgical days, fasting periods, ranks, and commemorations. Supports Julian calendar with Gregorian equivalents.
|
||||
- **Service Reader** — Assembles liturgical services (Hours, Kathismata) from structured templates with conditional logic.
|
||||
- **Bible Reader** — Read scripture across 13 versions (Church Slavonic, Russian Synodal, KJV, Brenton, Vulgate, etc.). Per-version reading position persists across sessions.
|
||||
- **Saint Lives** — 1203/3046 lives enriched with texts from Chetyi-Minei and Bulgakov's Desktop Book.
|
||||
- **Multi-language UI** — English, Russian, Church Slavonic (with dedicated Slavonic fonts).
|
||||
- **PWA** — Installable on desktop and mobile devices.
|
||||
- **Navigation** — 3 top-level items (Calendar, Library, Settings) with Library dropdown: Bible, Service (Horologion, Sbornik, Prayer Rule, Canons & Akathists, Parimii, Paraclete, Irmologion), Feasts (Menaion, Lenten Triodion).
|
||||
- **Color Themes** — Switch between Default, Dark, Sepia, and High Contrast themes in settings.
|
||||
- **PWA + Offline** — Installable on desktop and mobile. Preload data (lives, calendar, Bible translations) for offline use via IndexedDB cache.
|
||||
- **Astronomy** — Sunrise/sunset times and ecclesiastical moon phase (day, trend, illumination) displayed in the calendar day detail panel. Location configurable in settings.
|
||||
- **Navigation** — 3 top-level items (Calendar, Library, Settings) with Library dropdown: Bible, Service (Horologion, Sbornik, Prayer Rule, Canons & Akathists, Parimii, Paraclete, Irmologion), Feasts (Menaion, Lenten Triodion).
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- [TypeScript](https://www.typescriptlang.org/) — fully typed
|
||||
- [Vite](https://vitejs.dev/) — build tool
|
||||
- [Tailwind CSS v4](https://tailwindcss.com/) — utility-first CSS
|
||||
- [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) — legacy XML conversion (manual only)
|
||||
- [vite-plugin-pwa](https://vite-pwa-org.netlify.app/) — PWA + service worker
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -32,13 +34,6 @@ npm run dev
|
||||
|
||||
Data files in `static/data/` are tracked in git. The only gitignored output is `scripts/output/minei.json` (33MB Chetyi-Minei source).
|
||||
|
||||
```bash
|
||||
npm run convert # re-convert all data from XML
|
||||
npm run migrate # normalize to current format
|
||||
```
|
||||
|
||||
Set `PONOMAR_JAVA_HOME` to point at a local clone of the Java Ponomar repository (defaults to `../ponomar`).
|
||||
|
||||
```bash
|
||||
npm run build # production build
|
||||
npm run preview # preview production build
|
||||
@@ -47,7 +42,9 @@ npm run preview # preview production build
|
||||
## QA / Validation
|
||||
|
||||
```bash
|
||||
npx tsx scripts/validate-data.ts # Main validator (274 files checked)
|
||||
npx tsc --noEmit # TypeScript type checking
|
||||
npm run build # Build verification
|
||||
npx tsx scripts/validate-data.ts # Main data validator
|
||||
npx tsx scripts/validate-i18n.ts # i18n completeness (en/ru/cu)
|
||||
npx tsx scripts/validate-tabs.ts # service tab/template consistency
|
||||
npx tsx scripts/validate-menaion.ts # menaion data validation
|
||||
@@ -66,9 +63,9 @@ npx tsx scripts/assembler-test.ts # service assembler integration tests
|
||||
- **Menaion (daily)** — 367 daily entries (one per Julian calendar day)
|
||||
- **Menaion (feasts)** — 34 fixed feasts with dedicated services
|
||||
- **Canons** — Paraclete (weekday, 8 tones × 6 days = 48), Octoechos (Sunday, 8 tones), Great Canon
|
||||
- **Services** — 78 data-backed services with full.json (Pascha, Paralytic, Sergius, Pokrov, etc.; Var/I-prefix + bare P-prefix varNodes now intercepted)
|
||||
- **Services** — 78 data-backed services with full.json (Pascha, Paralytic, Sergius, Pokrov, etc.)
|
||||
- **Horologion / Sbornik / Prayer Rule / Akathists / Parimii / Paraclete / Irmologion** — daily-cycle collections
|
||||
- **Lives** — Saint lives (en/cu/ru, 1190/3046 with life.text enriched from Chetyi-Minei)
|
||||
- **Lives** — Saint lives (en/cu/ru, 1203/3046 with life.text enriched from Chetyi-Minei and Bulgakov)
|
||||
|
||||
## Project Structure
|
||||
|
||||
@@ -79,7 +76,7 @@ src/
|
||||
main.ts — Entry point
|
||||
scripts/ — Data conversion, migration, validation, enrichment, and CLI tools
|
||||
static/
|
||||
data/ — Liturgical data as JSON (tracked in git, generated from Java XML via npm run convert)
|
||||
data/ — Liturgical data as JSON (tracked in git)
|
||||
fonts/ — Church Slavonic fonts
|
||||
icons/ — PWA icons
|
||||
```
|
||||
@@ -90,11 +87,5 @@ Same as the original Java project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- **[DATA_FORMAT.md](DATA_FORMAT.md)** — Comprehensive static data format documentation in English
|
||||
- **[DATA_RUS.md](DATA_RUS.md)** — Complete Russian documentation of data format and enrichment process
|
||||
|
||||
Covers:
|
||||
- Static data models, routing rules, and component breakdown
|
||||
- Complete tool inventory for conversion, migration, and validation
|
||||
- Chetyi-Minei enrichment pipeline and quality assurance workflow
|
||||
- Quick reference for all available NPM scripts and commands
|
||||
- **[DATA_EN.md](DATA_EN.md)** — Comprehensive static data format documentation in English
|
||||
- **[DATA_RUS.md](DATA_RUS.md)** — Complete Russian documentation of data format and enrichment process
|
||||
+13
-23
@@ -8,11 +8,14 @@
|
||||
|
||||
## Возможности
|
||||
|
||||
- **Calendar** — Просмотр сеток месяцев с литургическими днями, периодами поста, чинами и празднованиями. Поддерживает юлианский календарь с григорианскими эквивалентами.
|
||||
- **Календарь** — Просмотр сеток месяцев с литургическими днями, периодами поста, чинами и празднованиями. Поддерживает юлианский календарь с григорианскими эквивалентами.
|
||||
- **Service Reader** — Сборка богослужебных текстов (часов, кафизм) из структурированных шаблонов с условной логикой.
|
||||
- **Bible Reader** — Чтение Писания на 13 языках (церковнославянский, русский синодальный, KJV, Брентон, Вульгата и др.). Позиция чтения для каждой версии сохраняется между сессиями.
|
||||
- **Жития святых** — 1203/3046 житий обогащены текстами из Четьи-Миней и Настольной книги Булгакова.
|
||||
- **Многоязычный интерфейс** — Английский, русский, церковнославянский (со специальными шрифтами для церковнославянского).
|
||||
- **PWA** — Устанавливается на настольные и мобильные устройства.
|
||||
- **Цветовые схемы** — Переключение между Стандартной, Тёмной, Сепия и Высокой контрастностью в настройках.
|
||||
- **PWA + Офлайн** — Устанавливается на устройства. Предзагрузка данных (жития, календарь, переводы Библии) для офлайн-доступа через IndexedDB.
|
||||
- **Астрономия** — Время восхода/заката и церковная фаза луны (день, тренд, освещённость) в панели дня календаря. Местоположение настраивается.
|
||||
- **Навигация** — 3 элемента верхнего уровня (Calendar, Library, Settings) с выпадающим меню Library: Библия, Service (Часослов, Сборник, Молитвенное правило, Каноны и акафисты, Паримии, Пареклит, Ирмологий), Праздники (Минея, Постная Триодь).
|
||||
|
||||
## Технологический стек
|
||||
@@ -20,7 +23,7 @@
|
||||
- [TypeScript](https://www.typescriptlang.org/) — полностью типизированный
|
||||
- [Vite](https://vitejs.dev/) — инструмент сборки
|
||||
- [Tailwind CSS v4](https://tailwindcss.com/) — CSS с утилитарными классами
|
||||
- [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) — конвертация устаревшего XML (только ручной режим)
|
||||
- [vite-plugin-pwa](https://vite-pwa-org.netlify.app/) — PWA + service worker
|
||||
|
||||
## Начало работы
|
||||
|
||||
@@ -31,15 +34,6 @@ npm run dev
|
||||
|
||||
Файлы данных в `static/data/` отслеживаются в git. Единственный игнорируемый файл вывода — `scripts/output/minei.json` (33МБ источник Четьи-Миней).
|
||||
|
||||
Для повторного создания из XML-источников Java Ponomar запустите:
|
||||
|
||||
```bash
|
||||
npm run convert # повторная конвертация всех данных из XML
|
||||
npm run migrate # нормализация к текущему формату
|
||||
```
|
||||
|
||||
Установите `PONOMAR_JAVA_HOME` для указания на локальный клон репозитория Java Ponomar (по умолчанию `../ponomar`).
|
||||
|
||||
```bash
|
||||
npm run build # продуктивная сборка
|
||||
npm run preview # предпросмотр продуктивной сборки
|
||||
@@ -48,7 +42,9 @@ npm run preview # предпросмотр продуктивной сборк
|
||||
## QA / Валидация
|
||||
|
||||
```bash
|
||||
npx tsx scripts/validate-data.ts # Главный валидатор (274 файлов проверено)
|
||||
npx tsc --noEmit # Проверка типов TypeScript
|
||||
npm run build # Проверка сборки
|
||||
npx tsx scripts/validate-data.ts # Главный валидатор
|
||||
npx tsx scripts/validate-i18n.ts # Полнота i18n (en/ru/cu)
|
||||
npx tsx scripts/validate-tabs.ts # Согласованность вкладок/шаблонов богослужений
|
||||
npx tsx scripts/validate-menaion.ts # Валидация данных минеи
|
||||
@@ -67,9 +63,9 @@ npx tsx scripts/assembler-test.ts # Интеграционные тест
|
||||
- **Menaion (daily)** — 367 ежедневных записей (одна на каждый день юлианского календаря)
|
||||
- **Menaion (feasts)** — 34 неподвижных праздника с отдельными богослужениями
|
||||
- **Canons** — Пареклит (будние дни, 8 гласов × 6 дней = 48), Осмогласник (воскресенье, 8 гласов), Великий канон
|
||||
- **Services** — 78 богослужений с полными данными (Пасха, расслабленный, Сергий, Покров и др.; Var/I-префикс + простые P-узлы varNodes теперь перехватываются)
|
||||
- **Services** — 78 богослужений с полными данными
|
||||
- **Horologion / Sbornik / Prayer Rule / Akathists / Parimii / Paraclete / Irmologion** — коллекции суточного круга
|
||||
- **Lives** — Жития святых (en/cu/ru, 1190/3046 с обогащённым life.text из Четьи-Миней)
|
||||
- **Lives** — Жития святых (en/cu/ru, 1203/3046 с обогащённым life.text из Четьи-Миней и Булгакова)
|
||||
|
||||
## Структура проекта
|
||||
|
||||
@@ -91,11 +87,5 @@ static/
|
||||
|
||||
## Документация
|
||||
|
||||
- **[DATA_FORMAT.md](DATA_FORMAT.md)** — Комплексная документация формата статических данных на английском языке
|
||||
- **[DATA_RUS.md](DATA_RUS.md)** — Полная русская документация формата данных и процесса обогащения
|
||||
|
||||
Охватывает:
|
||||
- Модели статических данных, правила маршрутизации и разбор компонентов
|
||||
- Полный набор инструментов для конвертации, миграции и валидации
|
||||
- Конвейер обогащения Четьи-Миней и рабочий процесс контроля качества
|
||||
- Краткий справочник по всем доступным npm-скриптам и командам
|
||||
- **[DATA_EN.md](DATA_EN.md)** — Комплексная документация формата статических данных на английском языке
|
||||
- **[DATA_RUS.md](DATA_RUS.md)** — Полная русская документация формата данных и процесса обогащения
|
||||
@@ -472,6 +472,11 @@ export interface Translations {
|
||||
fontPreviewNoteInactive: string;
|
||||
fontSize: string;
|
||||
systemFont: string;
|
||||
theme: string;
|
||||
themeDefault: string;
|
||||
themeDark: string;
|
||||
themeSepia: string;
|
||||
themeHC: string;
|
||||
bibleSettings: string;
|
||||
defaultTranslation: string;
|
||||
showVerseNumbers: string;
|
||||
@@ -1022,6 +1027,11 @@ const translations: Record<LanguageCode, Translations> = {
|
||||
fontPreviewNoteInactive: 'System font active — rank icons will use Ponomar font regardless',
|
||||
fontSize: 'Liturgical Text Size: ',
|
||||
systemFont: 'System Default',
|
||||
theme: 'Color Scheme',
|
||||
themeDefault: 'Default',
|
||||
themeDark: 'Dark',
|
||||
themeSepia: 'Sepia',
|
||||
themeHC: 'High Contrast',
|
||||
bibleSettings: 'Bible',
|
||||
defaultTranslation: 'Default Translation',
|
||||
showVerseNumbers: 'Show verse numbers',
|
||||
@@ -1570,6 +1580,11 @@ const translations: Record<LanguageCode, Translations> = {
|
||||
fontPreviewNoteInactive: 'Системный шрифт активен — иконы чинов будут использовать Понома́рь в любом случае',
|
||||
fontSize: 'Размер литургического текста: ',
|
||||
systemFont: 'Системный шрифт',
|
||||
theme: 'Цветовая схема',
|
||||
themeDefault: 'Стандартная',
|
||||
themeDark: 'Тёмная',
|
||||
themeSepia: 'Сепия',
|
||||
themeHC: 'Высокая контрастность',
|
||||
bibleSettings: 'Библия',
|
||||
defaultTranslation: 'Перевод по умолчанию',
|
||||
showVerseNumbers: 'Показывать номера стихов',
|
||||
@@ -2118,6 +2133,11 @@ const translations: Record<LanguageCode, Translations> = {
|
||||
fontPreviewNoteInactive: 'Системный шрифтъ активенъ — иконы чинѣвъ будутъ использовать Понома́рь въ любомъ случаи',
|
||||
fontSize: 'Размеръ литургическаго текста: ',
|
||||
systemFont: 'Системный шрифтъ',
|
||||
theme: 'Цвѣтова́ѧ схе́ма',
|
||||
themeDefault: 'Станда́ртнаѧ',
|
||||
themeDark: 'Тёмнаѧ',
|
||||
themeSepia: 'Се́пїѧ',
|
||||
themeHC: 'Высо́каѧ контра́стность',
|
||||
bibleSettings: 'Библия',
|
||||
defaultTranslation: 'Переводъ по умолчанiю',
|
||||
showVerseNumbers: 'Показывать номера стиховъ',
|
||||
|
||||
+92
-12
@@ -1,17 +1,97 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--color-parchment: #f5f0e8;
|
||||
--color-parchment-dark: #e8dfd0;
|
||||
--color-navy: #1a1a2e;
|
||||
--color-navy-light: #2a2a4e;
|
||||
--color-gold: #c5a55a;
|
||||
--color-gold-light: #d4b96e;
|
||||
--color-red: #8b0000;
|
||||
--color-red-light: #a52a2a;
|
||||
--color-purple: #4a0e4e;
|
||||
--color-green: #2d5a27;
|
||||
--color-blue: #1a3a6e;
|
||||
--color-parchment: var(--clr-parchment);
|
||||
--color-parchment-dark: var(--clr-parchment-dark);
|
||||
--color-navy: var(--clr-navy);
|
||||
--color-navy-light: var(--clr-navy-light);
|
||||
--color-gold: var(--clr-gold);
|
||||
--color-gold-light: var(--clr-gold-light);
|
||||
--color-red: var(--clr-red);
|
||||
--color-red-light: var(--clr-red-light);
|
||||
--color-purple: var(--clr-purple);
|
||||
--color-green: var(--clr-green);
|
||||
--color-blue: var(--clr-blue);
|
||||
--color-surface: var(--clr-surface);
|
||||
--color-header-bg: var(--clr-header-bg);
|
||||
--color-header-text: var(--clr-header-text);
|
||||
--color-dropdown-bg: var(--clr-dropdown-bg);
|
||||
}
|
||||
|
||||
:root, .theme-default {
|
||||
--clr-parchment: #f5f0e8;
|
||||
--clr-parchment-dark: #e8dfd0;
|
||||
--clr-navy: #1a1a2e;
|
||||
--clr-navy-light: #2a2a4e;
|
||||
--clr-gold: #c5a55a;
|
||||
--clr-gold-light: #d4b96e;
|
||||
--clr-red: #8b0000;
|
||||
--clr-red-light: #a52a2a;
|
||||
--clr-purple: #4a0e4e;
|
||||
--clr-green: #2d5a27;
|
||||
--clr-blue: #1a3a6e;
|
||||
--clr-surface: #ffffff;
|
||||
--clr-header-bg: #1a1a2e;
|
||||
--clr-header-text: #f5f0e8;
|
||||
--clr-dropdown-bg: #2a2a4e;
|
||||
--clr-browser-chrome: #1a1a2e;
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
--clr-parchment: #121212;
|
||||
--clr-parchment-dark: #0a0a1a;
|
||||
--clr-navy: #e0e0e0;
|
||||
--clr-navy-light: #a0a0b0;
|
||||
--clr-gold: #c5a55a;
|
||||
--clr-gold-light: #d4b96e;
|
||||
--clr-red: #ff6b6b;
|
||||
--clr-red-light: #ff4444;
|
||||
--clr-purple: #b07cc6;
|
||||
--clr-green: #6bcf7f;
|
||||
--clr-blue: #6ba3d6;
|
||||
--clr-surface: #2a2a3e;
|
||||
--clr-header-bg: #1a1a2e;
|
||||
--clr-header-text: #f5f0e8;
|
||||
--clr-dropdown-bg: #0e0e1a;
|
||||
--clr-browser-chrome: #1a1a2e;
|
||||
}
|
||||
|
||||
.theme-sepia {
|
||||
--clr-parchment: #f4ecd8;
|
||||
--clr-parchment-dark: #e8dcc8;
|
||||
--clr-navy: #3b2f2f;
|
||||
--clr-navy-light: #5c4a4a;
|
||||
--clr-gold: #8b7355;
|
||||
--clr-gold-light: #a08868;
|
||||
--clr-red: #8b3a3a;
|
||||
--clr-red-light: #a54a4a;
|
||||
--clr-purple: #4a0e4e;
|
||||
--clr-green: #2d5a27;
|
||||
--clr-blue: #1a3a6e;
|
||||
--clr-surface: #faf3e6;
|
||||
--clr-header-bg: #3b2f2f;
|
||||
--clr-header-text: #f4ecd8;
|
||||
--clr-dropdown-bg: #5c4a4a;
|
||||
--clr-browser-chrome: #3b2f2f;
|
||||
}
|
||||
|
||||
.theme-hc {
|
||||
--clr-parchment: #ffffff;
|
||||
--clr-parchment-dark: #e6e6e6;
|
||||
--clr-navy: #000000;
|
||||
--clr-navy-light: #222222;
|
||||
--clr-gold: #666666;
|
||||
--clr-gold-light: #888888;
|
||||
--clr-red: #000000;
|
||||
--clr-red-light: #333333;
|
||||
--clr-purple: #555555;
|
||||
--clr-green: #555555;
|
||||
--clr-blue: #555555;
|
||||
--clr-surface: #ffffff;
|
||||
--clr-header-bg: #000000;
|
||||
--clr-header-text: #ffffff;
|
||||
--clr-dropdown-bg: #222222;
|
||||
--clr-browser-chrome: #000000;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -166,7 +246,7 @@ nav details.nav-group > div {
|
||||
z-index: 50;
|
||||
min-width: 14rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--color-navy-light);
|
||||
background-color: var(--color-dropdown-bg);
|
||||
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
|
||||
+12
-2
@@ -25,6 +25,15 @@ export class App {
|
||||
this.currentDate = JDate.today();
|
||||
this.settings = loadSettings();
|
||||
document.documentElement.style.setProperty('--liturgical-font-size', this.settings.fontSize + 'px');
|
||||
this.applyTheme();
|
||||
}
|
||||
|
||||
private applyTheme() {
|
||||
document.documentElement.className = 'theme-' + (this.settings.theme || 'default');
|
||||
const chromeColor = getComputedStyle(document.documentElement).getPropertyValue('--clr-browser-chrome').trim();
|
||||
if (chromeColor) {
|
||||
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', chromeColor);
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -115,10 +124,11 @@ export class App {
|
||||
|
||||
private render() {
|
||||
this.settings = loadSettings();
|
||||
this.applyTheme();
|
||||
const t = getTranslations(this.settings.language as LanguageCode);
|
||||
this.container.innerHTML = `
|
||||
<div class="app-container min-h-screen flex flex-col">
|
||||
<header class="bg-navy text-parchment p-3 flex items-center shadow-md relative">
|
||||
<header class="bg-header-bg text-header-text p-3 flex items-center shadow-md relative">
|
||||
<span id="app-title" class="hidden md:inline-flex items-center gap-2 text-lg font-bold whitespace-nowrap absolute left-3 top-1/2 -translate-y-1/2">
|
||||
<img src="/icons/icon-192.png" class="w-6 h-6" alt="">
|
||||
${t.nav.appTitle}
|
||||
@@ -127,7 +137,7 @@ export class App {
|
||||
<a href="#calendar" class="nav-link hover:text-gold transition-colors" data-view="calendar">${t.nav.calendar}</a>
|
||||
<details class="nav-group relative" data-nav="library">
|
||||
<summary class="cursor-pointer list-none hover:text-gold transition-colors flex items-center gap-1">${t.nav.library} <span class="text-xs">▾</span></summary>
|
||||
<div class="absolute left-0 top-full mt-1 z-50 min-w-56 rounded-lg bg-navy-light shadow-xl p-2 flex flex-col gap-1">
|
||||
<div class="absolute left-0 top-full mt-1 z-50 min-w-56 rounded-lg bg-dropdown-bg shadow-xl p-2 flex flex-col gap-1">
|
||||
<a href="#bible" class="nav-link hover:text-gold transition-colors px-2 py-1" data-view="bible"><span>📖</span> <span class="font-bold">${t.nav.bible}</span></a>
|
||||
<a href="#prayer" class="nav-link hover:text-gold transition-colors px-2 py-1" data-view="prayer">${t.nav.prayer}</a>
|
||||
<a href="#akathists" class="nav-link hover:text-gold transition-colors px-2 py-1" data-view="akathists">${t.nav.akathists}</a>
|
||||
|
||||
@@ -532,9 +532,9 @@ export class CalendarView {
|
||||
let classes = 'day-cell p-1 text-center text-sm cursor-pointer rounded transition-colors ';
|
||||
if (isSelected) classes += 'bg-navy text-parchment font-bold ';
|
||||
else if (isToday) classes += 'bg-gold/20 font-bold ';
|
||||
else if (dayRank >= 6) classes += 'bg-red-100 text-red-800 font-bold ';
|
||||
else if (fast === 1) classes += 'bg-purple-50 ';
|
||||
else if (fast === 2) classes += 'bg-blue-50 ';
|
||||
else if (dayRank >= 6) classes += 'bg-red/20 text-red font-bold ';
|
||||
else if (fast === 1) classes += 'bg-purple/20 ';
|
||||
else if (fast === 2) classes += 'bg-blue/20 ';
|
||||
|
||||
gridHtml += `<div class="${classes}" data-day="${d}">${d}</div>`;
|
||||
|
||||
@@ -583,21 +583,21 @@ export class CalendarView {
|
||||
|
||||
<div id="day-title" class="mb-2"></div>
|
||||
|
||||
<div id="astro-panel" class="mb-2 p-2 bg-white/50 border border-gold/20 rounded-lg text-sm text-navy">
|
||||
<div id="astro-panel" class="mb-2 p-2 bg-surface/50 border border-gold/20 rounded-lg text-sm text-navy">
|
||||
<span class="text-navy-light italic">${this.t.loading}</span>
|
||||
</div>
|
||||
|
||||
<div id="commemorations-panel" class="mb-2 p-2 bg-white/50 border border-gold/20 rounded-lg ${fnClass}">
|
||||
<div id="commemorations-panel" class="mb-2 p-2 bg-surface/50 border border-gold/20 rounded-lg ${fnClass}">
|
||||
<h3 class="font-bold text-red mb-2">${this.t.calendar.commemorations}</h3>
|
||||
<p class="text-sm text-navy-light italic">${this.t.loading}</p>
|
||||
</div>
|
||||
|
||||
<div id="fasting-panel" class="mb-2 p-2 bg-white/50 border border-gold/20 rounded-lg ${fnClass}">
|
||||
<div id="fasting-panel" class="mb-2 p-2 bg-surface/50 border border-gold/20 rounded-lg ${fnClass}">
|
||||
<h3 class="font-bold text-red mb-2">${this.t.calendar.fastingRule}</h3>
|
||||
<p class="text-sm italic text-navy-light">${this.t.loading}</p>
|
||||
</div>
|
||||
|
||||
<div id="readings-panel" class="mb-2 p-2 bg-white/50 border border-gold/20 rounded-lg ${fnClass}">
|
||||
<div id="readings-panel" class="mb-2 p-2 bg-surface/50 border border-gold/20 rounded-lg ${fnClass}">
|
||||
<h3 class="font-bold text-red mb-2">${this.t.calendar.readings}</h3>
|
||||
<p class="text-sm text-navy-light italic">${this.t.loading}</p>
|
||||
</div>
|
||||
@@ -795,7 +795,7 @@ export class CalendarView {
|
||||
const icon = this.t.calendar.rankIcons[rank] || '';
|
||||
const iconHtml = icon ? `<span class="font-slavonic text-lg mr-1">${icon}</span>` : '';
|
||||
const lifeHtml = lifeText
|
||||
? `<div class="life-text hidden mt-2 p-3 bg-white border border-gold/20 rounded text-sm leading-relaxed max-h-60 overflow-y-auto">${lifeText}</div>`
|
||||
? `<div class="life-text hidden mt-2 p-3 bg-surface border border-gold/20 rounded text-sm leading-relaxed max-h-60 overflow-y-auto">${lifeText}</div>`
|
||||
: '';
|
||||
commHtmlParts.push(
|
||||
`<div class="comm-item">
|
||||
@@ -957,7 +957,7 @@ commPanel.addEventListener('click', (e) => {
|
||||
<span class="text-navy">${solar?.sunset ?? '—'}</span>
|
||||
<span class="mx-2 text-gold/40">|</span>
|
||||
<span class="font-bold text-navy">${this.t.calendar.moonPhase}:</span>
|
||||
<span class="text-navy">${moon.emoji} ${moonName} ${moon.trend === 'waxing' ? '↗' : '↘'} (${this.t.calendar.moonDay} ${moon.moonDay}, ${moon.illumination}%)</span>
|
||||
<span class="text-navy"><span title="${moonName}">${moon.emoji}</span> ${moon.trend === 'waxing' ? '↗' : '↘'} ${this.t.calendar.moonDay} ${moon.moonDay}, ${moon.illumination}%</span>
|
||||
`;
|
||||
} else {
|
||||
astroPanel.innerHTML = `<span class="text-navy-light italic">${this.t.calendar.noLocation}</span>`;
|
||||
|
||||
@@ -72,13 +72,13 @@ export class MenaionView {
|
||||
class="sub-nav-chip text-sm transition-colors rounded-lg px-3 py-1
|
||||
${item.id === 'menaion'
|
||||
? 'border border-gold bg-gold/10 text-navy font-bold'
|
||||
: 'border border-gold/20 bg-white/50 text-navy hover:border-gold/50'}"
|
||||
: 'border border-gold/20 bg-surface/50 text-navy hover:border-gold/50'}"
|
||||
data-view="${item.view}"
|
||||
>${item.label}</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div id="menaion-calendar"></div>
|
||||
<div id="menaion-content" class="bg-white/50 border border-gold/20 rounded-lg p-4 min-h-[300px]">
|
||||
<div id="menaion-content" class="bg-surface/50 border border-gold/20 rounded-lg p-4 min-h-[300px]">
|
||||
<p class="text-navy-light italic">${this.t.prayer.selectSection}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -220,18 +220,18 @@ export class PrayerView {
|
||||
class="sub-nav-chip text-sm transition-colors rounded-lg px-3 py-1
|
||||
${this.collection === item.id
|
||||
? 'border border-gold bg-gold/10 text-navy font-bold'
|
||||
: 'border border-gold/20 bg-white/50 text-navy hover:border-gold/50'}"
|
||||
: 'border border-gold/20 bg-surface/50 text-navy hover:border-gold/50'}"
|
||||
data-view="${item.view}"
|
||||
>${item.label}</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
<p class="text-navy-light text-sm mb-2">${subtitle}</p>
|
||||
<select id="prayer-select" class="w-full border border-gold/20 bg-white/50 text-navy rounded-lg px-3 py-2 text-sm mb-3">
|
||||
<select id="prayer-select" class="w-full border border-gold/20 bg-surface/50 text-navy rounded-lg px-3 py-2 text-sm mb-3">
|
||||
${sections.map(s => `
|
||||
<option value="${s.id}" ${this.activeSection === s.id ? 'selected' : ''}>${s.name}</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
<div id="prayer-content" class="bg-white/50 border border-gold/20 rounded-lg p-4 min-h-[300px]">
|
||||
<div id="prayer-content" class="bg-surface/50 border border-gold/20 rounded-lg p-4 min-h-[300px]">
|
||||
<p class="text-navy-light italic">${this.t.prayer.selectSection}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -86,7 +86,7 @@ export class ServiceView {
|
||||
const availableServices = this.getAvailableServices(dayInfo);
|
||||
const fontClass = this.getFontClass();
|
||||
|
||||
const contentClass = `bg-white/50 border border-gold/20 rounded-lg p-4 ${this.activeService ? 'min-h-[300px]' : ''} ${fontClass}`;
|
||||
const contentClass = `bg-surface/50 border border-gold/20 rounded-lg p-4 ${this.activeService ? 'min-h-[300px]' : ''} ${fontClass}`;
|
||||
|
||||
const tabHtml = `
|
||||
<h3 class="font-bold text-red mb-2">${this.t.services.title}</h3>
|
||||
@@ -96,7 +96,7 @@ export class ServiceView {
|
||||
class="service-tab text-sm transition-colors
|
||||
${this.activeService === s.id
|
||||
? 'border border-gold bg-gold/10 text-navy font-bold rounded-lg px-3 py-1'
|
||||
: 'border border-gold/20 bg-white/50 text-navy hover:border-gold/50 rounded-lg px-3 py-1'}"
|
||||
: 'border border-gold/20 bg-surface/50 text-navy hover:border-gold/50 rounded-lg px-3 py-1'}"
|
||||
data-service="${s.id}"
|
||||
>${s.name}</button>
|
||||
`).join('')}
|
||||
@@ -134,7 +134,7 @@ export class ServiceView {
|
||||
if (id === serviceId) {
|
||||
t.className = 'service-tab text-sm transition-colors border border-gold bg-gold/10 text-navy font-bold rounded-lg px-3 py-1';
|
||||
} else {
|
||||
t.className = 'service-tab text-sm transition-colors border border-gold/20 bg-white/50 text-navy hover:border-gold/50 rounded-lg px-3 py-1';
|
||||
t.className = 'service-tab text-sm transition-colors border border-gold/20 bg-surface/50 text-navy hover:border-gold/50 rounded-lg px-3 py-1';
|
||||
}
|
||||
});
|
||||
this.loadService(service);
|
||||
|
||||
+30
-5
@@ -29,6 +29,7 @@ interface Settings {
|
||||
language: string;
|
||||
cuFont: string;
|
||||
fontSize: number;
|
||||
theme: string;
|
||||
defaultBibleVersion: string;
|
||||
showVerseNumbers: boolean;
|
||||
verseNewLine: boolean;
|
||||
@@ -42,6 +43,7 @@ const DEFAULT_SETTINGS: Settings = {
|
||||
language: 'en',
|
||||
cuFont: 'Ponomar',
|
||||
fontSize: 16,
|
||||
theme: 'default',
|
||||
defaultBibleVersion: 'kjv',
|
||||
showVerseNumbers: true,
|
||||
verseNewLine: false,
|
||||
@@ -123,7 +125,7 @@ export class SettingsView {
|
||||
<!-- Language -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-bold text-navy mb-2">${t.settings.language}</label>
|
||||
<select id="language" class="w-full border border-gold/30 rounded p-2 bg-white text-navy">
|
||||
<select id="language" class="w-full border border-gold/30 rounded p-2 bg-surface text-navy">
|
||||
${LANGUAGES.map(l => `
|
||||
<option value="${l.code}" ${l.code === this.settings.language ? 'selected' : ''}>
|
||||
${l.name} (${l.local})
|
||||
@@ -132,12 +134,25 @@ export class SettingsView {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Theme -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-bold text-red mb-3">${t.settings.theme}</h3>
|
||||
<div class="mb-3">
|
||||
<select id="theme" class="w-full border border-gold/30 rounded p-2 bg-surface text-navy">
|
||||
<option value="default" ${this.settings.theme === 'default' ? 'selected' : ''}>${t.settings.themeDefault}</option>
|
||||
<option value="dark" ${this.settings.theme === 'dark' ? 'selected' : ''}>${t.settings.themeDark}</option>
|
||||
<option value="sepia" ${this.settings.theme === 'sepia' ? 'selected' : ''}>${t.settings.themeSepia}</option>
|
||||
<option value="hc" ${this.settings.theme === 'hc' ? 'selected' : ''}>${t.settings.themeHC}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Font Settings -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-bold text-red mb-3">${t.settings.fontSettings}</h3>
|
||||
|
||||
<div class="mb-3">
|
||||
<select id="cu-font" class="w-full border border-gold/30 rounded p-2 bg-white text-navy">
|
||||
<select id="cu-font" class="w-full border border-gold/30 rounded p-2 bg-surface text-navy">
|
||||
${CU_FONTS.map(f => `
|
||||
<option value="${f.id}" ${f.id === this.settings.cuFont ? 'selected' : ''}>
|
||||
${f.id === '' ? t.settings.systemFont : f.name}
|
||||
@@ -187,12 +202,12 @@ export class SettingsView {
|
||||
<div class="flex-1">
|
||||
<label class="block text-xs font-bold text-navy mb-1">${t.settings.latitude}</label>
|
||||
<input type="number" id="latitude" step="0.0001" value="${this.settings.latitude}"
|
||||
class="w-full border border-gold/30 rounded p-2 bg-white text-navy text-sm">
|
||||
class="w-full border border-gold/30 rounded p-2 bg-surface text-navy text-sm">
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="block text-xs font-bold text-navy mb-1">${t.settings.longitude}</label>
|
||||
<input type="number" id="longitude" step="0.0001" value="${this.settings.longitude}"
|
||||
class="w-full border border-gold/30 rounded p-2 bg-white text-navy text-sm">
|
||||
class="w-full border border-gold/30 rounded p-2 bg-surface text-navy text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -209,7 +224,7 @@ export class SettingsView {
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-sm font-bold text-navy mb-1">${t.settings.defaultTranslation}</label>
|
||||
<select id="default-bible" class="w-full border border-gold/30 rounded p-2 bg-white text-navy">
|
||||
<select id="default-bible" class="w-full border border-gold/30 rounded p-2 bg-surface text-navy">
|
||||
<optgroup label="English">
|
||||
<option value="kjv" ${this.settings.defaultBibleVersion === 'kjv' ? 'selected' : ''}>KJV (${t.settings.bibleComments.fullBible})</option>
|
||||
<option value="brenton" ${this.settings.defaultBibleVersion === 'brenton' ? 'selected' : ''}>Brenton LXX (${t.settings.bibleComments.otOnly})</option>
|
||||
@@ -355,6 +370,16 @@ export class SettingsView {
|
||||
saveSettings(this.settings);
|
||||
});
|
||||
|
||||
document.getElementById('theme')?.addEventListener('change', (e) => {
|
||||
this.settings.theme = (e.target as HTMLSelectElement).value;
|
||||
saveSettings(this.settings);
|
||||
document.documentElement.className = 'theme-' + this.settings.theme;
|
||||
const chromeColor = getComputedStyle(document.documentElement).getPropertyValue('--clr-browser-chrome').trim();
|
||||
if (chromeColor) {
|
||||
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', chromeColor);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('default-bible')?.addEventListener('change', (e) => {
|
||||
this.settings.defaultBibleVersion = (e.target as HTMLSelectElement).value;
|
||||
saveSettings(this.settings);
|
||||
|
||||
Reference in new issue
Block a user