const COMIC_READER_LAYOUT_CONTRACT = Object.freeze({ contractId: 'story-first-three-frame-v1', columns: Object.freeze({ art: 3, content: 2, }), frames: Object.freeze({ front: Object.freeze({ imageMode: 'aspectFit', initialControls: 'none', revealGesture: 'single-tap', revealedControls: 'topbar-only', }), back: Object.freeze({ art: 2, content: 3, imageMode: 'aspectFit', }), interaction: Object.freeze({ art: 2, content: 3, imageMode: 'aspectFit', choiceCount: 2, choiceDirection: 'vertical', }), }), minimumControlPx: 48, compactHeightMaxPx: 620, fontModes: Object.freeze({ standard: 'large', xlarge: 'xlarge', }), audioSlot: Object.freeze({ placement: 'unified-topbar', minimumHeightPx: 48, textModeLabel: '直接看文字', readyLabel: '听一听', playingLabel: '暂停', replayLabel: '重听', usesPageAudioCueIds: true, }), artOverlayPolicy: Object.freeze({ event: 'transparent-actor-hotspot-only', emotion: 'transparent-actor-hotspot-only', }), }) const COMIC_PAGE_TEMPLATE_BY_TYPE = Object.freeze({ cover: 'chapter-ensemble', ensemble: 'chapter-ensemble', event: 'actor-event', emotion: 'emotion-interaction', memory: 'memory-cliffhanger', }) const COMIC_PRESENTATION_BY_CHAPTER = Object.freeze({ 'S01-C01': 'front-back-comic-v1', 'S01-C02': 'front-back-comic-v1', 'S01-C03': 'front-back-comic-v1', 'S01-C04': 'front-back-comic-v1', 'S01-C05': 'front-back-comic-v1', 'S01-C06': 'front-back-comic-v1', 'S01-C07': 'front-back-comic-v1', 'S01-C08': 'front-back-comic-v1', 'S01-C09': 'front-back-comic-v1', 'S01-C10': 'front-back-comic-v1', 'S01-C11': 'front-back-comic-v1', 'S01-C12': 'front-back-comic-v1', 'S01-C13': 'front-back-comic-v1', 'S01-C14': 'front-back-comic-v1', 'S01-C15': 'front-back-comic-v1', }) function getComicPresentationMode(chapterId) { return COMIC_PRESENTATION_BY_CHAPTER[String(chapterId || '')] || 'legacy-60-40-v1' } const COMIC_PAGE_LAYOUT_EXCEPTIONS = Object.freeze({ 'S01-C09-P03': Object.freeze({ layoutVariant: 'standard-landscape-60-40', behavior: 'key-safe-actor-hotspot', actorHotspot: Object.freeze({ x: 16, y: 3, w: 56, h: 82 }), reason: '人物仍可整片点击,但透明热区须止于画面下方钥匙带之前。', }), }) // These pages keep the full reviewed frame and add one read-only crop from // the same asset as a second comic panel. This is an art-reading aid, not a // new interaction or a page-layout exception. const COMIC_FIXED_DETAIL_INSET_PAGE_IDS = Object.freeze([ 'S01-C04-P04', 'S01-C09-P01', 'S01-C09-P05', 'S01-C09-P06', 'S01-C10-P07', 'S01-C13-P04', 'S01-C15-P05', 'S01-C15-P08', ]) function allowsFixedDetailInset(pageId) { return COMIC_FIXED_DETAIL_INSET_PAGE_IDS.indexOf( String(pageId || ''), ) >= 0 } function getComicPageLayoutException(pageId) { return COMIC_PAGE_LAYOUT_EXCEPTIONS[String(pageId || '')] || null } function resolveComicPageLayout(pageId, type) { const templateKey = COMIC_PAGE_TEMPLATE_BY_TYPE[type] || 'chapter-ensemble' const exception = getComicPageLayoutException(pageId) return { templateKey, layoutVariant: exception ? exception.layoutVariant : 'standard-landscape-60-40', layoutExceptionId: exception ? String(pageId) : '', layoutExceptionBehavior: exception ? exception.behavior : '', } } module.exports = { COMIC_READER_LAYOUT_CONTRACT, COMIC_PRESENTATION_BY_CHAPTER, COMIC_PAGE_TEMPLATE_BY_TYPE, COMIC_PAGE_LAYOUT_EXCEPTIONS, COMIC_FIXED_DETAIL_INSET_PAGE_IDS, allowsFixedDetailInset, getComicPresentationMode, getComicPageLayoutException, resolveComicPageLayout, }