Spaces:
Running
Running
Commit ·
2f3b92c
1
Parent(s): 86e003d
fix: Thai/Pali typography — center-aligned, proper paragraph structure
Browse files
webapp/tipitaka-web/src/components/layout/ReaderPanel.tsx
CHANGED
|
@@ -18,13 +18,30 @@ const SHELL_STYLES: Record<string, string> = {
|
|
| 18 |
};
|
| 19 |
|
| 20 |
// ── Render main text content ───────────────────────────────────────
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
const ReaderPanel: React.FC = () => {
|
| 30 |
const {
|
|
|
|
| 18 |
};
|
| 19 |
|
| 20 |
// ── Render main text content ───────────────────────────────────────
|
| 21 |
+
// CleanText returns paragraphs separated by \n\n. Lines within a paragraph
|
| 22 |
+
// use \n (single). We split properly to preserve paragraph rhythm.
|
| 23 |
+
const renderContent = (text: string, fontSize: number) => {
|
| 24 |
+
// Split into paragraphs by double newline
|
| 25 |
+
const paragraphs = text.split(/\n\n+/).filter(Boolean);
|
| 26 |
+
return (
|
| 27 |
+
<div
|
| 28 |
+
style={{ fontSize: `${fontSize}px`, lineHeight: '2.1' }}
|
| 29 |
+
className="reader-content text-center space-y-6"
|
| 30 |
+
>
|
| 31 |
+
{paragraphs.map((para, idx) => {
|
| 32 |
+
// Within a paragraph, preserve single line-breaks as <br/>
|
| 33 |
+
const html = para.replace(/\n/g, '<br/>');
|
| 34 |
+
return (
|
| 35 |
+
<p
|
| 36 |
+
key={idx}
|
| 37 |
+
className="leading-[2.1]"
|
| 38 |
+
dangerouslySetInnerHTML={{ __html: html }}
|
| 39 |
+
/>
|
| 40 |
+
);
|
| 41 |
+
})}
|
| 42 |
+
</div>
|
| 43 |
+
);
|
| 44 |
+
};
|
| 45 |
|
| 46 |
const ReaderPanel: React.FC = () => {
|
| 47 |
const {
|
webapp/tipitaka-web/src/index.css
CHANGED
|
@@ -6,7 +6,7 @@
|
|
| 6 |
--color-parchment: #F9F4E8;
|
| 7 |
--color-pali-text: #2C1810;
|
| 8 |
|
| 9 |
-
--font-sarabun: "Sarabun", sans-serif;
|
| 10 |
}
|
| 11 |
|
| 12 |
:root {
|
|
@@ -34,6 +34,30 @@ body {
|
|
| 34 |
width: 100%;
|
| 35 |
}
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
/* Custom Utilities */
|
| 38 |
.text-pali {
|
| 39 |
font-family: var(--font-sarabun);
|
|
|
|
| 6 |
--color-parchment: #F9F4E8;
|
| 7 |
--color-pali-text: #2C1810;
|
| 8 |
|
| 9 |
+
--font-sarabun: "Sarabun", "Noto Serif Thai", "Noto Sans Thai Looped", sans-serif;
|
| 10 |
}
|
| 11 |
|
| 12 |
:root {
|
|
|
|
| 34 |
width: 100%;
|
| 35 |
}
|
| 36 |
|
| 37 |
+
/* ── Thai/Pali Typography ──────────────────────────────────────── */
|
| 38 |
+
|
| 39 |
+
/* Font-kerning and Thai OpenType features for the body reading text */
|
| 40 |
+
.reader-content p {
|
| 41 |
+
/* Justification: inter-character adjusts spacing between Thai characters,
|
| 42 |
+
producing cleaner rag than inter-word (which needs spaces Thai doesn't have) */
|
| 43 |
+
text-justify: inter-character;
|
| 44 |
+
/* Prevent inappropriate line breaks in Thai (no spaces between words) */
|
| 45 |
+
word-break: keep-all;
|
| 46 |
+
overflow-wrap: break-word;
|
| 47 |
+
/* Enable font kerning */
|
| 48 |
+
font-kerning: normal;
|
| 49 |
+
/* OpenType features for Thai script rendering */
|
| 50 |
+
font-feature-settings: "kern" 1, "liga" 1, "clig" 1;
|
| 51 |
+
/* Balanced orphans/widows for paragraphs */
|
| 52 |
+
orphans: 2;
|
| 53 |
+
widows: 2;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
/* Remove gap when <br/> is at the start of a <p> (edge case) */
|
| 57 |
+
.reader-content p:first-child {
|
| 58 |
+
margin-top: 0;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
/* Custom Utilities */
|
| 62 |
.text-pali {
|
| 63 |
font-family: var(--font-sarabun);
|