@panelwave/player 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as rxjs from 'rxjs';
2
2
  import { Observable } from 'rxjs';
3
3
  import * as i0 from '@angular/core';
4
- import { OnDestroy, EventEmitter, OnInit, OnChanges, SimpleChanges, AfterViewInit, ElementRef } from '@angular/core';
4
+ import { OnDestroy, EventEmitter, OnInit, OnChanges, AfterViewChecked, SimpleChanges, AfterViewInit, ElementRef } from '@angular/core';
5
5
  import { SafeHtml } from '@angular/platform-browser';
6
6
  import * as _panelwave_player from '@panelwave/player';
7
7
 
@@ -36,6 +36,11 @@ interface Panel {
36
36
  audio?: PanelAudioTrack[];
37
37
  /** Array of panel variants based on conditions (optional) */
38
38
  variants?: PanelVariant[];
39
+ /**
40
+ * Animation of the panel while it is shown (schema `PanelAnimations`):
41
+ * layer keyframes (schema 1.6+) and/or a camera move.
42
+ */
43
+ animations?: PanelAnimations;
39
44
  /** Whether this panel can be shared socially (optional) */
40
45
  shareable?: boolean;
41
46
  /** Override for age rating (optional) */
@@ -48,6 +53,14 @@ interface Panel {
48
53
  memoryBudgetHint?: number;
49
54
  /** Plugin instances for this panel (optional) */
50
55
  plugins?: PluginInstance$1[];
56
+ /**
57
+ * Server-side paywall stub: public manifests strip the content of paid
58
+ * panels and set this flag. The player renders a lock placeholder instead
59
+ * of layers, bubbles and hotspots (see `isLockedPanel`).
60
+ */
61
+ 'x-locked'?: boolean;
62
+ /** Custom extension properties starting with x- (format 1.7+) */
63
+ [key: `x-${string}`]: unknown;
51
64
  }
52
65
  /**
53
66
  * Manifest audio role (schema `AudioTrack.role`). `ui` plays on the SFX bus,
@@ -71,6 +84,60 @@ interface PanelAudioTrack {
71
84
  /** JSON Logic condition; the track only plays when it evaluates true (optional) */
72
85
  visibleIf?: JsonLogic;
73
86
  }
87
+ /**
88
+ * Layer property a keyframe animates (schema 1.6+).
89
+ *
90
+ * - `opacity`: 0-1, replaces the layer's opacity
91
+ * - `transform.x` / `transform.y`: offset from the layer's resting position as a
92
+ * fraction of the panel's width / height (0.1 = 10 % right / down)
93
+ * - `transform.scale`: scale factor around the layer's center (1 = unchanged)
94
+ * - `transform.rotation`: degrees clockwise around the layer's center
95
+ * - `blur`: blur radius in px at a panel width of 1024 px (scaled with the rendered panel)
96
+ * - `brightness` / `contrast` / `saturate`: multiplier (1 = unchanged)
97
+ */
98
+ type AnimatableProperty = 'opacity' | 'transform.x' | 'transform.y' | 'transform.scale' | 'transform.rotation' | 'blur' | 'brightness' | 'contrast' | 'saturate';
99
+ /** Easing names the schema allows on animations and keyframes. */
100
+ type AnimationEasing = 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
101
+ /**
102
+ * One keyframe of a layer animation (schema `AnimationKeyframe`, 1.6+).
103
+ */
104
+ interface AnimationKeyframe {
105
+ /** Keyframe identifier (optional) */
106
+ id?: string;
107
+ /** Id of the animated layer (`Panel.layers[].id`) */
108
+ layerId: string;
109
+ /** Animated property */
110
+ property: AnimatableProperty;
111
+ /** Position on the animation's timeline in milliseconds */
112
+ timeMs: number;
113
+ /** Property value at this keyframe (units: see {@link AnimatableProperty}) */
114
+ value: number;
115
+ /** Easing from this keyframe to the next one of the same track (default `linear`) */
116
+ easing?: AnimationEasing;
117
+ }
118
+ /**
119
+ * Panel animation (schema `PanelAnimations`).
120
+ */
121
+ interface PanelAnimations {
122
+ /** Human-friendly name (authoring aid) */
123
+ name?: string;
124
+ /** Camera move start (normalized rect of the panel) */
125
+ startViewportRect?: NormalizedRect;
126
+ /** Camera move end (normalized rect of the panel) */
127
+ endViewportRect?: NormalizedRect;
128
+ /** Total running time; when omitted, keyframes run until their last keyframe */
129
+ durationMs?: number;
130
+ /** Easing of the camera move */
131
+ easing?: AnimationEasing;
132
+ /** Restart from 0 when durationMs is reached (default false) */
133
+ loop?: boolean;
134
+ /**
135
+ * Layer keyframes. Keyframes with the same layerId + property form a track;
136
+ * values hold before the first / after the last keyframe and are interpolated
137
+ * in between with the earlier keyframe's easing.
138
+ */
139
+ keyframes?: AnimationKeyframe[];
140
+ }
74
141
  /**
75
142
  * Format-specific view configuration
76
143
  */
@@ -348,6 +415,8 @@ interface BalloonConfig {
348
415
  strokeColor: string;
349
416
  /** Hex color */
350
417
  fillColor: string;
418
+ /** Hex color of the lettering (default #000000; schema 1.7.0) */
419
+ textColor?: string;
351
420
  tail: TailConfig;
352
421
  hideBorder: HideBorderConfig;
353
422
  }
@@ -370,6 +439,12 @@ interface Hotspot {
370
439
  label: LocalizedString;
371
440
  /** Localized accessibility label (optional, overrides label for screen readers) */
372
441
  ariaLabel?: LocalizedString;
442
+ /**
443
+ * How readers see it (schema 1.7.0): `area` = invisible click area, `button` = the
444
+ * label shown as a button, `auto` (default) = button when the panel's goTo hotspots
445
+ * lead to two or more panels (a choice), else area.
446
+ */
447
+ display?: 'auto' | 'button' | 'area';
373
448
  /** Action to perform when activated */
374
449
  action: HotspotAction;
375
450
  /** Condition for hotspot visibility (optional) */
@@ -422,7 +497,7 @@ type HotspotAction = HotspotGoToAction | HotspotSetVariablesAction | HotspotOpen
422
497
  */
423
498
  interface HotspotGoToAction {
424
499
  type: 'goTo';
425
- /** Target panel ID */
500
+ /** Target panel ID: any panel of the work, including one in another chapter */
426
501
  to: string;
427
502
  /** Variable mutations applied before navigating (optional) */
428
503
  mutations?: Mutation[];
@@ -686,9 +761,9 @@ interface Graph {
686
761
  * Edge connecting two panels with optional conditions
687
762
  */
688
763
  interface Edge {
689
- /** Source panel ID */
764
+ /** Source panel ID (a panel of the chapter that holds this graph) */
690
765
  from: string;
691
- /** Target panel ID */
766
+ /** Target panel ID: any panel of the work; one in another chapter switches the reader to that chapter */
692
767
  to: string;
693
768
  /** Condition that must be true for this edge to be traversable (optional) */
694
769
  condition?: JsonLogic;
@@ -706,6 +781,11 @@ interface Edge {
706
781
  priority?: number;
707
782
  /** Localized label for this edge (optional, for UI) */
708
783
  label?: Record<string, string>;
784
+ /**
785
+ * Editor edge metadata (schema `Edge.mutations`): the CMS keeps the choice
786
+ * label in an `{ op: 'meta', edgeType: { label } }` descriptor here.
787
+ */
788
+ mutations?: Record<string, unknown>[];
709
789
  }
710
790
  /**
711
791
  * Optional metadata for graph nodes
@@ -1396,6 +1476,29 @@ interface Page {
1396
1476
  /** Transition effect when leaving this page */
1397
1477
  out?: Transition;
1398
1478
  };
1479
+ /** Visual properties of the page (optional) */
1480
+ visual?: PageVisual;
1481
+ }
1482
+ /**
1483
+ * Visual properties of a page (schema Page.visual)
1484
+ */
1485
+ interface PageVisual {
1486
+ /**
1487
+ * Page background color (CSS hex) — the space between and around the
1488
+ * panels. Falls back to settings.typography.default_page_bg_color.
1489
+ */
1490
+ background_color?: string;
1491
+ /** Background image asset ID or URI */
1492
+ background_image?: string;
1493
+ /** Background texture asset ID or URI */
1494
+ background_texture?: string;
1495
+ /** Page margins in pixels */
1496
+ margins?: {
1497
+ top?: number;
1498
+ right?: number;
1499
+ bottom?: number;
1500
+ left?: number;
1501
+ };
1399
1502
  }
1400
1503
  /**
1401
1504
  * Page layout configuration
@@ -1554,8 +1657,8 @@ interface OutputPreset {
1554
1657
  interface Extras {
1555
1658
  /** Cover image(s) (optional) */
1556
1659
  cover?: ExtraItem;
1557
- /** Alternative covers (optional) */
1558
- alt_cover?: ExtraItem;
1660
+ /** Alternative cover(s): one block, or several (array form, schema 1.6+) */
1661
+ alt_cover?: ExtraItem | ExtraItem[];
1559
1662
  /** Character sheets (optional) */
1560
1663
  character_sheets?: ExtraItem[];
1561
1664
  /** Author information (optional) */
@@ -1587,6 +1690,12 @@ interface ExtraItem {
1587
1690
  shareable?: boolean;
1588
1691
  /** Whether this extra is behind a paywall (optional) */
1589
1692
  gated?: boolean;
1693
+ /** Character sheets: the character shown (first of characterIds for ensemble sheets) */
1694
+ characterId?: string;
1695
+ /** Character sheets: all characters shown on an ensemble sheet (schema 1.6+) */
1696
+ characterIds?: string[];
1697
+ /** Custom extension properties starting with x- (format 1.7+) */
1698
+ [key: `x-${string}`]: unknown;
1590
1699
  }
1591
1700
  /**
1592
1701
  * Image in an extra item
@@ -2010,6 +2119,26 @@ interface PanelChangeEvent {
2010
2119
  /** Transition used */
2011
2120
  transition?: string;
2012
2121
  }
2122
+ /**
2123
+ * Payload of `<pw-player-shell>`'s `(panelChange)` output.
2124
+ *
2125
+ * Panels are keyed by id in `chapter.panels`; the optional `Panel.id` of the
2126
+ * format is not required and is usually absent, so read the id from
2127
+ * `panelId`, not from `panel`.
2128
+ */
2129
+ interface PlayerPanelChangeEvent {
2130
+ /** The panel that is now current (the manifest object, variants unresolved). */
2131
+ panel: Panel;
2132
+ /** The chapter the panel belongs to. */
2133
+ chapter: Chapter;
2134
+ /** Id of the current panel: its key in `chapter.panels`. */
2135
+ panelId: string;
2136
+ /**
2137
+ * Id of the panel that was current before this change (it may belong to
2138
+ * the previous chapter). Undefined for the first panel after a (re)load.
2139
+ */
2140
+ previousPanelId?: string;
2141
+ }
2013
2142
  /**
2014
2143
  * Decision event payload
2015
2144
  */
@@ -2384,6 +2513,8 @@ declare class PlayerStateService {
2384
2513
  declare class ManifestService {
2385
2514
  private manifest;
2386
2515
  private indexes;
2516
+ /** Absolute URL the current manifest was fetched from (null for object manifests). */
2517
+ private manifestUrl;
2387
2518
  private readonly http;
2388
2519
  /**
2389
2520
  * Load manifest from a URL
@@ -2405,6 +2536,12 @@ declare class ManifestService {
2405
2536
  * Get the current manifest
2406
2537
  */
2407
2538
  getManifest(): PanelWaveManifest | null;
2539
+ /**
2540
+ * The URL the current manifest was loaded from, absolute — the last
2541
+ * fallback for relative asset references (AssetUrlService); null when the
2542
+ * manifest was supplied as an object.
2543
+ */
2544
+ getManifestUrl(): string | null;
2408
2545
  /**
2409
2546
  * Check if a manifest is loaded
2410
2547
  */
@@ -2509,6 +2646,20 @@ declare class ManifestService {
2509
2646
  static ɵprov: i0.ɵɵInjectableDeclaration<ManifestService>;
2510
2647
  }
2511
2648
 
2649
+ /**
2650
+ * Turns manifest asset references (`src`, poster, portrait, thumbnail…) into
2651
+ * loadable URLs: absolute ones pass through, relative ones resolve against
2652
+ * the loaded manifest's `assets.base` (per category, then `mediaBase`) and,
2653
+ * failing that, the URL the manifest itself came from. One place for the
2654
+ * rule every layer/overlay used to re-implement with `baseUrl + src`.
2655
+ */
2656
+ declare class AssetUrlService {
2657
+ private readonly manifestService;
2658
+ resolve(src: string | undefined | null, category?: AssetCategory): string;
2659
+ static ɵfac: i0.ɵɵFactoryDeclaration<AssetUrlService, never>;
2660
+ static ɵprov: i0.ɵɵInjectableDeclaration<AssetUrlService>;
2661
+ }
2662
+
2512
2663
  /**
2513
2664
  * Variable Store Service
2514
2665
  * Provides scoped variable storage with mutations and persistence
@@ -2769,6 +2920,19 @@ declare class FlowEngineService {
2769
2920
  * @returns Navigation result with next panel and transition
2770
2921
  */
2771
2922
  getNextPanel(graph: Graph, currentPanelId: string, context: Record<string, unknown>, defaultTransition?: Transition, defaultCameraMove?: CameraMove): NavigationResult;
2923
+ /**
2924
+ * Next panel within a chapter. A chapter WITH edges uses the graph
2925
+ * ({@link getNextPanel}, conditions and priorities included). A chapter
2926
+ * without any edges follows its reading order (entry, then
2927
+ * `chapter.panels` key order) with the format defaults; null after the
2928
+ * last panel.
2929
+ */
2930
+ getNextInChapter(chapter: Chapter, currentPanelId: string, context: Record<string, unknown>, defaultTransition?: Transition, defaultCameraMove?: CameraMove): NavigationResult;
2931
+ /**
2932
+ * Previous panel(s) within a chapter: the sources of edges into the panel,
2933
+ * or — in a chapter without edges — the panel before it in reading order.
2934
+ */
2935
+ getPreviousInChapter(chapter: Chapter, currentPanelId: string): string[];
2772
2936
  /**
2773
2937
  * Camera move to play when navigating BACK from `currentPanelId` to
2774
2938
  * `previousPanelId`: the move of the edge originally traversed
@@ -2973,6 +3137,10 @@ declare class ImageCacheService {
2973
3137
  * Support for createImageBitmap
2974
3138
  */
2975
3139
  private readonly supportsImageBitmap;
3140
+ /** Images warmed for `<img>` rendering (see preload), least recent first. */
3141
+ private warmed;
3142
+ /** How many warmed images stay referenced (a few panels/pages ahead). */
3143
+ private readonly WARM_LIMIT;
2976
3144
  /**
2977
3145
  * Load an image from cache or network
2978
3146
  */
@@ -2998,9 +3166,16 @@ declare class ImageCacheService {
2998
3166
  */
2999
3167
  private evictLRU;
3000
3168
  /**
3001
- * Preload an image
3169
+ * Preload an image for an upcoming `<img>`: loads it the way an `<img>`
3170
+ * does (same URL, no CORS request) and decodes it, then keeps the element
3171
+ * alive so the browser's memory cache serves the next `<img src>` with the
3172
+ * decoded pixels — the panel appears in the frame it mounts, without the
3173
+ * blank gap a cold load (or a fetch()/ImageBitmap copy the `<img>` never
3174
+ * uses) leaves between autoplay panels.
3002
3175
  */
3003
3176
  preload(url: string): Promise<void>;
3177
+ /** Load and decode an image through an `Image` element. */
3178
+ private warmImage;
3004
3179
  /**
3005
3180
  * Preload multiple images
3006
3181
  */
@@ -3696,6 +3871,12 @@ declare class VisibilityService implements OnDestroy {
3696
3871
  * Auto-advance coupling (§4.3.6): when the last queue slot completes its pass
3697
3872
  * the sequencer emits {@link queueComplete}; the host advances the page/panel
3698
3873
  * only when autoplay is on.
3874
+ *
3875
+ * Image holds: with a page timeline (see {@link start}) the panels without a
3876
+ * sequenced video hold the sequence for their dwell time, in reading order —
3877
+ * video A, image B (2 s), image C (4 s), video D plays A, waits 6 s, plays D,
3878
+ * so the reader has time for the art between the videos. Images before the
3879
+ * first video delay it; images after the last one delay `queueComplete`.
3699
3880
  */
3700
3881
 
3701
3882
  /**
@@ -3744,6 +3925,15 @@ interface SequencedVideo extends SequencedVideoInfo {
3744
3925
  /** Optional hook invoked right before a stalled slot is skipped. */
3745
3926
  onStallSkip?(): void;
3746
3927
  }
3928
+ /**
3929
+ * One panel of the page in reading order, with the time a panel without a
3930
+ * sequenced video holds the sequence (its autoplay dwell time).
3931
+ */
3932
+ interface PageTimelineEntry {
3933
+ panelId: string;
3934
+ /** Dwell time in ms; ignored for panels whose videos play in the sequence. */
3935
+ holdMs: number;
3936
+ }
3747
3937
  /** Default stall-skip timeout: 10 s (§7). */
3748
3938
  declare const DEFAULT_STALL_TIMEOUT_MS = 10000;
3749
3939
  declare class VideoSequencerService implements OnDestroy {
@@ -3765,6 +3955,12 @@ declare class VideoSequencerService implements OnDestroy {
3765
3955
  private running;
3766
3956
  /** Stall-skip timer for the active slot. */
3767
3957
  private stallTimer?;
3958
+ /** The page in reading order, with each panel's hold time. */
3959
+ private timeline;
3960
+ /** Timeline index of the last slot that played (-1: none yet). */
3961
+ private timelinePosition;
3962
+ /** Pending image hold before the next slot (or before `queueComplete`). */
3963
+ private holdTimer?;
3768
3964
  /**
3769
3965
  * Stall-skip timeout (ms). If the active slot makes no progress for this
3770
3966
  * long the sequencer skips it (§7). Injectable via {@link setStallTimeout}
@@ -3789,8 +3985,12 @@ declare class VideoSequencerService implements OnDestroy {
3789
3985
  * visibility changes so newly-visible videos append and leaving ones drop.
3790
3986
  * Already-registered sequenced videos (components reused across a page
3791
3987
  * change) are re-observed and re-queued.
3988
+ *
3989
+ * @param timeline The page's panels in reading order with their hold times.
3990
+ * Panels without a sequenced video hold the sequence for that long
3991
+ * between the videos. Omitted: the videos play back to back.
3792
3992
  */
3793
- start(): void;
3993
+ start(timeline?: readonly PageTimelineEntry[]): void;
3794
3994
  /** Stop the sequence and release all observers/timers. */
3795
3995
  reset(): void;
3796
3996
  ngOnDestroy(): void;
@@ -3842,9 +4042,18 @@ declare class VideoSequencerService implements OnDestroy {
3842
4042
  private completeActiveSlot;
3843
4043
  /**
3844
4044
  * Advance to the next queue slot, or emit `queueComplete` when the queue is
3845
- * exhausted.
4045
+ * exhausted — each after holding for the image panels in between.
3846
4046
  */
3847
4047
  private advance;
4048
+ /**
4049
+ * Total hold of the image panels between the last played slot and the
4050
+ * timeline index `target` (exclusive). Panels whose videos are sequenced
4051
+ * never hold: they play, or were skipped as not visible.
4052
+ */
4053
+ private holdBefore;
4054
+ /** Index of a panel in the page timeline, or -1. */
4055
+ private timelineIndex;
4056
+ private clearHoldTimer;
3848
4057
  /** Activate the head slot of the queue (all its sequenced videos). */
3849
4058
  private playNext;
3850
4059
  private armStallTimer;
@@ -3931,9 +4140,16 @@ declare class PreloadService {
3931
4140
  /** Low-priority items handed to requestIdleCallback but not started yet. */
3932
4141
  private idlePending;
3933
4142
  private readonly imageCache;
4143
+ private readonly paywall;
3934
4144
  constructor();
3935
4145
  /**
3936
- * Add item to preload queue
4146
+ * Add item to preload queue.
4147
+ *
4148
+ * Items of a panel the paywall locks for the reader (the renderers show
4149
+ * its placeholder) are skipped: gated artwork is not fetched ahead of the
4150
+ * gate. A later call after the lock lifts preloads them. Only panel ids
4151
+ * the paywall knows are judged; items without one (or with a legacy index
4152
+ * id from preloadNext) load as before.
3937
4153
  */
3938
4154
  add(item: PreloadItem): void;
3939
4155
  /**
@@ -3952,6 +4168,8 @@ declare class PreloadService {
3952
4168
  * Predict next indices based on direction
3953
4169
  */
3954
4170
  private predict;
4171
+ /** Does the paywall lock this (known) panel for the reader? */
4172
+ private isLockedPanel;
3955
4173
  /**
3956
4174
  * Clear queue
3957
4175
  */
@@ -4201,6 +4419,10 @@ declare class TrackingService {
4201
4419
  * by `refId` (see `isExtraLocked`).
4202
4420
  * - A gated panel unlocks when the gating rule's requirement is satisfied by
4203
4421
  * the reader's entitlement snapshot.
4422
+ * - Age requirements are NOT subject to that precedence: every rule that
4423
+ * applies to a panel (its panel rules, its chapter's rules, every work
4424
+ * rule; each outside its own preview) and carries an age must be met first.
4425
+ * A chapter preview or a free panel rule never bypasses a work age gate.
4204
4426
  */
4205
4427
 
4206
4428
  type EvaluatorEntitlementType = 'free' | 'subscription' | 'purchase' | 'age_gate';
@@ -4347,16 +4569,41 @@ declare class PaywallService {
4347
4569
  private chapterOrder;
4348
4570
  private manifest;
4349
4571
  private products;
4572
+ /** Panels the server stripped for this reader (`"x-locked": true`). */
4573
+ private lockedPanels;
4574
+ /** Decisions per panel for the current rules + snapshot (cleared on change). */
4575
+ private decisions;
4576
+ /** False while a host entitlement adapter decides access (see setEnforced). */
4577
+ private enforced;
4350
4578
  private snapshotSubject;
4351
4579
  readonly snapshot$: Observable<EntitlementSnapshot>;
4580
+ private changesSubject;
4581
+ /**
4582
+ * Fires whenever a lock can flip: new manifest, new snapshot, enforcement
4583
+ * change or clear(). Renderers re-check `isPanelLocked` on it.
4584
+ */
4585
+ readonly changes$: Observable<void>;
4352
4586
  /** Load the rules and reading order from a manifest. Safe to call repeatedly. */
4353
4587
  setManifest(manifest: PanelWaveManifest | null): void;
4354
4588
  /** `paywall.products` by id (format 1.6); the first entry wins on duplicate ids. */
4355
4589
  static productsFromManifest(manifest: PanelWaveManifest | null): Map<string, PaywallProduct>;
4356
4590
  /** Replace what the reader owns (after sign-in, or a completed purchase). */
4357
4591
  setSnapshot(snapshot: EntitlementSnapshot | null | undefined): void;
4592
+ /**
4593
+ * Whether the rules are this service's to enforce. The shell turns it off
4594
+ * while a host `entitlementAdapter` decides access (the adapter wins over
4595
+ * manifest rules): the rules then still describe gates, but renderers no
4596
+ * longer lock panels by them (`x-locked` stubs stay locked regardless).
4597
+ */
4598
+ setEnforced(enforced: boolean): void;
4599
+ /**
4600
+ * Should renderers show the locked placeholder for this panel? True when
4601
+ * the rules are enforced here and the reader's snapshot does not open it.
4602
+ * Memoized per rules + snapshot, so it is cheap to call from templates.
4603
+ */
4604
+ isPanelLocked(panelId: string): boolean;
4358
4605
  getSnapshot(): EntitlementSnapshot;
4359
- /** True when the work carries no paywall rules at all. */
4606
+ /** True when the work carries no paywall rules and no locked panels. */
4360
4607
  get isFreeWork(): boolean;
4361
4608
  /** Reading-order index used for preview counting, or -1 when unknown. */
4362
4609
  indexOf(panelId: string): number;
@@ -4366,8 +4613,16 @@ declare class PaywallService {
4366
4613
  * An unknown panel id gets `MAX_SAFE_INTEGER` as its index so it falls
4367
4614
  * OUTSIDE any free preview: a panel the reading order does not know about
4368
4615
  * must not be handed out as a free sample.
4616
+ *
4617
+ * Defence in depth: a panel the server stripped (`"x-locked": true`) is
4618
+ * locked whatever the rules say — its content is not in the manifest, so
4619
+ * neither a free preview nor an entitlement can show it. The applying
4620
+ * rule's own lock reason is kept while it locks; otherwise the reason is
4621
+ * the rule's purchase/subscription requirement, else `purchase_required`
4622
+ * (never the age, which would re-open the age gate forever on a stub).
4369
4623
  */
4370
4624
  evaluate(panelId: string): AccessDecision;
4625
+ private decide;
4371
4626
  /** Is this extras block locked by an extras-scoped rule the reader does not satisfy? */
4372
4627
  isExtraLocked(extraId: string): boolean;
4373
4628
  /** Convenience: may the reader open this panel? */
@@ -4398,6 +4653,8 @@ declare class PaywallService {
4398
4653
  private get defaultLocale();
4399
4654
  /** Reset to a manifest-less, entitlement-less state. */
4400
4655
  clear(): void;
4656
+ /** Drop memoized decisions and tell renderers. */
4657
+ private changed;
4401
4658
  /**
4402
4659
  * Reader-facing wording for a lock reason. Kept here rather than in the
4403
4660
  * overlay so the same sentence is used wherever a gate is explained.
@@ -4998,6 +5255,13 @@ declare class PlayerService {
4998
5255
  static ɵprov: i0.ɵɵInjectableDeclaration<PlayerService>;
4999
5256
  }
5000
5257
 
5258
+ /**
5259
+ * True when a panel is a server-side paywall stub: public manifests strip
5260
+ * the content of paid panels and flag them `"x-locked": true`. Only the
5261
+ * boolean `true` counts (a `"true"` string is not a lock).
5262
+ */
5263
+ declare function isLockedPanel(panel: Panel | null | undefined): boolean;
5264
+
5001
5265
  /**
5002
5266
  * Video Config Utilities
5003
5267
  * Resolves the effective VideoLayer playback config from layer fields,
@@ -5089,6 +5353,8 @@ interface BalloonOptions {
5089
5353
  strokeWidth: number;
5090
5354
  strokeColor: string;
5091
5355
  fillColor: string;
5356
+ /** Lettering colour (CSS colour; BalloonConfig.textColor, schema 1.7.0). */
5357
+ textColor: string;
5092
5358
  tailWidth: number;
5093
5359
  cornerRadius: number;
5094
5360
  fontFamily: string;
@@ -5127,6 +5393,8 @@ interface BalloonRenderResult {
5127
5393
  width: number;
5128
5394
  height: number;
5129
5395
  svg: SVGSVGElement;
5396
+ /** True when the text needs more height than a fixed maxHeight allows (it is cut off). */
5397
+ textOverflows: boolean;
5130
5398
  balloon: ComicBalloon;
5131
5399
  update: (newText?: string, newTailOptions?: TailOptions | null) => BalloonRenderResult;
5132
5400
  updateTail: (newTailOptions: TailOptions | null) => BalloonRenderResult;
@@ -5226,6 +5494,177 @@ declare function resolvePanelVariant(panel: Panel, context: VariantContext, forc
5226
5494
  */
5227
5495
  declare function resolvePanels(panels: Record<string, Panel>, context: VariantContext): Record<string, Panel>;
5228
5496
 
5497
+ /**
5498
+ * Page view format selection. A chapter carries one page sequence per output
5499
+ * format (`Page.layout.format`): mobile, tablet, desktop, big screen, print…
5500
+ * The player shows the sequence that suits the screen best and keeps the
5501
+ * others out of page navigation, the TOC and the page lookup.
5502
+ */
5503
+
5504
+ /** Screen classes the format preference lists are keyed by. */
5505
+ type ScreenClass = 'phone' | 'tablet' | 'desktop' | 'bigscreen';
5506
+ /**
5507
+ * Classify the player's viewport. `width`/`height` are CSS px, `dpr` the
5508
+ * device pixel ratio: a 4K screen at 150 % or 200 % scaling still counts as
5509
+ * a big screen, a laptop at 125 % does not.
5510
+ */
5511
+ declare function screenClassFor(width: number, height: number, dpr?: number): ScreenClass;
5512
+ /** The output format a page is laid out for; null for legacy pages without one. */
5513
+ declare function pageFormatOf(page: Page | null | undefined): string | null;
5514
+ /** Distinct formats of the given pages, in first-seen order. */
5515
+ declare function pageFormatsOf(pages: readonly Page[] | null | undefined): string[];
5516
+ /**
5517
+ * Formats ranked for a screen class: its preference list, then any formats
5518
+ * it does not name (closest aspect ratio to `aspect` first). Only formats in
5519
+ * `available` are returned.
5520
+ */
5521
+ declare function rankPageFormats(available: readonly string[], screen: ScreenClass, aspect?: number): string[];
5522
+ /**
5523
+ * The page format to show on this screen, or null when the pages carry no
5524
+ * format at all. `preferred` (a host override) wins when pages exist for it.
5525
+ */
5526
+ declare function pickPageFormat(available: readonly string[], width: number, height: number, dpr?: number, preferred?: string | null): string | null;
5527
+ /**
5528
+ * The page sequence of one format. Pages without a format (legacy) belong to
5529
+ * every sequence; a null format means "all pages".
5530
+ */
5531
+ declare function pagesForFormat(pages: readonly Page[] | null | undefined, format: string | null): Page[];
5532
+ /** Width / height of a page's frame: its canvas size, else its format's aspect, else 16:9. */
5533
+ declare function pageAspectRatio(page: Page | null | undefined): number;
5534
+
5535
+ /**
5536
+ * Reading position as a URL: page view and panel view get their own
5537
+ * addresses, so a reader can bookmark or share exactly what they see.
5538
+ *
5539
+ * ?page=pg-D3 page view, page `pg-D3`
5540
+ * ?panel=ch1-p022 panel view (and canvas view), panel `ch1-p022`
5541
+ * (neither) the cover / the start of the work
5542
+ *
5543
+ * Ids are the manifest's page and panel ids. A page id belongs to one output
5544
+ * format; the player falls back to the page showing the same panels when the
5545
+ * reader's screen uses another format.
5546
+ */
5547
+ /** What the player shows: the cover, a page, a single panel or the canvas. */
5548
+ type PlayerView = 'cover' | 'page' | 'panel' | 'canvas';
5549
+ /** The reader's position, emitted by the shell's `locationChange` output. */
5550
+ interface PlayerLocation {
5551
+ view: PlayerView;
5552
+ /** Chapter of the current panel (absent on the cover of an empty work). */
5553
+ chapterId?: string;
5554
+ /** Current panel (page view: the panel the reader is on). */
5555
+ panelId?: string;
5556
+ /** Open page (page view only). */
5557
+ pageId?: string;
5558
+ }
5559
+ /** URL query parameters the position uses. */
5560
+ declare const LOCATION_PARAMS: {
5561
+ readonly page: "page";
5562
+ readonly panel: "panel";
5563
+ };
5564
+ /** The position a URL's query string asks for (page wins over panel). */
5565
+ declare function parseLocationSearch(search: string): {
5566
+ pageId?: string;
5567
+ panelId?: string;
5568
+ };
5569
+ /**
5570
+ * The URL for a position: `href` with the position's query parameter set and
5571
+ * every other parameter kept (e.g. `embed`). `drop` removes further
5572
+ * parameters (a share link leaves out `embed`).
5573
+ */
5574
+ declare function locationUrl(href: string, location: PlayerLocation, drop?: readonly string[]): string;
5575
+
5576
+ /**
5577
+ * Page background color. The page color fills the space between and around
5578
+ * the panels in page view and the frame around the panel in panel view, so a
5579
+ * reader moving from a white page to a black one sees the switch at the
5580
+ * first panel of the new page.
5581
+ */
5582
+
5583
+ /** Used when neither the page nor the work sets a color. */
5584
+ declare const DEFAULT_PAGE_BACKGROUND = "#1a1a1a";
5585
+ /**
5586
+ * The page's background color: `page.visual.background_color`, else the
5587
+ * work's `settings.typography.default_page_bg_color`, else
5588
+ * {@link DEFAULT_PAGE_BACKGROUND}. Values that are not CSS hex colors are
5589
+ * skipped.
5590
+ */
5591
+ declare function resolvePageBackground(page: Page | null | undefined, settings: Settings | null | undefined): string;
5592
+
5593
+ /**
5594
+ * Extras Viewer Component
5595
+ * Displays bonus content gallery
5596
+ */
5597
+
5598
+ /**
5599
+ * Extra item type
5600
+ */
5601
+ type ExtraType = 'cover' | 'art' | 'bts' | 'interview' | 'other';
5602
+ /**
5603
+ * Extra item definition
5604
+ */
5605
+ interface Extra {
5606
+ id: string;
5607
+ type: ExtraType;
5608
+ title: LocalizedString;
5609
+ description?: LocalizedString;
5610
+ thumbnail?: string;
5611
+ asset?: string;
5612
+ mediaType?: 'image' | 'video' | 'audio' | 'document';
5613
+ gated?: boolean;
5614
+ tags?: string[];
5615
+ /** Character sheets: names of the characters shown (one, or several on an ensemble sheet). */
5616
+ characters?: LocalizedString[];
5617
+ }
5618
+
5619
+ /**
5620
+ * Map the manifest's keyed `extras` blocks (schema $defs/ExtraBlock) to the
5621
+ * flat list the extras viewer renders — including the media each block
5622
+ * carries, resolved through the asset catalog.
5623
+ *
5624
+ * ExtraBlock media, in schema terms:
5625
+ * - `images[] { assetId, caption }`, `video[] (VideoLayer)`, `audio[] (AudioTrack)`
5626
+ * reference catalog assets by id;
5627
+ * - `url` / `thumbnail` are direct links (thumbnail may also be an asset id);
5628
+ * - `contentType` (image | video | audio | pdf | text) states the kind explicitly.
5629
+ * Previously only id/title/text were passed on, so every extra rendered as an
5630
+ * empty image.
5631
+ */
5632
+
5633
+ /** Just the catalog shape this module reads (image / video / audio items). */
5634
+ interface CatalogLike {
5635
+ category?: string;
5636
+ variants?: {
5637
+ src: string;
5638
+ w?: number;
5639
+ direction?: string;
5640
+ }[];
5641
+ poster?: {
5642
+ src: string;
5643
+ };
5644
+ }
5645
+ type AssetLookup = (assetId: string) => CatalogLike | null | undefined;
5646
+
5647
+ /**
5648
+ * Thumbnails for navigation surfaces (thumbnail strip, TOC) and the work's
5649
+ * cover. Panels carry no thumbnail field of their own: the thumbnail is a
5650
+ * small rendition of the panel's artwork, resolved through the asset catalog.
5651
+ */
5652
+
5653
+ /** Width (physical px) a thumbnail tile is rendered at. */
5654
+ declare const THUMBNAIL_WIDTH = 320;
5655
+ /**
5656
+ * The thumbnail source of a panel: an explicit `thumbnail`, else its
5657
+ * bottom-most visible image layer (the artwork the panel is drawn on), else
5658
+ * the poster of its first video layer. Empty when the panel has no artwork.
5659
+ */
5660
+ declare function panelThumbnailSrc(panel: Panel | null | undefined, lookup: AssetLookup, width?: number): string;
5661
+ /**
5662
+ * The work's cover image: `meta.cover` (asset id or URL), else the
5663
+ * `extras.cover` block (its image, link or thumbnail). A gated cover block
5664
+ * is not shown. Empty when the work has no cover.
5665
+ */
5666
+ declare function coverImageSrc(manifest: PanelWaveManifest | null | undefined, lookup: AssetLookup, width?: number): string;
5667
+
5229
5668
  /**
5230
5669
  * Renders an inlined Lucide icon by name (e.g. `lucideX`).
5231
5670
  *
@@ -5262,6 +5701,19 @@ interface Character {
5262
5701
  role?: string;
5263
5702
  }
5264
5703
 
5704
+ /**
5705
+ * Viewport Component
5706
+ * Main rendering container for panels with pan/zoom support
5707
+ */
5708
+
5709
+ /** A panel the viewport should warm ahead of time (see `preloadTargets`). */
5710
+ interface ViewportPreloadTarget {
5711
+ panelId: string;
5712
+ panel: Panel;
5713
+ /** Share of the panel container (panel view) or page canvas (page view) width it renders at; default 1. */
5714
+ widthFraction?: number;
5715
+ }
5716
+
5265
5717
  /**
5266
5718
  * Camera state: world coordinates of the viewport center plus zoom factor
5267
5719
  * (world-to-screen scale).
@@ -5507,30 +5959,6 @@ interface Preferences {
5507
5959
  highContrast: boolean;
5508
5960
  }
5509
5961
 
5510
- /**
5511
- * Extras Viewer Component
5512
- * Displays bonus content gallery
5513
- */
5514
-
5515
- /**
5516
- * Extra item type
5517
- */
5518
- type ExtraType = 'cover' | 'art' | 'bts' | 'interview' | 'other';
5519
- /**
5520
- * Extra item definition
5521
- */
5522
- interface Extra {
5523
- id: string;
5524
- type: ExtraType;
5525
- title: LocalizedString;
5526
- description?: LocalizedString;
5527
- thumbnail?: string;
5528
- asset?: string;
5529
- mediaType?: 'image' | 'video' | 'audio' | 'document';
5530
- gated?: boolean;
5531
- tags?: string[];
5532
- }
5533
-
5534
5962
  /**
5535
5963
  * Age Gate Component
5536
5964
  * Displays age verification UI for restricted content
@@ -5679,6 +6107,15 @@ declare class BranchChooserComponent {
5679
6107
  /**
5680
6108
  * Entitlement adapter interface
5681
6109
  * Provides entitlement and paywall functionality
6110
+ *
6111
+ * An adapter decides NAVIGATION only. It wins over the manifest's paywall
6112
+ * rules, so with an adapter the rules no longer lock rendering: a panel the
6113
+ * adapter refuses is still rendered. A move to it is stopped at the gate,
6114
+ * but a refused entry panel (initial load, bookmark resume) gets the paywall
6115
+ * raised OVER its rendered content, and page / canvas view show it in full.
6116
+ * Hosts must not rely on an adapter to hide content: strip it on the server
6117
+ * (`"x-locked": true` stubs stay locked regardless) or pass an
6118
+ * `entitlementSnapshot`, which locks rendering as well.
5682
6119
  */
5683
6120
  interface EntitlementAdapter {
5684
6121
  /**
@@ -5698,7 +6135,7 @@ interface EntitlementAdapter {
5698
6135
  * Player Shell Component
5699
6136
  * Main orchestrator for the PanelWave player
5700
6137
  */
5701
- declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6138
+ declare class PlayerShellComponent implements OnInit, OnChanges, AfterViewChecked, OnDestroy {
5702
6139
  /**
5703
6140
  * Manifest to load
5704
6141
  */
@@ -5708,7 +6145,9 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
5708
6145
  */
5709
6146
  manifestUrl?: string;
5710
6147
  /**
5711
- * Entitlement adapter for paywall integration
6148
+ * Entitlement adapter for paywall integration. Gates navigation only: a
6149
+ * refused entry panel gets the paywall over its rendered content (see
6150
+ * {@link EntitlementAdapter}); don't rely on it to hide content.
5712
6151
  */
5713
6152
  entitlementAdapter?: EntitlementAdapter;
5714
6153
  /**
@@ -5787,17 +6226,55 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
5787
6226
  */
5788
6227
  initialChapterId?: string;
5789
6228
  /**
5790
- * Initial panel ID
6229
+ * Initial panel ID. Without `initialChapterId` the chapter holding the
6230
+ * panel is looked up. A panel link opens in panel view.
5791
6231
  */
5792
6232
  initialPanelId?: string;
6233
+ /**
6234
+ * Initial page (manifest page id, e.g. from a `?page=` link): opens that
6235
+ * page in page view. On a screen whose page format differs, the page
6236
+ * showing the same panels opens. Wins over `initialPanelId`.
6237
+ */
6238
+ initialPageId?: string;
6239
+ /**
6240
+ * View to open in. Default: page view when the work has pages (after the
6241
+ * cover), panel view for a panel link.
6242
+ */
6243
+ initialViewMode?: 'page' | 'panel';
6244
+ /**
6245
+ * Link the share dialog offers. Default: the current address with the
6246
+ * reading position (`?page=` / `?panel=`, see `locationUrl`).
6247
+ */
6248
+ shareUrl?: string;
6249
+ /**
6250
+ * The reading position changed: cover, page, panel or canvas — for hosts
6251
+ * that keep it in the address bar (`locationUrl`) so pages and panels
6252
+ * can be bookmarked and shared.
6253
+ */
6254
+ locationChange: EventEmitter<PlayerLocation>;
5793
6255
  /**
5794
6256
  * Enable autoplay
5795
6257
  */
5796
6258
  autoplay: boolean;
5797
6259
  /**
5798
- * Seconds per panel in autoplay mode
6260
+ * Seconds per panel in autoplay mode. With the default author timing this
6261
+ * only applies to panels without an authored `durationMs` (and the cover);
6262
+ * once the reader picks a speed in the toolbar it applies to every panel.
5799
6263
  */
5800
6264
  secondsPerPanel: number;
6265
+ /**
6266
+ * Page view format: 'auto' (default) shows the page sequence of the
6267
+ * authored output format that suits the screen (phone → mobile-portrait,
6268
+ * 4K → bigscreen-landscape, …); a format id forces that sequence when the
6269
+ * work has pages for it.
6270
+ */
6271
+ pageFormat: string;
6272
+ /**
6273
+ * Show the work's cover (`meta.cover` / `extras.cover`) before the first
6274
+ * panel when reading starts at the beginning. It also heads the thumbnail
6275
+ * strip and the table of contents.
6276
+ */
6277
+ showCover: boolean;
5801
6278
  /**
5802
6279
  * Force reduced motion (host override). Reduced motion is also on when the
5803
6280
  * reader enabled it in Settings or the OS asks for it — see `motionReduced`.
@@ -5831,12 +6308,10 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
5831
6308
  */
5832
6309
  ready: EventEmitter<void>;
5833
6310
  /**
5834
- * Panel changed
6311
+ * The current panel changed. Carries the panel's id (`panelId`, its key in
6312
+ * `chapter.panels`) and the id of the panel before it (`previousPanelId`).
5835
6313
  */
5836
- panelChange: EventEmitter<{
5837
- panel: Panel;
5838
- chapter: Chapter;
5839
- }>;
6314
+ panelChange: EventEmitter<PlayerPanelChangeEvent>;
5840
6315
  /**
5841
6316
  * Chapter changed
5842
6317
  */
@@ -5897,6 +6372,8 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
5897
6372
  * Current page (for page view)
5898
6373
  */
5899
6374
  currentPage?: Page;
6375
+ /** Cache for {@link pageBackground}: the page lookup runs once per panel. */
6376
+ private pageBackgroundCache;
5900
6377
  /**
5901
6378
  * Current chapter
5902
6379
  */
@@ -6008,6 +6485,21 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6008
6485
  * reveal modes and revisit-jumps).
6009
6486
  */
6010
6487
  visitedPanelIds: string[];
6488
+ /**
6489
+ * Page view and choices: the reader's choices so far (choice panel →
6490
+ * the panel the chosen hotspot jumped to). Panels off the chosen path —
6491
+ * and the branches of choices still ahead — show as placeholders in page
6492
+ * view; pages made up of off-path panels only are skipped.
6493
+ */
6494
+ private madeChoices;
6495
+ private branchGraph;
6496
+ /** Placeholders in page view (off the path, or behind a choice not made yet). */
6497
+ hiddenPanelIds: ReadonlySet<string>;
6498
+ /** Off the chosen path for good (pages of these only are skipped). */
6499
+ private unchosenPanelIds;
6500
+ private refreshBranchVisibility;
6501
+ /** A page whose panels are all off the chosen path: page navigation skips it. */
6502
+ private isPageOffPath;
6011
6503
  /**
6012
6504
  * Autoplay timer
6013
6505
  */
@@ -6016,6 +6508,25 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6016
6508
  * Autoplay enabled state
6017
6509
  */
6018
6510
  autoplayEnabled: boolean;
6511
+ /**
6512
+ * Autoplay dwell time source: 'author' plays each panel for its authored
6513
+ * `durationMs` (the CMS timeline), 'manual' for the reader's chosen
6514
+ * `secondsPerPanel`.
6515
+ */
6516
+ autoplayTiming: 'author' | 'manual';
6517
+ /** Page format of the page sequence page view shows (null: pages carry none). */
6518
+ activePageFormat: string | null;
6519
+ /** Cover image (screen-sized and thumbnail), empty when the work has none. */
6520
+ coverUrl: string;
6521
+ coverThumbUrl: string;
6522
+ /** The cover is shown in place of the story (before the first panel). */
6523
+ coverVisible: boolean;
6524
+ /** Upcoming panels the viewport warms (reading-order look-ahead). */
6525
+ preloadTargets: ViewportPreloadTarget[];
6526
+ /** PanelWave icon on the button that opens the toolbar. */
6527
+ readonly panelwaveIcon = "data:image/webp;base64,UklGRsAIAABXRUJQVlA4WAoAAAAQAAAAXwAAXwAAQUxQSEYBAAABkFvbkiJJPzPGAWYql4YcIS+6RyQ72ATSUBqRmaEiI/4sU5wVdyNiAvDVJAlt4wtbr3T+4c6X7cWxViRJ+N4sGGqc0xmk87w5BMnfIall+o5uajGYmvN+SpJ8Q9C1QSvGQK0Y1zshX8kYOWBtDNZqHlTIADJ69qgMWLnfgwxA1qgMWbmSEwSzrBl0zSkIqht6VM7rYaBJZdjKJjqO6XE5jzsmWRh44eQiNTLl0g4tMufRC//yPbz//v8XwJfoXrdokRk3F6mRKecnWCIrnOw8pcflPO7AHDUuZROobuhROW8qCGZZR1VzBoIkq9SYlMs5ARk9u9SIlHs9yAAyBndZWzRW87BCxpeCznVasUisGDe6Ifi6JJm6p5uWGEzNeT8jyPh2Fgw1zugM0nneGIJkfG+ShNax+e1X+p/mfN+ZH21FkoSvAlZQOCBUBwAAMCcAnQEqYABgAD49GIlDIiGhGOxlwCADxLEAYIrA6v8eP1X9i9HKuv5Xx19vbFX23fCT/ZeznlVf1A9ID+zew39Ov2c94f/D+pH9UP1N+AD+3fyDrIv1y9gD9gPTg9mz9nP2S9qe8VfrXKepEvtb+Z4bfc3/M+RP/L60B6gXaz/O/0TkG+sH+I+yX3ifyx5kzwD+gexF/Lf916P//J/kfS79Mf8/2K/6N/wP7jwp/6qmmH+Bke5fvpsKYgnbbHJ+5JNbzvovUMpGvFbPhSdXv4mudq7J3JpY+cd3YB+FrXSjO96Z1uqRSupyQKMks88O+qrrWcCv/IXTVsPZTiSVfG0F//RIHkujLTn7aNVcxMJ1qPdwfGRVjHu/zw25uaCx4k7Z+2qRHnHZ4Fv3LQDFvsllIbtdMVUEb7/s3vvk2y/D1AD+/4jx3hcTfhp2Fc0QpOSuEo0+XD2JgL78MEuNS4cD3szjG5u9ubfRvbb5E2Q9L4cjoTCGeuYUuTkaXDUbXaIVIBowYBor0PL95qme/l8L/c+jdWXBYSqk6+qDjlExFrNPtFND4UXmi7c+8/z/xKyetEEaVl96Rin/3FdLds5JrGII0SetRvv6p1TcPzBw1uo9NsxeB5tSANP1M791mBUwujUIXL+IxCMifqxpvLBFtFFa++zxc5cP3FUm85BSP/Cyk+2/WF6t8Zaw7bjyT1LOr4K56SaX6v/HkDsk7SF/8KprdETZPjtmEhrLD4RdS32Sxar50fJsW3OsG7fvnGgjvNa1pd7lt94v+mK4Mtr3ZNjw7qR/6G3+6Z/VM5r+Oxqa/m/KspviOYmWJQNJCER4+LDZ83GrGlNFQUT9skMKLmZvPfNbDuPXSqtjYXYkzKF4HwI5+nN6IG4Z2KPE+W1FUdiAlawAABwyJlahyiS/J9pxcipXizhve8YHG+Vl45/YURvvxY90ouentqgzpcEKPe7p7FLZaxcGChS2+zpH4T6s71Ovgg0QjMYHS76F2/I5mmklWr1ZRzesPETuNgw4JNu7o59og0/M/yGP8PQpTc87BiRwsJOkNJeYgBz85b+s6pFKhcn+dVQwMl+eluHcbj+BhrOhsUJB8UkB1phz3gMdxhsP5qx7+Dljmp5mhCtfFhejzwiLmMgSW1OgYQx8+P6S+JS6QdFVEgT0v/8/etNl3pWWZT9UpQm8ei9u03e11Ma93WFye0luqxuBfwnT+buQ7v0CY79n50q/4Wy/3i/QkaFBrO1mnykE82bs2O+ltFr5B821XqhQ8lKkQCgv18RXH98NTQTefkopusNNFb7/hXZLkewml5Xv1la/j4ZuQHowMeGIQHgNJ4AolpQ6S3TMkGnL2qzPiT3W75oNRwziMG+QgoSHuViyv64arFzYRwRCCq1FW5uFCiOOiiNudZqHnLI6yJXd823R2YznyRErj3O7fKW39fejyi/XNGQ5kdSIHEQKNgzGYFiQxkJ3ChQsakfAojOHyoswxfRZVCKiR6JTv7Q8vYc2a9QPtSMv2S7HgwJ0GPJepptro1F0YesT0e69qvGRdxWG6bfaAQolbdo6JxWEbNCtxTqLW184Kq6ttULPn2u7v1V7WFgkLO2PeFu394i4YF/08/fg7WbsP1HphLTpZmzzweaWlWIJhWH8YYcN+OxGKpIi0Z1FuznImzsRzQOF9Xc2T3eX9pHtr0QqixFizTt65n44mVGkl34jEs0MdVtIoCW56/58M+HHfjUk53MdPLOP1qMpt2Ks9p0CLrh04kNz/2jpjDout/WQqFwu5uGrj4J2F9Sv+AZi0pJkzu00A0jqNKnyJ1qikL/zyJClNINpUAYQ1ubYxUcy/Rd25s3ySipFU55NyUZWfXoZNkM/zZdP/6wsqqdU476WNeflerRVBfCX5tWHOMTMQbtmawbx7UQySm7pBK+Ntoo/IeFxyXqyFYrLz7ZkpInDBv9OK0lv+qCI8hFKDaCLyR19j/w/RrYxsFEHo/+PYcV0fuleAOSKodrWNm95a8kpbpB6iMipeiFlaZDkYs1yqn0XDVHOX1Z4/EJ68eqXkCYh8gIOQHx9R3BRIDOrw/kPdbeg1C5uOD5jnzHVnuyGH0svJHvQIM7iUlYrMc1x5xVuStPjRo4gknNs+k+PWwSdrMQVoNrMe+RLnoxURWUjjb7j3bnxLJ9Qq9SKJzUjTcyO62UF9iK/aoI/IJT/SDmuF2PWG8N99VR3QOi+cYE7FH272alWsjaPnbB6aUlSFnFuOJC8GAc0+mZqvoIl+oXncngAMUQPlGV30HZbH/MQ8v/7A4679grPQnyRcHdabNUje/PJS+RQsvUgRUVHUIMFCAczD9orXM2Nkwcfl30oUq4RAEoQ+oz5LyTIJhWSndtGwYb5/1drF/pMi7wMWMi66iLxCxrN87d1ZW3kBEEgeRNW/3CpBCX/0WPlqVNRmUs2z5skWXqzzNfb5FsqVva833/A+SX4Wu5onPUBNA2RXLK3+JchJR4Xl20AAA==";
6528
+ /** Panels on the current page (page view): all of them are "current" in the thumbnail strip. */
6529
+ get currentPagePanelIds(): string[];
6019
6530
  /**
6020
6531
  * Speech-bubble toggle state. Every bubble is implicitly subject to this
6021
6532
  * (schema 1.3) — per-bubble visibleIf is reserved for story logic.
@@ -6091,6 +6602,8 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6091
6602
  private analyticsSessionEnded;
6092
6603
  private workCompleteTracked;
6093
6604
  private lastTrackedPanelId?;
6605
+ /** Id of the panel of the last `panelChange` emission (its `previousPanelId` source). */
6606
+ private lastPanelChangeId?;
6094
6607
  /** Bound pagehide handler so add/removeEventListener match. */
6095
6608
  private readonly onPageHide;
6096
6609
  private readonly playerState;
@@ -6100,6 +6613,13 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6100
6613
  private readonly translationService;
6101
6614
  private readonly canvasCamera;
6102
6615
  private readonly paywallService;
6616
+ private readonly assetUrl;
6617
+ private readonly preloadService;
6618
+ private readonly host;
6619
+ /** The player is shown in fullscreen (kept in sync with the browser, Esc included). */
6620
+ isFullscreen: boolean;
6621
+ /** The browser can show the player in fullscreen (not e.g. on iPhone Safari). */
6622
+ readonly fullscreenAvailable: boolean;
6103
6623
  /** ngOnInit ran: later input changes are live updates, not initial values. */
6104
6624
  private initialized;
6105
6625
  /** The state subscriptions are set up once per instance, not per (re)load. */
@@ -6164,13 +6684,6 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6164
6684
  * navigation, so session_start precedes the first panel_view.
6165
6685
  */
6166
6686
  private startAnalyticsSession;
6167
- /**
6168
- * Deterministic reading order for a chapter: breadth-first over the graph
6169
- * from the entry panel(s), then any unreachable panels in declaration
6170
- * order (branching narratives have no single true order — BFS approximates
6171
- * "distance from start", which is what the funnel needs).
6172
- */
6173
- private readingOrderForChapter;
6174
6687
  /**
6175
6688
  * Coarse device class for analytics breakdowns (matches the dashboard's
6176
6689
  * device segments): touch + narrow = phone, touch + wide = tablet,
@@ -6183,7 +6696,16 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6183
6696
  * one-time `work_complete` signal.
6184
6697
  */
6185
6698
  private trackPanelView;
6186
- /** An end panel (no outgoing edges) of the manifest's last chapter. */
6699
+ /**
6700
+ * A panel's id: its key in `chapter.panels`. Panels don't reliably carry
6701
+ * their own id (`Panel.id` is optional in the format), so resolve it by
6702
+ * identity, as getCurrentPanelId() does.
6703
+ */
6704
+ private panelIdOf;
6705
+ /**
6706
+ * An end panel of the manifest's last chapter: no outgoing edges, or —
6707
+ * in a chapter without edges — the last panel in reading order.
6708
+ */
6187
6709
  private isEndOfWork;
6188
6710
  /**
6189
6711
  * Close the analytics session exactly once: emit `session_end` and flush
@@ -6216,6 +6738,13 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6216
6738
  * blocked attempt never changes story state.
6217
6739
  */
6218
6740
  navigateToPanel(chapterId: string, panelId: string, transition?: Transition, cameraMove?: CameraMove, mutations?: Mutation[]): Promise<void>;
6741
+ /**
6742
+ * Raise the gate in front of a panel the reader may not open: the age gate
6743
+ * for an unanswered age requirement (answered in-player), the paywall
6744
+ * otherwise. A host-supplied adapter still wins — it is the documented
6745
+ * override. Returns true when a gate was raised.
6746
+ */
6747
+ private raiseGateIfBlocked;
6219
6748
  /**
6220
6749
  * Rebuild the flat variable context handed to the viewport/canvas for
6221
6750
  * hotspot visibleIf evaluation. New object identity per call so OnPush
@@ -6233,6 +6762,11 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6233
6762
  * Keep the audio engine's playing set in step with the effective panel
6234
6763
  * (variant-resolved, so a variant's `audio` override wins) and the
6235
6764
  * variable context its tracks' `visibleIf` conditions read.
6765
+ *
6766
+ * A panel the renderers lock (age gate, paywall, `x-locked` stub) stays
6767
+ * silent: it syncs with no panel, which stops its tracks. The shell
6768
+ * re-syncs on `PaywallService.changes$`, so the audio starts once the age
6769
+ * is confirmed or the purchase lands.
6236
6770
  */
6237
6771
  private syncPanelAudio;
6238
6772
  /**
@@ -6321,6 +6855,21 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6321
6855
  * Toolbar event handlers
6322
6856
  */
6323
6857
  onToggleView(): void;
6858
+ /** Toolbar fullscreen button: show the player in fullscreen, or go back to the browser view. */
6859
+ toggleFullscreen(): void;
6860
+ /** Follow fullscreen changes from any source (button, Esc, F11-less browser UI). */
6861
+ onFullscreenChange(): void;
6862
+ /**
6863
+ * Double-click on the viewport: a panel in page view opens large in panel
6864
+ * view; in panel view the reader goes back to the page.
6865
+ */
6866
+ onPanelDoubleClick(panelId: string | null): void;
6867
+ /** Viewport previous arrow: the previous page in page view, else the previous panel. */
6868
+ onViewportPrevious(): void;
6869
+ /** Viewport next arrow: the next page in page view, else the next panel. */
6870
+ onViewportNext(): void;
6871
+ /** Switch to a view mode, setting up the page / video sequence / camera it needs. */
6872
+ private setViewMode;
6324
6873
  /** Land the canvas camera on the current panel (entering canvas view). */
6325
6874
  private jumpCameraToCurrentPanel;
6326
6875
  /**
@@ -6380,6 +6929,13 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6380
6929
  onPreferencesChange(prefs: Preferences): void;
6381
6930
  onToggleAutoplay(): void;
6382
6931
  onSecondsPerPanelChange(seconds: number): void;
6932
+ /** Toolbar: back to the author's timing ('author') or the reader's speed ('manual'). */
6933
+ onAutoplayTimingChange(timing: 'author' | 'manual'): void;
6934
+ /**
6935
+ * Seconds shown next to the autoplay controls: the reader's speed, or with
6936
+ * the author's timing the current panel's (page's) authored dwell time.
6937
+ */
6938
+ get autoplaySecondsDisplay(): number;
6383
6939
  onToggleThumbnails(): void;
6384
6940
  onOpenToc(): void;
6385
6941
  onOpenSettings(): void;
@@ -6408,8 +6964,8 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6408
6964
  * override; navigation keeps it per panel.
6409
6965
  */
6410
6966
  onCycleAlternative(): void;
6411
- /** Open the branch chooser with the paths currently open from this panel. */
6412
- onShowBranches(): void;
6967
+ /** Open the branch chooser with the paths currently open from this panel (or `panelId`). */
6968
+ onShowBranches(panelId?: string): void;
6413
6969
  onOpenExtras(): void;
6414
6970
  /** Toggle the reader's like for this work (persisted on the device). */
6415
6971
  onLike(): void;
@@ -6420,6 +6976,25 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6420
6976
  */
6421
6977
  onBookmark(): void;
6422
6978
  onShare(): void;
6979
+ /** Link in the open share dialog. */
6980
+ shareLink: string;
6981
+ /**
6982
+ * What to share: the host's `shareUrl`, else the current address with the
6983
+ * reading position — the page in page view, the panel in panel view, the
6984
+ * work itself on the cover. `embed` is dropped: the link opens the full
6985
+ * reader.
6986
+ */
6987
+ private buildShareLink;
6988
+ /** The reading position: cover, page (with its id), panel or canvas. */
6989
+ get currentLocation(): PlayerLocation;
6990
+ /** Last position reported through `locationChange`. */
6991
+ private lastLocationKey;
6992
+ /**
6993
+ * Report a changed reading position. Checked after every render: the
6994
+ * position changes in many places (navigation, view toggle, cover, page
6995
+ * format). Page view reports the page, not each panel the reader focuses.
6996
+ */
6997
+ ngAfterViewChecked(): void;
6423
6998
  onOpenComments(): void;
6424
6999
  /**
6425
7000
  * Modal close handlers
@@ -6437,6 +7012,7 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6437
7012
  onTocNavigate(target: {
6438
7013
  chapterId: string;
6439
7014
  panelId?: string;
7015
+ cover?: boolean;
6440
7016
  }): void;
6441
7017
  /**
6442
7018
  * Handle thumbnail navigation
@@ -6444,6 +7020,7 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6444
7020
  onThumbnailNavigate(target: {
6445
7021
  chapterId: string;
6446
7022
  panelId: string;
7023
+ cover?: boolean;
6447
7024
  }): void;
6448
7025
  /**
6449
7026
  * Keyboard shortcut handler
@@ -6480,6 +7057,24 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6480
7057
  * timer.
6481
7058
  */
6482
7059
  private startAutoplay;
7060
+ /** Run the wall-clock autoplay step: show progress, advance after `durationMs`. */
7061
+ private armAutoplayTimer;
7062
+ /** One autoplay step: leave the cover, turn the page (page view) or go to the next panel. */
7063
+ private autoplayAdvance;
7064
+ /** The reader is on the work's last panel (page view: its last page). */
7065
+ private atEndOfWork;
7066
+ /** Dwell time of one panel: authored `durationMs`, or the reader's seconds per panel. */
7067
+ private panelDwellMs;
7068
+ /** The current page's panel ids in reading order (placement order without one). */
7069
+ private pageReadingOrderIds;
7070
+ /**
7071
+ * Start the page-view video sequence. Image panels between the videos hold
7072
+ * it for their dwell time (authored `durationMs`, else the reader's seconds
7073
+ * per panel), so the art gets read before the next video starts.
7074
+ */
7075
+ private startPageSequence;
7076
+ /** Dwell time of what is on screen: the current panel, or every panel of the current page. */
7077
+ private currentDwellMs;
6483
7078
  /**
6484
7079
  * Stop autoplay
6485
7080
  */
@@ -6530,26 +7125,124 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6530
7125
  */
6531
7126
  private stopAutoplayProgress;
6532
7127
  /**
6533
- * Find the page that contains the current panel
7128
+ * The page (of the active format's page sequence) that shows a panel —
7129
+ * by default the current one. A panel the sequence leaves out lands on the
7130
+ * page closest before it in reading order.
6534
7131
  */
6535
7132
  private findPageContainingPanel;
6536
7133
  /**
6537
- * Navigate to next page (in page view mode)
6538
- */
6539
- navigateToNextPage(): void;
7134
+ * Background color of the page the reader is on — the open page in page
7135
+ * view, the page holding the current panel in panel view — so the frame
7136
+ * switches color exactly when the first panel of the next page appears.
7137
+ */
7138
+ get pageBackground(): string;
7139
+ /**
7140
+ * Open the initial view: page view (default; `initialViewMode` overrides)
7141
+ * on `page` — or, when that page is not in the screen's page format, the
7142
+ * page showing the current panel. Canvas chapters and hosts that force a
7143
+ * view mode keep theirs. Behind the cover the page sequence waits until
7144
+ * the cover is left.
7145
+ */
7146
+ private enterInitialView;
7147
+ /** A page by its manifest id, in any chapter and format. */
7148
+ private findPageById;
7149
+ /** The chapter that holds a panel. */
7150
+ private findChapterOfPanel;
7151
+ /** First panel of a page in reading order (placement order without one). */
7152
+ private firstPanelOf;
7153
+ /** The chapter's pages in the active page format (legacy pages without a format included). */
7154
+ chapterPages(chapter: Chapter | undefined): Page[];
7155
+ /**
7156
+ * Pick the page sequence for the current screen (or the host's
7157
+ * `pageFormat`). In page view a change of format re-opens the page that
7158
+ * shows the current panel in the new sequence.
7159
+ */
7160
+ private updatePageFormat;
7161
+ onWindowResize(): void;
7162
+ /**
7163
+ * After a jump to a panel (TOC, thumbnails, chapter change, next/previous):
7164
+ * the cover gives way, page view opens the page showing the panel, and the
7165
+ * look-ahead follows.
7166
+ */
7167
+ private afterJump;
7168
+ /** The chapter before (-1) or after (+1) the current one. */
7169
+ private adjacentChapter;
7170
+ private isFirstChapter;
7171
+ /** The panel a page starts with: first in its reading order, else its first placement. */
7172
+ private firstPanelIdOf;
7173
+ /** Show a page of the current chapter, its first panel becoming the current one. */
7174
+ private showPage;
7175
+ /**
7176
+ * Page view across a chapter boundary: the first (+1) or last (-1) page of
7177
+ * the adjacent chapter. The move goes through the panel gates like any
7178
+ * other; a blocked move stays put.
7179
+ */
7180
+ private turnToAdjacentChapter;
7181
+ /**
7182
+ * Navigate to next page (in page view mode); past the chapter's last page
7183
+ * to the next chapter. From the cover, the story starts.
7184
+ */
7185
+ navigateToNextPage(): Promise<void>;
7186
+ /** Make the cover visible (thumbnail strip, TOC, back from the first panel/page). */
7187
+ showCoverPage(): void;
7188
+ /** Leave the cover: the story starts at the current (first) panel or page. */
7189
+ hideCover(): void;
7190
+ /**
7191
+ * While the cover is up the viewport is not mounted, so its look-ahead
7192
+ * cannot warm what comes first. Warm it here: the first panel (page view:
7193
+ * the current page's panels) at the rendition a freshly mounted panel
7194
+ * starts with (the catalog's primary variant, before layout knows its
7195
+ * width) — the story then appears without a load gap.
7196
+ */
7197
+ private warmBehindCover;
7198
+ /** Resolve the cover image from `meta.cover` / `extras.cover` through the asset catalog. */
7199
+ private resolveCover;
7200
+ /** The work's title in the current locale (cover alt text). */
7201
+ get workTitle(): string;
7202
+ /**
7203
+ * Reading-order look-ahead for the viewport's preloader: the next panels
7204
+ * (panel view) or the next pages' panels (page view), crossing into the
7205
+ * next chapter, so autoplay and paging never wait for artwork.
7206
+ */
7207
+ private updatePreloadTargets;
6540
7208
  /**
6541
7209
  * Restart the page-view video sequence (and autoplay arming) after the
6542
7210
  * current page changed.
6543
7211
  */
6544
7212
  private onPageChanged;
6545
7213
  /**
6546
- * Navigate to previous page (in page view mode)
7214
+ * Page view shows a whole page at once, so the per-panel navigation gate
7215
+ * never runs: panels the paywall locks render as placeholders. When some of
7216
+ * them only wait for the reader's age, ask once for the page (the highest
7217
+ * minimum age among them); confirming unlocks them in place.
7218
+ */
7219
+ private promptAgeGateForPage;
7220
+ /**
7221
+ * Page view: the reader activated a locked panel's placeholder. Raise the
7222
+ * gate that unlocks it in place, without moving: the age gate when only
7223
+ * the age is missing, otherwise the paywall for that panel (the same gate
7224
+ * a panel move would raise). A panel that is not locked raises nothing.
7225
+ */
7226
+ onLockedPanelActivate(panelId: string): void;
7227
+ /**
7228
+ * Ask once for the age the given panels wait for (the highest minimum
7229
+ * age among those locked only by their age), with no pending move:
7230
+ * confirming unlocks them in place.
6547
7231
  */
6548
- navigateToPreviousPage(): void;
7232
+ private promptAgeGate;
6549
7233
  /**
6550
- * Get available locales from manifest
7234
+ * Navigate to previous page (in page view mode); before the chapter's
7235
+ * first page to the previous chapter's last page, before the work's first
7236
+ * page to the cover.
7237
+ */
7238
+ navigateToPreviousPage(): Promise<void>;
7239
+ /**
7240
+ * Languages the work can be read in (the toolbar shows the language switch
7241
+ * when there are several): `meta.locales` plus the languages the content
7242
+ * is translated into. Computed once per manifest.
6551
7243
  */
6552
7244
  get availableLocales(): LocaleCode[];
7245
+ private localeCache?;
6553
7246
  /**
6554
7247
  * Get characters from manifest mapped to roster format
6555
7248
  */
@@ -6611,8 +7304,19 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6611
7304
  * "Choices". A closed conditional edge does not count.
6612
7305
  */
6613
7306
  get hasBranchesAhead(): boolean;
6614
- /** Outgoing edges of the current panel whose conditions pass right now. */
7307
+ /** Outgoing edges of the current panel (or `fromPanelId`) whose conditions pass right now. */
6615
7308
  private openEdges;
7309
+ /**
7310
+ * The current panel is a decision: two or more open paths carry an authored
7311
+ * choice label. "Next" then asks the reader instead of following one of them.
7312
+ */
7313
+ private isDecisionAhead;
7314
+ /**
7315
+ * Page view: a decision on the current page the reader has not taken yet
7316
+ * (the last one in reading order). Turning the page would only show the
7317
+ * placeholders of its branches, so "next" asks instead.
7318
+ */
7319
+ private pendingDecisionOnPage;
6616
7320
  /** Open outgoing edges with reader-facing labels. */
6617
7321
  private computeBranchChoices;
6618
7322
  private branchLabel;
@@ -6620,7 +7324,7 @@ declare class PlayerShellComponent implements OnInit, OnChanges, OnDestroy {
6620
7324
  /** Traverse the chosen edge (its transition / camera move / mutations apply). */
6621
7325
  onBranchChosen(choice: BranchChoice): Promise<void>;
6622
7326
  static ɵfac: i0.ɵɵFactoryDeclaration<PlayerShellComponent, never>;
6623
- static ɵcmp: i0.ɵɵComponentDeclaration<PlayerShellComponent, "pw-player-shell", never, { "manifest": { "alias": "manifest"; "required": false; }; "manifestUrl": { "alias": "manifestUrl"; "required": false; }; "entitlementAdapter": { "alias": "entitlementAdapter"; "required": false; }; "entitlementSnapshot": { "alias": "entitlementSnapshot"; "required": false; }; "entitlementEndpoint": { "alias": "entitlementEndpoint"; "required": false; }; "readerToken": { "alias": "readerToken"; "required": false; }; "initialVariables": { "alias": "initialVariables"; "required": false; }; "locale": { "alias": "locale"; "required": false; }; "initialChapterId": { "alias": "initialChapterId"; "required": false; }; "initialPanelId": { "alias": "initialPanelId"; "required": false; }; "autoplay": { "alias": "autoplay"; "required": false; }; "secondsPerPanel": { "alias": "secondsPerPanel"; "required": false; }; "reducedMotion": { "alias": "reducedMotion"; "required": false; }; "showToolbar": { "alias": "showToolbar"; "required": false; }; "viewModeOverride": { "alias": "viewModeOverride"; "required": false; }; }, { "paywallAction": "paywallAction"; "ageVerified": "ageVerified"; "likeChange": "likeChange"; "bookmarkChange": "bookmarkChange"; "ready": "ready"; "panelChange": "panelChange"; "chapterChange": "chapterChange"; "error": "error"; "localeChange": "localeChange"; "variableChange": "variableChange"; "navigationAttempt": "navigationAttempt"; "cameraChange": "cameraChange"; }, never, never, true, never>;
7327
+ static ɵcmp: i0.ɵɵComponentDeclaration<PlayerShellComponent, "pw-player-shell", never, { "manifest": { "alias": "manifest"; "required": false; }; "manifestUrl": { "alias": "manifestUrl"; "required": false; }; "entitlementAdapter": { "alias": "entitlementAdapter"; "required": false; }; "entitlementSnapshot": { "alias": "entitlementSnapshot"; "required": false; }; "entitlementEndpoint": { "alias": "entitlementEndpoint"; "required": false; }; "readerToken": { "alias": "readerToken"; "required": false; }; "initialVariables": { "alias": "initialVariables"; "required": false; }; "locale": { "alias": "locale"; "required": false; }; "initialChapterId": { "alias": "initialChapterId"; "required": false; }; "initialPanelId": { "alias": "initialPanelId"; "required": false; }; "initialPageId": { "alias": "initialPageId"; "required": false; }; "initialViewMode": { "alias": "initialViewMode"; "required": false; }; "shareUrl": { "alias": "shareUrl"; "required": false; }; "autoplay": { "alias": "autoplay"; "required": false; }; "secondsPerPanel": { "alias": "secondsPerPanel"; "required": false; }; "pageFormat": { "alias": "pageFormat"; "required": false; }; "showCover": { "alias": "showCover"; "required": false; }; "reducedMotion": { "alias": "reducedMotion"; "required": false; }; "showToolbar": { "alias": "showToolbar"; "required": false; }; "viewModeOverride": { "alias": "viewModeOverride"; "required": false; }; }, { "paywallAction": "paywallAction"; "ageVerified": "ageVerified"; "likeChange": "likeChange"; "bookmarkChange": "bookmarkChange"; "locationChange": "locationChange"; "ready": "ready"; "panelChange": "panelChange"; "chapterChange": "chapterChange"; "error": "error"; "localeChange": "localeChange"; "variableChange": "variableChange"; "navigationAttempt": "navigationAttempt"; "cameraChange": "cameraChange"; }, never, never, true, never>;
6624
7328
  }
6625
7329
 
6626
7330
  /**
@@ -6685,9 +7389,11 @@ declare class CanvasStageComponent implements OnInit, OnChanges, OnDestroy {
6685
7389
  cameraChange: EventEmitter<CameraState>;
6686
7390
  readonly camera: CanvasCameraService;
6687
7391
  private readonly manifestService;
7392
+ private readonly assetUrlService;
6688
7393
  private readonly preloadService;
6689
7394
  private readonly elementRef;
6690
7395
  private readonly cdr;
7396
+ private readonly paywall;
6691
7397
  private readonly destroy$;
6692
7398
  /** Current world transform CSS for `.canvas-world`. */
6693
7399
  worldTransform: string;
@@ -6756,6 +7462,12 @@ declare class CanvasStageComponent implements OnInit, OnChanges, OnDestroy {
6756
7462
  /** Balloon lettering scale (same DIN-A4 reference as the panel viewport). */
6757
7463
  getReadingScale(): number;
6758
7464
  isCurrent(panelId: string): boolean;
7465
+ /**
7466
+ * Show the locked placeholder instead of the panel's content: a server
7467
+ * stub (`"x-locked": true`), or a panel the paywall rules lock for the
7468
+ * reader's current snapshot (age gate not yet answered, not purchased).
7469
+ */
7470
+ isLocked(panel: Panel | null | undefined, panelId?: string | null): boolean;
6759
7471
  trackPlacement(_index: number, entry: StagePlacement): string;
6760
7472
  private freeRoamAllowed;
6761
7473
  onPointerDown(event: PointerEvent): void;
@@ -6924,7 +7636,25 @@ declare class HotspotsOverlayComponent implements OnChanges {
6924
7636
  * Hotspots passing their visibleIf condition
6925
7637
  */
6926
7638
  visibleHotspots: Hotspot[];
7639
+ /**
7640
+ * Hotspots shown as labelled buttons: `display: "button"`, and with `auto`
7641
+ * (the default) the goTo hotspots of a panel whose hotspots lead to two or
7642
+ * more panels (a decision). `area` — and any other auto hotspot — stays an
7643
+ * invisible area over the artwork (a door, a phone, a painted button).
7644
+ */
7645
+ choiceIds: ReadonlySet<string>;
6927
7646
  ngOnChanges(): void;
7647
+ /** The visible button text of a choice hotspot (its label), or '' for plain areas. */
7648
+ choiceText(h: Hotspot): string;
7649
+ /** Bounding box (container units) of a hotspot shape — where its button label sits. */
7650
+ boundsOf(s: HotspotShape): {
7651
+ x: number;
7652
+ y: number;
7653
+ width: number;
7654
+ height: number;
7655
+ };
7656
+ /** Label font size: fits the button height, readable but never huge. */
7657
+ choiceFontSize(h: Hotspot): number;
6928
7658
  /**
6929
7659
  * Accessible label: ariaLabel wins over label, falls back to the id
6930
7660
  */
@@ -7072,6 +7802,7 @@ declare class VideoLayerComponent implements OnInit, OnChanges, OnDestroy {
7072
7802
  private readonly sequencer;
7073
7803
  private readonly audioEngine;
7074
7804
  private readonly cdr;
7805
+ private readonly assetUrl;
7075
7806
  /** Follows the player-wide Audio toggle (engine master mute). */
7076
7807
  private masterMuteSub?;
7077
7808
  private readonly host;
@@ -7079,8 +7810,6 @@ declare class VideoLayerComponent implements OnInit, OnChanges, OnDestroy {
7079
7810
  src: string;
7080
7811
  /** Reverse-variant URL used for the pingpong backward pass (empty if none). */
7081
7812
  reverseSrc: string;
7082
- /** Base URL for resolving relative paths. */
7083
- baseUrl: string;
7084
7813
  /** Poster image URL. */
7085
7814
  poster: string;
7086
7815
  /** Show native controls. */
@@ -7176,6 +7905,8 @@ declare class VideoLayerComponent implements OnInit, OnChanges, OnDestroy {
7176
7905
  private registeredWithSequencer;
7177
7906
  /** Whether this video is currently the sequencer's active slot member. */
7178
7907
  private activatedBySequencer;
7908
+ /** An `on-view` video started by hovering it (page view), not by the sequence. */
7909
+ private hoverPlaying;
7179
7910
  /**
7180
7911
  * What triggered the most recent playback, for tracking payloads. Set when
7181
7912
  * playback starts (sequencer / view / hover / click).
@@ -7189,6 +7920,7 @@ declare class VideoLayerComponent implements OnInit, OnChanges, OnDestroy {
7189
7920
  private readonly onTimeUpdateBound;
7190
7921
  getVideoUrl(): string;
7191
7922
  getPosterUrl(): string;
7923
+ /** Absolute URLs as-is; relative ones via the manifest's assets.base / manifest URL. */
7192
7924
  private resolveUrl;
7193
7925
  private get video();
7194
7926
  private get startTimeSec();
@@ -7224,14 +7956,27 @@ declare class VideoLayerComponent implements OnInit, OnChanges, OnDestroy {
7224
7956
  /** Called from the template once the <video> element is available. */
7225
7957
  onLoadedMetadata(): void;
7226
7958
  private handleViewActiveChange;
7227
- /** Click / tap handler (on-click toggle). */
7959
+ /**
7960
+ * Click / tap handler: on-click layers toggle; any other video that is
7961
+ * not playing (waiting for its turn in the page sequence, finished, or
7962
+ * paused) starts on a click.
7963
+ */
7228
7964
  onClick(): void;
7229
7965
  /** Keyboard handler (Enter / Space) for on-click layers. */
7230
7966
  onKeydown(event: KeyboardEvent): void;
7231
- /** Mouse enter — on-hover start (page view only). */
7967
+ /**
7968
+ * Mouse enter — on-hover start (page view only). An `on-view` video in page
7969
+ * view that is not playing (still waiting for its turn in the sequence, or
7970
+ * finished) plays while the pointer rests on it too.
7971
+ */
7232
7972
  onMouseEnter(): void;
7233
- /** Mouse leave — on-hover pause keeping position. */
7973
+ /**
7974
+ * Mouse leave — on-hover pause keeping position; an `on-view` video the
7975
+ * hover started pauses too, unless the sequence took it over meanwhile.
7976
+ */
7234
7977
  onMouseLeave(): void;
7978
+ /** The video is not playing: never started, paused or finished. */
7979
+ private isIdle;
7235
7980
  private togglePlayback;
7236
7981
  /** Whether this layer participates in keyboard interaction. */
7237
7982
  isKeyboardInteractive(): boolean;
@@ -7336,7 +8081,7 @@ declare class VideoLayerComponent implements OnInit, OnChanges, OnDestroy {
7336
8081
  private scheduleFrameStep;
7337
8082
  private stopFrameStepping;
7338
8083
  static ɵfac: i0.ɵɵFactoryDeclaration<VideoLayerComponent, never>;
7339
- static ɵcmp: i0.ɵɵComponentDeclaration<VideoLayerComponent, "pw-video-layer", never, { "src": { "alias": "src"; "required": false; }; "reverseSrc": { "alias": "reverseSrc"; "required": false; }; "baseUrl": { "alias": "baseUrl"; "required": false; }; "poster": { "alias": "poster"; "required": false; }; "controls": { "alias": "controls"; "required": false; }; "playMode": { "alias": "playMode"; "required": false; }; "startMode": { "alias": "startMode"; "required": false; }; "muted": { "alias": "muted"; "required": false; }; "startAtMs": { "alias": "startAtMs"; "required": false; }; "loopFromMs": { "alias": "loopFromMs"; "required": false; }; "streaming": { "alias": "streaming"; "required": false; }; "viewMode": { "alias": "viewMode"; "required": false; }; "viewActive": { "alias": "viewActive"; "required": false; }; "reducedMotion": { "alias": "reducedMotion"; "required": false; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; }; "videoId": { "alias": "videoId"; "required": false; }; "panelId": { "alias": "panelId"; "required": false; }; "assetId": { "alias": "assetId"; "required": false; }; "placementId": { "alias": "placementId"; "required": false; }; "readingOrderIndex": { "alias": "readingOrderIndex"; "required": false; }; "placementZ": { "alias": "placementZ"; "required": false; }; "placementY": { "alias": "placementY"; "required": false; }; "placementX": { "alias": "placementX"; "required": false; }; }, { "videoPlay": "videoPlay"; "videoPause": "videoPause"; "videoEnd": "videoEnd"; "videoLoop": "videoLoop"; "passComplete": "passComplete"; "videoError": "videoError"; }, never, never, true, never>;
8084
+ static ɵcmp: i0.ɵɵComponentDeclaration<VideoLayerComponent, "pw-video-layer", never, { "src": { "alias": "src"; "required": false; }; "reverseSrc": { "alias": "reverseSrc"; "required": false; }; "poster": { "alias": "poster"; "required": false; }; "controls": { "alias": "controls"; "required": false; }; "playMode": { "alias": "playMode"; "required": false; }; "startMode": { "alias": "startMode"; "required": false; }; "muted": { "alias": "muted"; "required": false; }; "startAtMs": { "alias": "startAtMs"; "required": false; }; "loopFromMs": { "alias": "loopFromMs"; "required": false; }; "streaming": { "alias": "streaming"; "required": false; }; "viewMode": { "alias": "viewMode"; "required": false; }; "viewActive": { "alias": "viewActive"; "required": false; }; "reducedMotion": { "alias": "reducedMotion"; "required": false; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; }; "videoId": { "alias": "videoId"; "required": false; }; "panelId": { "alias": "panelId"; "required": false; }; "assetId": { "alias": "assetId"; "required": false; }; "placementId": { "alias": "placementId"; "required": false; }; "readingOrderIndex": { "alias": "readingOrderIndex"; "required": false; }; "placementZ": { "alias": "placementZ"; "required": false; }; "placementY": { "alias": "placementY"; "required": false; }; "placementX": { "alias": "placementX"; "required": false; }; }, { "videoPlay": "videoPlay"; "videoPause": "videoPause"; "videoEnd": "videoEnd"; "videoLoop": "videoLoop"; "passComplete": "passComplete"; "videoError": "videoError"; }, never, never, true, never>;
7340
8085
  }
7341
8086
 
7342
8087
  declare class PlayerComponent {
@@ -7344,5 +8089,5 @@ declare class PlayerComponent {
7344
8089
  static ɵcmp: i0.ɵɵComponentDeclaration<PlayerComponent, "pw-player", never, {}, {}, never, never, true, never>;
7345
8090
  }
7346
8091
 
7347
- export { ANONYMOUS_READER, ActionModalComponent, AgeGateComponent, AudioEngineService, BranchChooserComponent, CANVAS_PANEL_BUDGET, CanvasCameraService, CanvasStageComponent, ComicBalloon, DEFAULT_BALLOON_CONFIG, DEFAULT_STALL_TIMEOUT_MS, DEFAULT_VIDEO_MUTED, DEFAULT_VIDEO_PLAY_MODE, DEFAULT_VIDEO_START_MODE, EntitlementError, EntitlementService, ExportService, FlowEngineService, HotspotActionService, HotspotsOverlayComponent, HttpEntitlementAdapter, ImageCacheService, ManifestService, MockEntitlementAdapter, NullEntitlementAdapter, PANEL_AUDIO_LEAVE_FADE_MS, PanelAudioService, PaywallOverlayComponent, PaywallService, PlayerComponent, PlayerEvent, PlayerService, PlayerShellComponent, PlayerStateService, PluginHostService, PluginSandboxComponent, PreloadService, PwIconComponent, RESERVED_NAMESPACES, SpeechBubblesComponent, TrackingService, TranslationService, UserGestureService, VISIBILITY_THRESHOLD, VariableStoreService, VideoControllerService, VideoLayerComponent, VideoSequencerService, VisibilityService, applyVariantOverrides, balloonConfigToRenderOptions, balloonConfigToTailOptions, busForRole, chapterOrderFromManifest, createBalloon, evaluatePanelAccess, evaluateWorkAccess, findWorkGateRule, fromManifestRule, isExtraLocked, mergeBalloonConfig, readingOrderFromManifest, resolveMuted, resolvePanelVariant, resolvePanels, resolvePlayMode, resolveStartMode, resolveVideoConfig, rulesFromManifest, satisfiesRule, selectVariant, toEntitlementStatus };
7348
- export type { AccessDecision, AgeVerificationResult, AssetBase, AssetCatalogItem, AssetCatalogItemAudio, AssetCatalogItemImage, AssetCatalogItemJson, AssetCatalogItemPluginPayload, AssetCatalogItemSubtitle, AssetCatalogItemVector, AssetCatalogItemVideo, AssetCategory, AssetCommon, AssetRef, AssetReference, AssetVariant, Assets, AudioLayer, AudioRole, AudioTrack, AudioVariant, BalloonConfig, BalloonConfigOverride, BalloonOptions, BalloonRenderResult, BalloonType, Bookmark, BoundingBox, BranchChoice, BubbleStyle, BubbleTail, CameraMove, CameraMovePath, CameraState, CameraZoomProfile, CanvasBackground, CanvasCameraPolicy, CanvasDecoration, CanvasFitMode, CanvasFreeRoam, CanvasLayout, CanvasOverviewPolicy, CanvasPlacement, CanvasRevealMode, Chapter, Character$1 as Character, ContentWarning, Creator, DecisionEvent, DeepLinkTarget, DeviceVariables, Edge, EffectiveVideoConfig, EntitlementAdapter$1 as EntitlementAdapter, EntitlementContext, EntitlementErrorCode, EntitlementSnapshot, EntitlementSnapshotResponse, EntitlementStatus, EntitlementVariables, ErrorEvent$1 as ErrorEvent, EvaluatorEntitlementType, EvaluatorRule, EvaluatorScope, ExtraImage, ExtraItem, Extras, FormatView, Graph, HideBorderConfig, Hotspot, HotspotAction, HotspotCircle, HotspotGoToAction, HotspotOpenExtrasAction, HotspotOpenModalAction, HotspotPluginEventAction, HotspotPolygon, HotspotRect, HotspotSetVariablesAction, HotspotShape, HotspotUiEffect, HttpEntitlementAdapterConfig, ImageLayer, ImageVariant, JsonLogic, JsonLogicExpression, JsonValue, JsonVariant, Layer, LayerKind, LayerViewMode, LocaleCode, LocalizedString, LockReason, Meta, Mutation, MutationOperation, NavigationPath, NodeMetadata, NormalizedPoint, NormalizedRect, OutputFormat, OutputPreset, OverlayState, Page, PageLayout, Panel, PanelAudioRole, PanelAudioTrack, PanelChangeEvent, PanelPartial, PanelPlacement, PanelRef, PanelVariant, PanelWaveManifest, PanelwaveHeader, Paywall, PaywallAction, PaywallEvent, PaywallGate$1 as PaywallGate, PaywallProduct, PaywallRule, PlaybackState, PlayerError, PlayerErrorCode, PlayerEventData, PlayerOptions, PlayerPreferences, PlayerState, PlayerStatistics, PluginInstance$1 as PluginInstance, PluginLayer, PreferenceVariables, PreloadItem, PreloadPriority, PreloadSettings, PreloadStatus$1 as PreloadStatus, PreviewInfo, PurchaseInfo, ReadingProgress, ResolvedPanel, ResolvedPanelAudio, SequenceAudioTrack, SequencedVideo, SequencedVideoInfo, Settings, SpeechBubble, StagePlacement, SubtitleVariant, TailConfig, TailCurve, TailOptions, TextLayer, TextStyle, Tracking, TrackingConfig$1 as TrackingConfig, TrackingEvent, Transform, Transition, TraversalOptions, TraversalResult, UIDefaults, UISettings, UserInfo, UserVariables, VariableChangeEvent, VariableContext, VariableDefinition, VariableMutation, VariableScope, VariableStore, VariableType, VariableValidationResult, Variables, VariantContext, VectorVariant, VideoEvent, VideoLayer, VideoPlayMode, VideoPoster, VideoStartMode, VideoState, VideoStatus, VideoTrackingPayload, VideoTrigger, VideoVariant, ViewMode, ViewportState, VisibilityChange, WorldPoint, WorldRect };
8092
+ export { ANONYMOUS_READER, ActionModalComponent, AgeGateComponent, AssetUrlService, AudioEngineService, BranchChooserComponent, CANVAS_PANEL_BUDGET, CanvasCameraService, CanvasStageComponent, ComicBalloon, DEFAULT_BALLOON_CONFIG, DEFAULT_PAGE_BACKGROUND, DEFAULT_STALL_TIMEOUT_MS, DEFAULT_VIDEO_MUTED, DEFAULT_VIDEO_PLAY_MODE, DEFAULT_VIDEO_START_MODE, EntitlementError, EntitlementService, ExportService, FlowEngineService, HotspotActionService, HotspotsOverlayComponent, HttpEntitlementAdapter, ImageCacheService, LOCATION_PARAMS, ManifestService, MockEntitlementAdapter, NullEntitlementAdapter, PANEL_AUDIO_LEAVE_FADE_MS, PanelAudioService, PaywallOverlayComponent, PaywallService, PlayerComponent, PlayerEvent, PlayerService, PlayerShellComponent, PlayerStateService, PluginHostService, PluginSandboxComponent, PreloadService, PwIconComponent, RESERVED_NAMESPACES, SpeechBubblesComponent, THUMBNAIL_WIDTH, TrackingService, TranslationService, UserGestureService, VISIBILITY_THRESHOLD, VariableStoreService, VideoControllerService, VideoLayerComponent, VideoSequencerService, VisibilityService, applyVariantOverrides, balloonConfigToRenderOptions, balloonConfigToTailOptions, busForRole, chapterOrderFromManifest, coverImageSrc, createBalloon, evaluatePanelAccess, evaluateWorkAccess, findWorkGateRule, fromManifestRule, isExtraLocked, isLockedPanel, locationUrl, mergeBalloonConfig, pageAspectRatio, pageFormatOf, pageFormatsOf, pagesForFormat, panelThumbnailSrc, parseLocationSearch, pickPageFormat, rankPageFormats, readingOrderFromManifest, resolveMuted, resolvePageBackground, resolvePanelVariant, resolvePanels, resolvePlayMode, resolveStartMode, resolveVideoConfig, rulesFromManifest, satisfiesRule, screenClassFor, selectVariant, toEntitlementStatus };
8093
+ export type { AccessDecision, AgeVerificationResult, AnimatableProperty, AnimationEasing, AnimationKeyframe, AssetBase, AssetCatalogItem, AssetCatalogItemAudio, AssetCatalogItemImage, AssetCatalogItemJson, AssetCatalogItemPluginPayload, AssetCatalogItemSubtitle, AssetCatalogItemVector, AssetCatalogItemVideo, AssetCategory, AssetCommon, AssetRef, AssetReference, AssetVariant, Assets, AudioLayer, AudioRole, AudioTrack, AudioVariant, BalloonConfig, BalloonConfigOverride, BalloonOptions, BalloonRenderResult, BalloonType, Bookmark, BoundingBox, BranchChoice, BubbleStyle, BubbleTail, CameraMove, CameraMovePath, CameraState, CameraZoomProfile, CanvasBackground, CanvasCameraPolicy, CanvasDecoration, CanvasFitMode, CanvasFreeRoam, CanvasLayout, CanvasOverviewPolicy, CanvasPlacement, CanvasRevealMode, Chapter, Character$1 as Character, ContentWarning, Creator, DecisionEvent, DeepLinkTarget, DeviceVariables, Edge, EffectiveVideoConfig, EntitlementAdapter$1 as EntitlementAdapter, EntitlementContext, EntitlementErrorCode, EntitlementSnapshot, EntitlementSnapshotResponse, EntitlementStatus, EntitlementVariables, ErrorEvent$1 as ErrorEvent, EvaluatorEntitlementType, EvaluatorRule, EvaluatorScope, ExtraImage, ExtraItem, Extras, FormatView, Graph, HideBorderConfig, Hotspot, HotspotAction, HotspotCircle, HotspotGoToAction, HotspotOpenExtrasAction, HotspotOpenModalAction, HotspotPluginEventAction, HotspotPolygon, HotspotRect, HotspotSetVariablesAction, HotspotShape, HotspotUiEffect, HttpEntitlementAdapterConfig, ImageLayer, ImageVariant, JsonLogic, JsonLogicExpression, JsonValue, JsonVariant, Layer, LayerKind, LayerViewMode, LocaleCode, LocalizedString, LockReason, Meta, Mutation, MutationOperation, NavigationPath, NodeMetadata, NormalizedPoint, NormalizedRect, OutputFormat, OutputPreset, OverlayState, Page, PageLayout, PageVisual, Panel, PanelAnimations, PanelAudioRole, PanelAudioTrack, PanelChangeEvent, PanelPartial, PanelPlacement, PanelRef, PanelVariant, PanelWaveManifest, PanelwaveHeader, Paywall, PaywallAction, PaywallEvent, PaywallGate$1 as PaywallGate, PaywallProduct, PaywallRule, PlaybackState, PlayerError, PlayerErrorCode, PlayerEventData, PlayerLocation, PlayerOptions, PlayerPanelChangeEvent, PlayerPreferences, PlayerState, PlayerStatistics, PlayerView, PluginInstance$1 as PluginInstance, PluginLayer, PreferenceVariables, PreloadItem, PreloadPriority, PreloadSettings, PreloadStatus$1 as PreloadStatus, PreviewInfo, PurchaseInfo, ReadingProgress, ResolvedPanel, ResolvedPanelAudio, ScreenClass, SequenceAudioTrack, SequencedVideo, SequencedVideoInfo, Settings, SpeechBubble, StagePlacement, SubtitleVariant, TailConfig, TailCurve, TailOptions, TextLayer, TextStyle, Tracking, TrackingConfig$1 as TrackingConfig, TrackingEvent, Transform, Transition, TraversalOptions, TraversalResult, UIDefaults, UISettings, UserInfo, UserVariables, VariableChangeEvent, VariableContext, VariableDefinition, VariableMutation, VariableScope, VariableStore, VariableType, VariableValidationResult, Variables, VariantContext, VectorVariant, VideoEvent, VideoLayer, VideoPlayMode, VideoPoster, VideoStartMode, VideoState, VideoStatus, VideoTrackingPayload, VideoTrigger, VideoVariant, ViewMode, ViewportState, VisibilityChange, WorldPoint, WorldRect };