Pre-compute romcal year maps on server boot for current + next civil year across en/de/la in each rite's default diocese, non-blocking so startup is unaffected. Also fixes several 1962-rite rendering bugs: commemorations previously leaked 1969-shape ids (e.g. andrew_apostle) next to proper 1962 sancti; station church names came through unresolved because RomcalConfig's internal i18next has no bundle loaded; season names arrived as raw keys (advent.season) for the same reason. All three now resolve locally via the shipped 1962 bundle with Latin as fallback. ClassIV ferias get a small dot on the grid.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homepage",
|
"name": "homepage",
|
||||||
"version": "1.37.6",
|
"version": "1.37.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Generated
+11
-11
@@ -22,7 +22,7 @@ importers:
|
|||||||
version: 2.1.8-no-fsevents.3
|
version: 2.1.8-no-fsevents.3
|
||||||
'@romcal/calendar.general-roman':
|
'@romcal/calendar.general-roman':
|
||||||
specifier: 3.0.0-dev.125
|
specifier: 3.0.0-dev.125
|
||||||
version: 3.0.0-dev.125(romcal@https://codeload.github.com/AlexBocken/romcal/tar.gz/efbaab5813ecbdd4956d357c3f3a9bf06b6e1dde(typescript@6.0.2))
|
version: 3.0.0-dev.125(romcal@https://codeload.github.com/AlexBocken/romcal/tar.gz/e4530cf926486d229ae4a22c32e7d8b4a56e5077(typescript@6.0.2))
|
||||||
'@sveltejs/adapter-node':
|
'@sveltejs/adapter-node':
|
||||||
specifier: ^5.5.4
|
specifier: ^5.5.4
|
||||||
version: 5.5.4(@sveltejs/kit@2.56.1(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.1)(vite@8.0.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.1)(@types/node@22.18.0)(esbuild@0.27.3)(terser@5.46.0)))(svelte@5.55.1)(typescript@6.0.2)(vite@8.0.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.1)(@types/node@22.18.0)(esbuild@0.27.3)(terser@5.46.0)))
|
version: 5.5.4(@sveltejs/kit@2.56.1(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.1)(vite@8.0.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.1)(@types/node@22.18.0)(esbuild@0.27.3)(terser@5.46.0)))(svelte@5.55.1)(typescript@6.0.2)(vite@8.0.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.1)(@types/node@22.18.0)(esbuild@0.27.3)(terser@5.46.0)))
|
||||||
@@ -55,7 +55,7 @@ importers:
|
|||||||
version: 4.2.1
|
version: 4.2.1
|
||||||
romcal:
|
romcal:
|
||||||
specifier: github:AlexBocken/romcal#dev
|
specifier: github:AlexBocken/romcal#dev
|
||||||
version: https://codeload.github.com/AlexBocken/romcal/tar.gz/efbaab5813ecbdd4956d357c3f3a9bf06b6e1dde(typescript@6.0.2)
|
version: https://codeload.github.com/AlexBocken/romcal/tar.gz/e4530cf926486d229ae4a22c32e7d8b4a56e5077(typescript@6.0.2)
|
||||||
sharp:
|
sharp:
|
||||||
specifier: ^0.34.5
|
specifier: ^0.34.5
|
||||||
version: 0.34.5
|
version: 0.34.5
|
||||||
@@ -1421,8 +1421,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
i18next@26.0.4:
|
i18next@26.0.6:
|
||||||
resolution: {integrity: sha512-gXF7U9bfioXPLv7mw8Qt2nfO7vij5MyINvPgVv99pX3fL1Y01pw2mKBFrlYpRxRCl2wz3ISenj6VsMJT2isfuA==}
|
resolution: {integrity: sha512-A4U6eCXodIbrhf8EarRurB9/4ebyaurH4+fu4gig9bqxmpSt+fCAFm/GpRQDcN1Xzu/LdFCx4nYHsnM1edIIbg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: ^5 || ^6
|
typescript: ^5 || ^6
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@@ -1782,8 +1782,8 @@ packages:
|
|||||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
romcal@https://codeload.github.com/AlexBocken/romcal/tar.gz/efbaab5813ecbdd4956d357c3f3a9bf06b6e1dde:
|
romcal@https://codeload.github.com/AlexBocken/romcal/tar.gz/e4530cf926486d229ae4a22c32e7d8b4a56e5077:
|
||||||
resolution: {tarball: https://codeload.github.com/AlexBocken/romcal/tar.gz/efbaab5813ecbdd4956d357c3f3a9bf06b6e1dde}
|
resolution: {tarball: https://codeload.github.com/AlexBocken/romcal/tar.gz/e4530cf926486d229ae4a22c32e7d8b4a56e5077}
|
||||||
version: 3.0.0-dev.125
|
version: 3.0.0-dev.125
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
@@ -2683,9 +2683,9 @@ snapshots:
|
|||||||
'@rollup/rollup-win32-x64-msvc@4.60.1':
|
'@rollup/rollup-win32-x64-msvc@4.60.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@romcal/calendar.general-roman@3.0.0-dev.125(romcal@https://codeload.github.com/AlexBocken/romcal/tar.gz/efbaab5813ecbdd4956d357c3f3a9bf06b6e1dde(typescript@6.0.2))':
|
'@romcal/calendar.general-roman@3.0.0-dev.125(romcal@https://codeload.github.com/AlexBocken/romcal/tar.gz/e4530cf926486d229ae4a22c32e7d8b4a56e5077(typescript@6.0.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
romcal: https://codeload.github.com/AlexBocken/romcal/tar.gz/efbaab5813ecbdd4956d357c3f3a9bf06b6e1dde(typescript@6.0.2)
|
romcal: https://codeload.github.com/AlexBocken/romcal/tar.gz/e4530cf926486d229ae4a22c32e7d8b4a56e5077(typescript@6.0.2)
|
||||||
|
|
||||||
'@sec-ant/readable-stream@0.4.1': {}
|
'@sec-ant/readable-stream@0.4.1': {}
|
||||||
|
|
||||||
@@ -3181,7 +3181,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
i18next@26.0.4(typescript@6.0.2):
|
i18next@26.0.6(typescript@6.0.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.2
|
'@babel/runtime': 7.29.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -3545,9 +3545,9 @@ snapshots:
|
|||||||
'@rollup/rollup-win32-x64-msvc': 4.60.1
|
'@rollup/rollup-win32-x64-msvc': 4.60.1
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
romcal@https://codeload.github.com/AlexBocken/romcal/tar.gz/efbaab5813ecbdd4956d357c3f3a9bf06b6e1dde(typescript@6.0.2):
|
romcal@https://codeload.github.com/AlexBocken/romcal/tar.gz/e4530cf926486d229ae4a22c32e7d8b4a56e5077(typescript@6.0.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
i18next: 26.0.4(typescript@6.0.2)
|
i18next: 26.0.6(typescript@6.0.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import * as auth from "./auth"
|
|||||||
import { initializeScheduler } from "./lib/server/scheduler"
|
import { initializeScheduler } from "./lib/server/scheduler"
|
||||||
import { dbConnect } from "./utils/db"
|
import { dbConnect } from "./utils/db"
|
||||||
import { errorWithVerse, getRandomVerse } from "$lib/server/errorQuote"
|
import { errorWithVerse, getRandomVerse } from "$lib/server/errorQuote"
|
||||||
|
import { warmLiturgicalCache } from "$lib/server/liturgicalCalendar"
|
||||||
|
|
||||||
async function timing({ event, resolve }: Parameters<Handle>[0]) {
|
async function timing({ event, resolve }: Parameters<Handle>[0]) {
|
||||||
const marks: Record<string, number> = {};
|
const marks: Record<string, number> = {};
|
||||||
@@ -43,6 +44,16 @@ await dbConnect().then(() => {
|
|||||||
// Don't crash the server - API routes will attempt reconnection
|
// Don't crash the server - API routes will attempt reconnection
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Warm liturgical calendar cache in the background — non-blocking so the
|
||||||
|
// server starts accepting requests immediately; any request arriving before
|
||||||
|
// warmup completes falls back to lazy computation (still correct, just cold).
|
||||||
|
{
|
||||||
|
const t0 = performance.now();
|
||||||
|
warmLiturgicalCache()
|
||||||
|
.then(() => console.log(`✅ Liturgical calendar cache warmed in ${Math.round(performance.now() - t0)}ms`))
|
||||||
|
.catch((error) => console.error('⚠️ Liturgical calendar warmup failed:', error));
|
||||||
|
}
|
||||||
|
|
||||||
async function authorization({ event, resolve }: Parameters<Handle>[0]) {
|
async function authorization({ event, resolve }: Parameters<Handle>[0]) {
|
||||||
const session = await event.locals.timing.measure('auth', () => event.locals.auth());
|
const session = await event.locals.timing.measure('auth', () => event.locals.auth());
|
||||||
event.locals.session = session;
|
event.locals.session = session;
|
||||||
|
|||||||
@@ -39,10 +39,17 @@ export interface Rite1962Commem {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Rite1962StationChurch {
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
mass?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Rite1962Detail {
|
export interface Rite1962Detail {
|
||||||
class: 1 | 2 | 3 | 4;
|
class: 1 | 2 | 3 | 4;
|
||||||
kind: 'tempora' | 'sancti';
|
kind: 'tempora' | 'sancti';
|
||||||
commemorations: Rite1962Commem[];
|
commemorations: Rite1962Commem[];
|
||||||
|
stationChurches?: Rite1962StationChurch[];
|
||||||
octave?: {
|
octave?: {
|
||||||
ofId: string;
|
ofId: string;
|
||||||
day: number;
|
day: number;
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import { createRequire } from 'node:module';
|
|||||||
import { existsSync, readFileSync } from 'node:fs';
|
import { existsSync, readFileSync } from 'node:fs';
|
||||||
import {
|
import {
|
||||||
colorLabel1962,
|
colorLabel1962,
|
||||||
|
DEFAULT_DIOCESE_1962,
|
||||||
|
DEFAULT_DIOCESE_1969,
|
||||||
rank1962Label,
|
rank1962Label,
|
||||||
season1962Label,
|
season1962Label,
|
||||||
type CalendarLang,
|
type CalendarLang,
|
||||||
@@ -153,16 +155,17 @@ function getRomcal1962(lang: CalendarLang, diocese: Diocese1962): Promise<Romcal
|
|||||||
let p = romcal1962ByKey.get(key);
|
let p = romcal1962ByKey.get(key);
|
||||||
if (p) return p;
|
if (p) return p;
|
||||||
const calendar = calendars1962[diocese];
|
const calendar = calendars1962[diocese];
|
||||||
// `localizedCalendar` must be a 1969-shape bundle; the 1962 names live on
|
// Do NOT pass `localizedCalendar` here: RomcalConfig's bundle-only branch
|
||||||
// the 1962 propers bundle and are injected via createI18n1962 extraNames.
|
// (`if (config?.localizedCalendar)` in rites/roman1969/src/models/config.ts)
|
||||||
const base1969 = bundles1969.general[lang];
|
// pushes only the bundle's inputs and skips `particularCalendar` AND the
|
||||||
|
// 1962 sanctoral layering entirely. The 1962 names live on the 1962
|
||||||
|
// propers bundle and are supplied via `createI18n1962` + `resolveName1962`.
|
||||||
p = loadBundle1962(lang).then((b) => {
|
p = loadBundle1962(lang).then((b) => {
|
||||||
const i18next = createI18n1962(lang, { [lang]: b.i18n.names });
|
const i18next = createI18n1962(lang, { [lang]: b.i18n.names });
|
||||||
// `i18next` is part of Romcal's runtime config but absent from the
|
// `i18next` is part of Romcal's runtime config but absent from the
|
||||||
// published input type. Build via a permissive record so TS accepts it.
|
// published input type. Build via a permissive record so TS accepts it.
|
||||||
const base: Record<string, unknown> = {
|
const base: Record<string, unknown> = {
|
||||||
i18next,
|
i18next,
|
||||||
localizedCalendar: base1969,
|
|
||||||
scope: 'liturgical'
|
scope: 'liturgical'
|
||||||
};
|
};
|
||||||
if (calendar) base.particularCalendar = calendar;
|
if (calendar) base.particularCalendar = calendar;
|
||||||
@@ -203,8 +206,57 @@ function colorKeysFrom(c: LiturgicalDay1962): string[] {
|
|||||||
return c.colors ? [...c.colors] : [];
|
return c.colors ? [...c.colors] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildCommemorations(d: LiturgicalDay1962): Rite1962Commem[] {
|
function buildCommemorations(
|
||||||
return (d.commemorations ?? []).map((c) => ({ id: c.id, name: c.name }));
|
d: LiturgicalDay1962,
|
||||||
|
localBundle: RomcalBundle1962 | null,
|
||||||
|
laBundle: RomcalBundle1962
|
||||||
|
): Rite1962Commem[] {
|
||||||
|
const out: Rite1962Commem[] = [];
|
||||||
|
for (const c of d.commemorations ?? []) {
|
||||||
|
const resolved = resolveCommemName(c.id, c.name, localBundle, laBundle);
|
||||||
|
// Drop 1969 GRC leaks: the hardcoded `GeneralRoman` import in
|
||||||
|
// RomcalConfig adds 1969-shaped ids (e.g. `andrew_apostle`) that are
|
||||||
|
// not in either 1962 bundle. They show up as losers on the same date
|
||||||
|
// as proper 1962 sancti — filter them out.
|
||||||
|
if (resolved == null) continue;
|
||||||
|
out.push({ id: c.id, name: resolved });
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCommemName(
|
||||||
|
id: string,
|
||||||
|
raw: string | undefined,
|
||||||
|
localBundle: RomcalBundle1962 | null,
|
||||||
|
laBundle: RomcalBundle1962
|
||||||
|
): string | null {
|
||||||
|
const bundles = [localBundle, laBundle].filter(
|
||||||
|
(b): b is RomcalBundle1962 => b != null
|
||||||
|
);
|
||||||
|
for (const b of bundles) {
|
||||||
|
const v = b.i18n.names?.[id];
|
||||||
|
if (v && v !== id) return v;
|
||||||
|
}
|
||||||
|
// Not in any 1962 bundle → treat as 1969 leak and drop.
|
||||||
|
if (!raw || raw === id) return null;
|
||||||
|
// Defensive: raw looks like an i18n key path (namespace/key) — also drop.
|
||||||
|
if (/^[a-z][a-z0-9_]*[/.][a-z_]/.test(raw)) return null;
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveStationName(
|
||||||
|
key: string,
|
||||||
|
localBundle: RomcalBundle1962 | null,
|
||||||
|
laBundle: RomcalBundle1962
|
||||||
|
): string {
|
||||||
|
const bundles = [localBundle, laBundle].filter(
|
||||||
|
(b): b is RomcalBundle1962 => b != null
|
||||||
|
);
|
||||||
|
for (const b of bundles) {
|
||||||
|
const v = (b.i18n as { stationChurches?: Record<string, string> }).stationChurches?.[key];
|
||||||
|
if (v && v !== key) return v;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sectionsFromBundle(
|
function sectionsFromBundle(
|
||||||
@@ -292,9 +344,20 @@ function adaptDay1962(
|
|||||||
const detail: Rite1962Detail = {
|
const detail: Rite1962Detail = {
|
||||||
class: classOf,
|
class: classOf,
|
||||||
kind: d.kind1962 ?? 'tempora',
|
kind: d.kind1962 ?? 'tempora',
|
||||||
commemorations: buildCommemorations(d),
|
commemorations: buildCommemorations(d, localBundle, laBundle),
|
||||||
propers
|
propers
|
||||||
};
|
};
|
||||||
|
if (d.stationChurches && d.stationChurches.length > 0) {
|
||||||
|
// Romcal's internal i18next has no resource bundles loaded (RomcalConfig
|
||||||
|
// ignores the `i18next` we pass in input), so `s.name` arrives equal to
|
||||||
|
// `s.key`. Resolve from the ships-with-bundle lookup table here, with
|
||||||
|
// Latin as a fallback floor.
|
||||||
|
detail.stationChurches = d.stationChurches.map((s) => ({
|
||||||
|
key: s.key,
|
||||||
|
name: resolveStationName(s.key, localBundle, laBundle),
|
||||||
|
...(s.mass ? { mass: s.mass } : {})
|
||||||
|
}));
|
||||||
|
}
|
||||||
if (d.octaveOf) detail.octave = { ofId: d.octaveOf.ofId, day: d.octaveOf.day };
|
if (d.octaveOf) detail.octave = { ofId: d.octaveOf.ofId, day: d.octaveOf.day };
|
||||||
if (d.vigilOf) detail.vigilOf = d.vigilOf;
|
if (d.vigilOf) detail.vigilOf = d.vigilOf;
|
||||||
if (d.transferredFromDate) detail.transferredFrom = d.transferredFromDate;
|
if (d.transferredFromDate) detail.transferredFrom = d.transferredFromDate;
|
||||||
@@ -307,7 +370,11 @@ function adaptDay1962(
|
|||||||
rankName: rank1962Label(classKey, lang),
|
rankName: rank1962Label(classKey, lang),
|
||||||
rank: classKey,
|
rank: classKey,
|
||||||
seasonKey,
|
seasonKey,
|
||||||
seasonNames: d.seasonNames ? [...d.seasonNames] : [],
|
// Romcal's own seasonNames come through unresolved ("advent.season") because
|
||||||
|
// RomcalConfig's internal i18next has no resource bundle loaded — we pass
|
||||||
|
// neither `localizedCalendar` nor a positional `locale`. Resolve here via
|
||||||
|
// our own helper, which handles both 1962 CamelCase and 1969 SCREAMING_SNAKE.
|
||||||
|
seasonNames: seasonKey ? [season1962Label(seasonKey, lang)] : [],
|
||||||
colorNames,
|
colorNames,
|
||||||
colorKeys,
|
colorKeys,
|
||||||
psalterWeek: null,
|
psalterWeek: null,
|
||||||
@@ -340,9 +407,6 @@ export async function getYear1962(
|
|||||||
map.set(iso, adaptDay1962(principal, lang, laBundle, localBundle));
|
map.set(iso, adaptDay1962(principal, lang, laBundle, localBundle));
|
||||||
}
|
}
|
||||||
yearCache1962.set(cacheKey, map);
|
yearCache1962.set(cacheKey, map);
|
||||||
// keep season1962Label referenced so tree-shakers don't drop it while the
|
|
||||||
// detail view gradually takes over rendering its own labels.
|
|
||||||
void season1962Label;
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,3 +415,22 @@ export function isoFor(year: number, month: number, day: number): string {
|
|||||||
const dd = String(day).padStart(2, '0');
|
const dd = String(day).padStart(2, '0');
|
||||||
return `${year}-${mm}-${dd}`;
|
return `${year}-${mm}-${dd}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-compute liturgical-calendar maps for the current and next civil year
|
||||||
|
// across all supported languages for each rite's default diocese. Pages hit
|
||||||
|
// year N and N+1 on every request (AdventI-rollover logic), so warming this
|
||||||
|
// slice means the hot path — today's view in the default rite/diocese — is
|
||||||
|
// cache-hot on the first request after boot. Non-default dioceses stay lazy.
|
||||||
|
const WARMUP_LANGS: readonly CalendarLang[] = ['en', 'de', 'la'] as const;
|
||||||
|
export async function warmLiturgicalCache(): Promise<void> {
|
||||||
|
const year = new Date().getFullYear();
|
||||||
|
const years = [year, year + 1];
|
||||||
|
const tasks: Promise<unknown>[] = [];
|
||||||
|
for (const y of years) {
|
||||||
|
for (const lang of WARMUP_LANGS) {
|
||||||
|
tasks.push(getYear(lang, DEFAULT_DIOCESE_1969, y));
|
||||||
|
tasks.push(getYear1962(lang, DEFAULT_DIOCESE_1962, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(tasks);
|
||||||
|
}
|
||||||
|
|||||||
+42
@@ -250,6 +250,17 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if hero.rite1962?.stationChurches?.length}
|
||||||
|
<div class="tc-stations">
|
||||||
|
<span class="tc-stations-label" aria-hidden="true">✦</span>
|
||||||
|
<span class="tc-stations-text">
|
||||||
|
<span class="tc-stations-title">{t1962('stationChurch', lang)}:</span>
|
||||||
|
{#each hero.rite1962.stationChurches as s, i (s.key + (s.mass ?? ''))}
|
||||||
|
{#if i > 0}<span class="tc-stations-sep"> · </span>{/if}<span class="tc-station-name">{s.name}</span>{#if s.mass}<span class="tc-station-mass"> ({s.mass.replace(/_/g, ' ')})</span>{/if}
|
||||||
|
{/each}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<span class="tc-arrow" aria-hidden="true">→</span>
|
<span class="tc-arrow" aria-hidden="true">→</span>
|
||||||
</section>
|
</section>
|
||||||
</a>
|
</a>
|
||||||
@@ -660,6 +671,37 @@
|
|||||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
}
|
}
|
||||||
|
.tc-stations {
|
||||||
|
margin-top: 0.9rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.55rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
.tc-stations-label {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.tc-stations-title {
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
opacity: 0.85;
|
||||||
|
margin-right: 0.35rem;
|
||||||
|
}
|
||||||
|
.tc-station-name {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.tc-station-mass {
|
||||||
|
opacity: 0.75;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
.tc-stations-sep {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
.tc-arrow {
|
.tc-arrow {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 1.1rem;
|
bottom: 1.1rem;
|
||||||
|
|||||||
+32
-4
@@ -154,6 +154,21 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if d.stationChurches?.length}
|
||||||
|
<div class="stations">
|
||||||
|
<h4>{t1962('stationChurch', lang)}</h4>
|
||||||
|
<ul>
|
||||||
|
{#each d.stationChurches as s (s.key + (s.mass ?? ''))}
|
||||||
|
<li>
|
||||||
|
<span class="station-name">{s.name}</span>
|
||||||
|
{#if s.mass}
|
||||||
|
<span class="station-mass">{s.mass.replace(/_/g, ' ')}</span>
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#if d.propers.length}
|
{#if d.propers.length}
|
||||||
<section class="propers">
|
<section class="propers">
|
||||||
<h4>{t1962('propers', lang)}</h4>
|
<h4>{t1962('propers', lang)}</h4>
|
||||||
@@ -351,6 +366,7 @@
|
|||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
.commems h4,
|
.commems h4,
|
||||||
|
.stations h4,
|
||||||
.propers h4 {
|
.propers h4 {
|
||||||
margin: 0.5rem 0 0.4rem;
|
margin: 0.5rem 0 0.4rem;
|
||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
@@ -359,10 +375,12 @@
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.commems {
|
.commems,
|
||||||
|
.stations {
|
||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
}
|
}
|
||||||
.commems ul {
|
.commems ul,
|
||||||
|
.stations ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -370,7 +388,8 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
}
|
}
|
||||||
.commems li {
|
.commems li,
|
||||||
|
.stations li {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
@@ -379,10 +398,19 @@
|
|||||||
border-radius: var(--radius-sm, 6px);
|
border-radius: var(--radius-sm, 6px);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.commem-name {
|
.commem-name,
|
||||||
|
.station-name {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
.station-name {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.station-mass {
|
||||||
|
color: var(--color-text-tertiary);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
.propers {
|
.propers {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|||||||
@@ -68,5 +68,6 @@ export function rankDotSize(rank: string): number {
|
|||||||
if (rank === 'ClassII' || rank === 'FEAST' || rank === 'SUNDAY' || rank === 'HOLY_DAY_OF_OBLIGATION')
|
if (rank === 'ClassII' || rank === 'FEAST' || rank === 'SUNDAY' || rank === 'HOLY_DAY_OF_OBLIGATION')
|
||||||
return 4;
|
return 4;
|
||||||
if (rank === 'ClassIII' || rank === 'MEMORIAL') return 3;
|
if (rank === 'ClassIII' || rank === 'MEMORIAL') return 3;
|
||||||
return 0; // don't render ferias/weekdays as dots
|
if (rank === 'ClassIV') return 2;
|
||||||
|
return 0; // 1969 weekdays/opt-memorials still skipped
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,7 +287,8 @@ export const ui1962 = {
|
|||||||
vigilOf: { en: 'Vigil of', de: 'Vigil von', la: 'Vigilia' },
|
vigilOf: { en: 'Vigil of', de: 'Vigil von', la: 'Vigilia' },
|
||||||
transferredFrom: { en: 'Transferred from', de: 'Übertragen von', la: 'Translatum ex' },
|
transferredFrom: { en: 'Transferred from', de: 'Übertragen von', la: 'Translatum ex' },
|
||||||
source: { en: 'Source', de: 'Quelle', la: 'Fons' },
|
source: { en: 'Source', de: 'Quelle', la: 'Fons' },
|
||||||
propers: { en: 'Mass propers', de: 'Messproprium', la: 'Propria Missæ' }
|
propers: { en: 'Mass propers', de: 'Messproprium', la: 'Propria Missæ' },
|
||||||
|
stationChurch: { en: 'Station church', de: 'Stationskirche', la: 'Statio' }
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export function t1962(key: keyof typeof ui1962, lang: CalendarLang): string {
|
export function t1962(key: keyof typeof ui1962, lang: CalendarLang): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user