From 6c2078f1a12cdd4e767ff7f4b0a568a0c4f77861 Mon Sep 17 00:00:00 2001 From: project7 Date: Sun, 17 May 2026 08:46:53 +0900 Subject: [PATCH 1/2] Add cartoon reader rendering with webtoon layout (#1214) StoryContent now accepts a contentType prop. When "cartoon", renders markdown images as a vertical webtoon layout: full-width responsive images with minimal gaps, centered in a max-w-2xl container. Scene labels render as centered bold text above panels. Fiction/undefined content type uses the existing prose styling path. Both story detail and individual plot pages pass content_type through. Co-Authored-By: Claude Opus 4.6 --- .../story/[storylineId]/[plotIndex]/page.tsx | 2 +- src/app/story/[storylineId]/page.tsx | 5 +-- src/components/StoryContent.tsx | 33 ++++++++++++++++++- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/app/story/[storylineId]/[plotIndex]/page.tsx b/src/app/story/[storylineId]/[plotIndex]/page.tsx index 3a48291e..3bf88751 100644 --- a/src/app/story/[storylineId]/[plotIndex]/page.tsx +++ b/src/app/story/[storylineId]/[plotIndex]/page.tsx @@ -153,7 +153,7 @@ export default async function PlotDetailPage({ params }: { params: Params }) { {/* Plot content */} {p.content ? ( - + ) : (

Content unavailable (CID: {p.content_cid}) diff --git a/src/app/story/[storylineId]/page.tsx b/src/app/story/[storylineId]/page.tsx index f223445b..8b0a6467 100644 --- a/src/app/story/[storylineId]/page.tsx +++ b/src/app/story/[storylineId]/page.tsx @@ -202,6 +202,7 @@ export default async function StoryPage({ params }: { params: Params }) { <> @@ -466,7 +467,7 @@ function GenesisSection({ plot, readingMode }: { plot: Plot; readingMode?: React {readingMode && {readingMode}} {plot.content ? ( - + ) : (

Content unavailable (CID: {plot.content_cid}) diff --git a/src/components/StoryContent.tsx b/src/components/StoryContent.tsx index 2b2e7dea..297a98f1 100644 --- a/src/components/StoryContent.tsx +++ b/src/components/StoryContent.tsx @@ -35,7 +35,38 @@ const sanitizeSchema = { }, }; -export function StoryContent({ content }: { content: string }) { +export function StoryContent({ content, contentType }: { content: string; contentType?: string }) { + if (contentType === "cartoon") { + return ( +

+ ( + // eslint-disable-next-line @next/next/no-img-element + {alt + ), + p: ({ children }) => ( +
{children}
+ ), + hr: () =>
, + strong: ({ children }) => ( +

{children}

+ ), + }} + > + {content} + +
+ ); + } + return (
Date: Sun, 17 May 2026 08:50:37 +0900 Subject: [PATCH 2/2] Thread contentType through ReadingMode overlay for cartoon rendering Passes content_type from storyline through ReadingModeWrapper and ReadingMode to StoryContent, so cartoon stories use the webtoon layout in the reading overlay as well as the main view. Co-Authored-By: Claude Opus 4.6 --- src/app/story/[storylineId]/[plotIndex]/page.tsx | 1 + src/app/story/[storylineId]/page.tsx | 1 + src/components/ReadingMode.tsx | 6 ++++-- src/components/ReadingModeWrapper.tsx | 3 +++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/app/story/[storylineId]/[plotIndex]/page.tsx b/src/app/story/[storylineId]/[plotIndex]/page.tsx index 3bf88751..127d0f5e 100644 --- a/src/app/story/[storylineId]/[plotIndex]/page.tsx +++ b/src/app/story/[storylineId]/[plotIndex]/page.tsx @@ -130,6 +130,7 @@ export default async function PlotDetailPage({ params }: { params: Params }) { storylineTitle={sl.title} chapters={deduplicateByPlotIndex(allPlots)} initialPlotIndex={pidx} + contentType={sl.content_type} />
diff --git a/src/app/story/[storylineId]/page.tsx b/src/app/story/[storylineId]/page.tsx index 8b0a6467..633c96c7 100644 --- a/src/app/story/[storylineId]/page.tsx +++ b/src/app/story/[storylineId]/page.tsx @@ -209,6 +209,7 @@ export default async function StoryPage({ params }: { params: Params }) { storylineTitle={sl.title} chapters={deduplicateByPlotIndex(plots)} initialPlotIndex={0} + contentType={sl.content_type} /> } /> diff --git a/src/components/ReadingMode.tsx b/src/components/ReadingMode.tsx index be7cfcff..e4b2e5ea 100644 --- a/src/components/ReadingMode.tsx +++ b/src/components/ReadingMode.tsx @@ -15,6 +15,7 @@ interface ReadingModeProps { storylineTitle: string; chapters: Chapter[]; initialChapterIndex: number; + contentType?: string; onClose: () => void; } @@ -54,6 +55,7 @@ export function ReadingMode({ storylineTitle, chapters, initialChapterIndex, + contentType, onClose, }: ReadingModeProps) { const [currentIdx, setCurrentIdx] = useState(initialChapterIndex); @@ -259,7 +261,7 @@ export function ReadingMode({
{chapter?.content ? ( - + ) : (

Content unavailable

)} @@ -281,7 +283,7 @@ export function ReadingMode({
{outgoingChapter.content ? ( - + ) : (

Content unavailable

)} diff --git a/src/components/ReadingModeWrapper.tsx b/src/components/ReadingModeWrapper.tsx index f115d479..e604015a 100644 --- a/src/components/ReadingModeWrapper.tsx +++ b/src/components/ReadingModeWrapper.tsx @@ -18,11 +18,13 @@ export function ReadingModeWrapper({ storylineTitle, chapters, initialPlotIndex, + contentType, }: { storylineId: number; storylineTitle: string; chapters: Chapter[]; initialPlotIndex: number; + contentType?: string; }) { const [active, setActive] = useState(false); @@ -39,6 +41,7 @@ export function ReadingModeWrapper({ storylineTitle={storylineTitle} chapters={chapters} initialChapterIndex={initialIdx >= 0 ? initialIdx : 0} + contentType={contentType} onClose={() => setActive(false)} /> )}