Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions components/liturgy/LiturgyChantPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useMemo, useState } from 'react';
import type { LiturgyDoc, Sangha, Witness } from '../../types/liturgy';
import { SectionRenderer } from './SectionRenderer';
import { ProseBlock } from './ProseBlock';
import { LiturgySettingsProvider, SettingsButton } from './LiturgySettings';
import { LiturgySettingsProvider, SettingsButton, useLiturgySettings } from './LiturgySettings';
import { WitnessDots } from './shapes/TripleScriptWitness';

/**
Expand Down Expand Up @@ -47,11 +47,23 @@ const TRADITION_LABELS: Record<LiturgyDoc['tradition'], string> = {
export const LiturgyChantPage: React.FC<{ doc: LiturgyDoc; sangha?: Sangha }> = ({
doc,
sangha,
}) => {
return (
<LiturgySettingsProvider>
<LiturgyChantPageBody doc={doc} sangha={sangha} />
</LiturgySettingsProvider>
);
};

const LiturgyChantPageBody: React.FC<{ doc: LiturgyDoc; sangha?: Sangha }> = ({
doc,
sangha,
}) => {
const backHref = sangha ? `/liturgy/${sangha.slug}` : '/liturgy';
const backLabel = sangha ? sangha.name : 'Liturgy';
const witnesses = useMemo(() => uniqueWitnesses(doc), [doc]);
const primaryWitness = witnesses[0]?.by ?? '';
const { settings } = useLiturgySettings();

// Page-level witness picker state. The dots row lives once at the top of
// the chant body; every triple-script-witness section honors the same
Expand All @@ -65,8 +77,10 @@ export const LiturgyChantPage: React.FC<{ doc: LiturgyDoc; sangha?: Sangha }> =
};

return (
<LiturgySettingsProvider>
<div className="min-h-screen bg-slate-950 text-slate-100">
<div
className="min-h-screen bg-slate-950 text-slate-100"
style={{ ['--liturgy-scale' as string]: settings.fontScale }}
>
{/* Thin nav at top — the chant artifact itself carries title-weight,
either by way of a recognizable opening line (morning-chants'
"Namo tassa...") or an explicit title-as-segment first section
Expand Down Expand Up @@ -178,7 +192,6 @@ export const LiturgyChantPage: React.FC<{ doc: LiturgyDoc; sangha?: Sangha }> =
)}
</footer>
</div>
</LiturgySettingsProvider>
);
};

Expand Down
51 changes: 50 additions & 1 deletion components/liturgy/LiturgySettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,28 @@ import React, { createContext, useContext, useEffect, useState } from 'react';
*/

export type LiturgySettings = {
/** Show subtle hue accents on refrain words (Buddhaṁ=sky, Dhammaṁ=amber, etc.). */
/** Show subtle hue accents on refrain words (Buddha=amber, Dharma=sky, Sangha=rose). */
showAccents: boolean;
/**
* Show a small Roman / phonetic transliteration line beneath non-Latin
* scripts (Chinese, Japanese, Tibetan, Devanāgarī). Helps readers who
* can't read the source script pronounce it.
*/
showTransliteration: boolean;
/**
* Multiplier on the chant body's base font size. 1.0 = default
* (mn10-matching large chant rendering). Range 0.7–1.6 via the
* settings slider. Applied as a `--liturgy-scale` CSS variable on
* the LiturgyChantPage wrapper; chant-body Tailwind classes read it
* via `text-[calc(var(--liturgy-scale,1)*Xrem)]`.
*/
fontScale: number;
};

const DEFAULT_SETTINGS: LiturgySettings = {
showAccents: true,
showTransliteration: true,
fontScale: 1.0,
};

const STORAGE_KEY = 'liturgy:settings';
Expand Down Expand Up @@ -142,6 +151,46 @@ export const SettingsButton: React.FC = () => {
<div className="text-[10px] text-slate-600 mt-1 leading-snug">
Phonetic line beneath the chant: romanization beneath non-Latin scripts, pronunciation respelling beneath Latin scripts.
</div>
{/* Font-size slider — multiplies the chant body. Default 1.0
matches the mn10 demo's large chant rendering. */}
<div className="mt-4">
<div className="flex items-center justify-between text-slate-300">
<span>Chant text size</span>
<span className="text-[10px] text-slate-500 tabular-nums">{Math.round(settings.fontScale * 100)}%</span>
</div>
<input
type="range"
min={0.7}
max={1.6}
step={0.05}
value={settings.fontScale}
onChange={(e) => setSettings({ ...settings, fontScale: parseFloat(e.target.value) })}
className="w-full mt-2 accent-emerald-500"
aria-label="Chant body font size"
/>
<div className="flex items-center justify-between text-[10px] text-slate-600 mt-1">
<button
type="button"
onClick={() => setSettings({ ...settings, fontScale: 0.85 })}
className="hover:text-emerald-400"
>Small</button>
<button
type="button"
onClick={() => setSettings({ ...settings, fontScale: 1.0 })}
className="hover:text-emerald-400"
>Default</button>
<button
type="button"
onClick={() => setSettings({ ...settings, fontScale: 1.2 })}
className="hover:text-emerald-400"
>Large</button>
<button
type="button"
onClick={() => setSettings({ ...settings, fontScale: 1.45 })}
className="hover:text-emerald-400"
>XL</button>
</div>
</div>
</div>
)}
</div>
Expand Down
19 changes: 17 additions & 2 deletions components/liturgy/shapes/SoundFormula.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ const SCRIPT_FONT: Record<string, string> = {
Hang: "'Noto Serif KR', serif",
};

// Per-script size multipliers — same values used in TripleScriptWitness.
const SCRIPT_SIZE_MULTIPLIER: Record<string, number> = {
Latn: 1.0,
Deva: 1.05,
Hant: 1.2,
Hans: 1.2,
Jpan: 1.2,
Tibt: 1.1,
Hang: 1.15,
};

function scriptSubtag(lang: string): string {
const parts = lang.split('-');
return parts.length >= 2 ? parts[1] : 'Latn';
Expand All @@ -54,8 +65,12 @@ const FormulaLine: React.FC<{ variant: ScriptVariant }> = ({ variant }) => {
return (
<>
<div
className="text-center text-4xl md:text-5xl lg:text-6xl leading-relaxed text-slate-100 tracking-wide select-text"
style={{ fontFamily, letterSpacing: script === 'Latn' ? '0.05em' : undefined }}
className="text-center leading-relaxed text-slate-100 tracking-wide select-text"
style={{
fontFamily,
letterSpacing: script === 'Latn' ? '0.05em' : undefined,
fontSize: `calc(${3 * (SCRIPT_SIZE_MULTIPLIER[script] ?? 1)}rem * var(--liturgy-scale, 1))`,
}}
lang={variant.lang}
>
{variant.text}
Expand Down
41 changes: 34 additions & 7 deletions components/liturgy/shapes/TripleScriptWitness.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@ const SCRIPT_FONT: Record<string, string> = {
Hang: "'Noto Serif KR', serif",
};

/**
* Per-script size multipliers. Latin (IAST/Pāli) is the baseline (1.0).
* CJK and Devanāgarī benefit from slightly larger rendering because their
* glyphs carry more visual detail per unit; the reader needs more pixels
* to resolve them comfortably. The English translation line below the
* chant body gets its own bump so the gloss reads as easily as the chant.
*/
const SCRIPT_SIZE_MULTIPLIER: Record<string, number> = {
Latn: 1.0,
Deva: 1.05,
Hant: 1.2,
Hans: 1.2,
Jpan: 1.2,
Tibt: 1.1,
Hang: 1.15,
};
const ENGLISH_LINE_MULTIPLIER = 1.4;

/** Resolve the script subtag from a BCP-47 tag (e.g. "sa-Latn" → "Latn"). */
function scriptSubtag(lang: string): string {
const parts = lang.split('-');
Expand Down Expand Up @@ -174,7 +192,7 @@ const TransliterationLine: React.FC<{
if (!respelling) return null;
return (
<div
className="text-slate-500 italic text-sm mt-1 leading-relaxed select-text tracking-wide"
className="relative z-0 text-slate-500 italic text-sm mt-1 leading-relaxed select-text tracking-wide"
style={{ fontFamily: SCRIPT_FONT.Latn }}
aria-label={`Pronunciation respelling of ${variant.label}`}
>
Expand All @@ -186,7 +204,7 @@ const TransliterationLine: React.FC<{
if (!variant.transliteration) return null;
return (
<div
className="text-slate-500 italic text-sm mt-1 leading-relaxed select-text"
className="relative z-0 text-slate-500 italic text-sm mt-1 leading-relaxed select-text"
style={{ fontFamily: SCRIPT_FONT.Latn }}
aria-label={`Transliteration of ${variant.label}`}
>
Expand Down Expand Up @@ -573,7 +591,11 @@ const PaliLine: React.FC<{
}> = ({ text, words = [], large = false, lang, tokens: tokenHints }) => {
const { settings } = useLiturgySettings();
const script = scriptSubtag(lang);
const sizeClass = large ? 'text-2xl md:text-3xl' : 'text-xl md:text-2xl';
// Base font sizes (rem). Reader can tune via the settings slider, which
// sets `--liturgy-scale` on the LiturgyChantPage wrapper. Each script
// also carries its own multiplier — CJK + Tibetan glyphs benefit from
// a slight upscale so the visual weight matches Latin chant body.
const baseRem = (large ? 1.875 : 1.5) * (SCRIPT_SIZE_MULTIPLIER[script] ?? 1);
const fontStack = SCRIPT_FONT[script] ?? SCRIPT_FONT.Latn;

const tokens = (() => {
Expand Down Expand Up @@ -615,8 +637,8 @@ const PaliLine: React.FC<{

return (
<div
className={`text-slate-100 leading-loose ${sizeClass}`}
style={{ fontFamily: fontStack }}
className="text-slate-100 leading-loose"
style={{ fontFamily: fontStack, fontSize: `calc(${baseRem}rem * var(--liturgy-scale, 1))` }}
lang={lang}
>
{tokens.map((t, i) => {
Expand Down Expand Up @@ -1236,8 +1258,13 @@ const SegmentRow: React.FC<{
title={segment.witnesses.length > 1 ? 'Click to switch translation' : undefined}
>
<div
className="text-slate-300 italic leading-relaxed text-base md:text-lg"
style={{ fontFamily: SERIF_STACK }}
className="text-slate-300 italic leading-relaxed"
style={{
fontFamily: SERIF_STACK,
// English translation line — 1.125rem base × ENGLISH_LINE_MULTIPLIER
// (1.4 by default) so the gloss reads as easily as the chant.
fontSize: `calc(${1.125 * ENGLISH_LINE_MULTIPLIER}rem * var(--liturgy-scale, 1))`,
}}
>
<EnglishLine
text={currentWitness.text}
Expand Down
2 changes: 1 addition & 1 deletion components/sutta-studio/demoPacket.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import type { DeepLoomPacket } from '../../types/suttaStudio';
import demoData from './demoPacket.json';
import demoData from '../../content/references/sutta/mn10.json';
export const DEMO_PACKET_MN10: DeepLoomPacket = demoData as unknown as DeepLoomPacket;
8 changes: 4 additions & 4 deletions data/liturgy/bodhi-vows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const bodhiVows: LiturgyDoc = {
pali: 'Shujō muhen sē gan do',
scripts: [
{ lang: 'ja-Jpan', label: 'Sino-Japanese', text: 'Shujō muhen sē gan do', tokens: ['Shujō', 'muhen', 'sē gan', 'do'] },
{ lang: 'zh-Hant', label: 'Hanzi', text: '衆生無邊誓願度', tokens: ['衆生', '無邊', '誓願', '度'], transliteration: 'Shujō muhen sē gan do' },
{ lang: 'zh-Hant', label: 'Hanzi', text: '衆生無邊誓願度', tokens: ['衆生', '無邊', '誓願', '度'], transliteration: 'zhòng shēng wú biān shì yuàn dù (Mandarin pinyin)' },
],
witnesses: [
{
Expand All @@ -64,7 +64,7 @@ export const bodhiVows: LiturgyDoc = {
pali: 'Bonnō mujin sē gan dan',
scripts: [
{ lang: 'ja-Jpan', label: 'Sino-Japanese', text: 'Bonnō mujin sē gan dan', tokens: ['Bonnō', 'mujin', 'sē gan', 'dan'] },
{ lang: 'zh-Hant', label: 'Hanzi', text: '煩惱無盡誓願斷', tokens: ['煩惱', '無盡', '誓願', '斷'], transliteration: 'Bonnō mujin sē gan dan' },
{ lang: 'zh-Hant', label: 'Hanzi', text: '煩惱無盡誓願斷', tokens: ['煩惱', '無盡', '誓願', '斷'], transliteration: 'fán nǎo wú jìn shì yuàn duàn (Mandarin pinyin)' },
],
witnesses: [
{
Expand All @@ -86,7 +86,7 @@ export const bodhiVows: LiturgyDoc = {
pali: 'Hōmon muryō sē gan gaku',
scripts: [
{ lang: 'ja-Jpan', label: 'Sino-Japanese', text: 'Hōmon muryō sē gan gaku', tokens: ['Hōmon', 'muryō', 'sē gan', 'gaku'] },
{ lang: 'zh-Hant', label: 'Hanzi', text: '法門無量誓願學', tokens: ['法門', '無量', '誓願', '學'], transliteration: 'Hōmon muryō sē gan gaku' },
{ lang: 'zh-Hant', label: 'Hanzi', text: '法門無量誓願學', tokens: ['法門', '無量', '誓願', '學'], transliteration: 'fǎ mén wú liàng shì yuàn xué (Mandarin pinyin)' },
],
witnesses: [
{
Expand All @@ -108,7 +108,7 @@ export const bodhiVows: LiturgyDoc = {
pali: 'Butsudō mujō sē gan jō',
scripts: [
{ lang: 'ja-Jpan', label: 'Sino-Japanese', text: 'Butsudō mujō sē gan jō', tokens: ['Butsudō', 'mujō', 'sē gan', 'jō'] },
{ lang: 'zh-Hant', label: 'Hanzi', text: '佛道無上誓願成', tokens: ['佛道', '無上', '誓願', '成'], transliteration: 'Butsudō mujō sē gan jō' },
{ lang: 'zh-Hant', label: 'Hanzi', text: '佛道無上誓願成', tokens: ['佛道', '無上', '誓願', '成'], transliteration: 'fó dào wú shàng shì yuàn chéng (Mandarin pinyin)' },
],
witnesses: [
{
Expand Down
Loading
Loading