@panelwave/player 1.0.0 → 1.0.1
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.
|
@@ -9714,11 +9714,11 @@ class VideoLayerComponent {
|
|
|
9714
9714
|
this.frameStepHandle = null;
|
|
9715
9715
|
}
|
|
9716
9716
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: VideoLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9717
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: VideoLayerComponent, isStandalone: true, selector: "pw-video-layer", inputs: { src: "src", reverseSrc: "reverseSrc", baseUrl: "baseUrl", poster: "poster", controls: "controls", playMode: "playMode", startMode: "startMode", muted: "muted", startAtMs: "startAtMs", loopFromMs: "loopFromMs", streaming: "streaming", viewMode: "viewMode", viewActive: "viewActive", reducedMotion: "reducedMotion", ariaLabel: "ariaLabel", videoId: "videoId", panelId: "panelId", assetId: "assetId", placementId: "placementId", readingOrderIndex: "readingOrderIndex", placementZ: "placementZ", placementY: "placementY", placementX: "placementX" }, outputs: { videoPlay: "videoPlay", videoPause: "videoPause", videoEnd: "videoEnd", videoLoop: "videoLoop", passComplete: "passComplete", videoError: "videoError" }, viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoElement"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\
|
|
9717
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: VideoLayerComponent, isStandalone: true, selector: "pw-video-layer", inputs: { src: "src", reverseSrc: "reverseSrc", baseUrl: "baseUrl", poster: "poster", controls: "controls", playMode: "playMode", startMode: "startMode", muted: "muted", startAtMs: "startAtMs", loopFromMs: "loopFromMs", streaming: "streaming", viewMode: "viewMode", viewActive: "viewActive", reducedMotion: "reducedMotion", ariaLabel: "ariaLabel", videoId: "videoId", panelId: "panelId", assetId: "assetId", placementId: "placementId", readingOrderIndex: "readingOrderIndex", placementZ: "placementZ", placementY: "placementY", placementX: "placementX" }, outputs: { videoPlay: "videoPlay", videoPause: "videoPause", videoEnd: "videoEnd", videoLoop: "videoLoop", passComplete: "passComplete", videoError: "videoError" }, viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoElement"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"video-layer-container\"\n [class.interactive]=\"isKeyboardInteractive()\"\n [attr.tabindex]=\"isKeyboardInteractive() ? 0 : null\"\n [attr.role]=\"isKeyboardInteractive() ? 'button' : null\"\n [attr.aria-label]=\"ariaLabel || null\"\n (click)=\"onClick()\"\n (keydown)=\"onKeydown($event)\"\n (mouseenter)=\"onMouseEnter()\"\n (mouseleave)=\"onMouseLeave()\">\n <video\n #videoElement\n class=\"video-layer\"\n [src]=\"getVideoUrl()\"\n [poster]=\"getPosterUrl()\"\n [muted]=\"displayMuted\"\n [controls]=\"controls\"\n playsinline\n preload=\"metadata\"\n (loadedmetadata)=\"onLoadedMetadata()\"\n (error)=\"onError($event)\">\n Your browser does not support the video tag.\n </video>\n\n @if (showUnmuteButton) {\n <button\n type=\"button\"\n class=\"video-unmute-affordance\"\n aria-label=\"Unmute video\"\n (click)=\"onUnmuteClick($event)\">\n <span class=\"video-unmute-icon\" aria-hidden=\"true\">\uD83D\uDD0A</span>\n <span class=\"video-unmute-label\">Unmute</span>\n </button>\n }\n</div>\n", styles: [".video-layer-container{position:relative;width:100%;height:100%;background:#000}.video-layer{width:100%;height:100%;display:block;object-fit:contain}.video-layer::-webkit-media-controls-panel{background:linear-gradient(to bottom,transparent,rgba(0,0,0,.7))}.video-layer-container.interactive{cursor:pointer}.video-layer-container.interactive:focus-visible{outline:2px solid #4f9dff;outline-offset:2px}.video-unmute-affordance{position:absolute;bottom:12px;right:12px;display:inline-flex;align-items:center;gap:6px;padding:6px 10px;font-size:13px;line-height:1;color:#fff;background:#000000a6;border:1px solid rgba(255,255,255,.25);border-radius:4px;cursor:pointer;z-index:2}.video-unmute-affordance:hover{background:#000c}.video-unmute-affordance:focus-visible{outline:2px solid #4f9dff;outline-offset:2px}.video-unmute-icon{font-size:15px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
9718
9718
|
}
|
|
9719
9719
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: VideoLayerComponent, decorators: [{
|
|
9720
9720
|
type: Component,
|
|
9721
|
-
args: [{ selector: 'pw-video-layer', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\
|
|
9721
|
+
args: [{ selector: 'pw-video-layer', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"video-layer-container\"\n [class.interactive]=\"isKeyboardInteractive()\"\n [attr.tabindex]=\"isKeyboardInteractive() ? 0 : null\"\n [attr.role]=\"isKeyboardInteractive() ? 'button' : null\"\n [attr.aria-label]=\"ariaLabel || null\"\n (click)=\"onClick()\"\n (keydown)=\"onKeydown($event)\"\n (mouseenter)=\"onMouseEnter()\"\n (mouseleave)=\"onMouseLeave()\">\n <video\n #videoElement\n class=\"video-layer\"\n [src]=\"getVideoUrl()\"\n [poster]=\"getPosterUrl()\"\n [muted]=\"displayMuted\"\n [controls]=\"controls\"\n playsinline\n preload=\"metadata\"\n (loadedmetadata)=\"onLoadedMetadata()\"\n (error)=\"onError($event)\">\n Your browser does not support the video tag.\n </video>\n\n @if (showUnmuteButton) {\n <button\n type=\"button\"\n class=\"video-unmute-affordance\"\n aria-label=\"Unmute video\"\n (click)=\"onUnmuteClick($event)\">\n <span class=\"video-unmute-icon\" aria-hidden=\"true\">\uD83D\uDD0A</span>\n <span class=\"video-unmute-label\">Unmute</span>\n </button>\n }\n</div>\n", styles: [".video-layer-container{position:relative;width:100%;height:100%;background:#000}.video-layer{width:100%;height:100%;display:block;object-fit:contain}.video-layer::-webkit-media-controls-panel{background:linear-gradient(to bottom,transparent,rgba(0,0,0,.7))}.video-layer-container.interactive{cursor:pointer}.video-layer-container.interactive:focus-visible{outline:2px solid #4f9dff;outline-offset:2px}.video-unmute-affordance{position:absolute;bottom:12px;right:12px;display:inline-flex;align-items:center;gap:6px;padding:6px 10px;font-size:13px;line-height:1;color:#fff;background:#000000a6;border:1px solid rgba(255,255,255,.25);border-radius:4px;cursor:pointer;z-index:2}.video-unmute-affordance:hover{background:#000c}.video-unmute-affordance:focus-visible{outline:2px solid #4f9dff;outline-offset:2px}.video-unmute-icon{font-size:15px}\n"] }]
|
|
9722
9722
|
}], propDecorators: { src: [{
|
|
9723
9723
|
type: Input
|
|
9724
9724
|
}], reverseSrc: [{
|
|
@@ -10195,11 +10195,11 @@ class LayerRendererComponent {
|
|
|
10195
10195
|
return kind === 'hotspot' || kind === 'button';
|
|
10196
10196
|
}
|
|
10197
10197
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: LayerRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10198
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: LayerRendererComponent, isStandalone: true, selector: "pw-layer-renderer", inputs: { layer: "layer", locale: "locale", baseUrl: "baseUrl", targetWidth: "targetWidth", altText: "altText", viewMode: "viewMode", viewActive: "viewActive", reducedMotion: "reducedMotion", panelId: "panelId", placementId: "placementId", readingOrderIndex: "readingOrderIndex", placementZ: "placementZ", placementY: "placementY", placementX: "placementX" }, outputs: { layerClick: "layerClick" }, ngImport: i0, template: "<div \
|
|
10198
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: LayerRendererComponent, isStandalone: true, selector: "pw-layer-renderer", inputs: { layer: "layer", locale: "locale", baseUrl: "baseUrl", targetWidth: "targetWidth", altText: "altText", viewMode: "viewMode", viewActive: "viewActive", reducedMotion: "reducedMotion", panelId: "panelId", placementId: "placementId", readingOrderIndex: "readingOrderIndex", placementZ: "placementZ", placementY: "placementY", placementX: "placementX" }, outputs: { layerClick: "layerClick" }, ngImport: i0, template: "<div \n class=\"layer-wrapper\"\n [ngStyle]=\"getLayerStyles()\"\n [style.transform]=\"getTransformStyle()\"\n [class.interactive]=\"isInteractive()\"\n [attr.data-layer-id]=\"layer.id\"\n [attr.data-layer-kind]=\"layer.kind\"\n [tabindex]=\"isInteractive() ? 0 : -1\"\n (click)=\"isInteractive() ? onLayerClick($event) : null\"\n (keydown.enter)=\"isInteractive() ? onLayerClick($any($event)) : null\">\n\n <!-- Image Layer -->\n @if (layer.kind === 'image') {\n <img \n [src]=\"getImageSrc()\"\n [alt]=\"getAltText()\"\n class=\"layer-image\"\n [attr.loading]=\"viewActive ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"viewActive ? 'high' : null\" />\n }\n\n <!-- Video Layer -->\n @if (layer.kind === 'video') {\n <pw-video-layer\n class=\"layer-video\"\n [src]=\"getVideoSrc()\"\n [reverseSrc]=\"getVideoReverseSrc()\"\n [baseUrl]=\"baseUrl\"\n [poster]=\"getVideoPoster()\"\n [controls]=\"getVideoControls()\"\n [playMode]=\"getVideoPlayMode()\"\n [startMode]=\"getVideoStartMode()\"\n [muted]=\"getVideoMuted()\"\n [startAtMs]=\"getVideoStartAtMs()\"\n [loopFromMs]=\"getVideoLoopFromMs()\"\n [streaming]=\"getVideoStreaming()\"\n [viewMode]=\"viewMode\"\n [viewActive]=\"viewActive\"\n [reducedMotion]=\"reducedMotion\"\n [videoId]=\"layer.id\"\n [panelId]=\"panelId\"\n [assetId]=\"getVideoAssetId()\"\n [placementId]=\"placementId || panelId\"\n [readingOrderIndex]=\"readingOrderIndex\"\n [placementZ]=\"placementZ\"\n [placementY]=\"placementY\"\n [placementX]=\"placementX\"\n [ariaLabel]=\"getAltText()\">\n </pw-video-layer>\n }\n\n <!-- Text Layer -->\n @if (layer.kind === 'text') {\n <div \n class=\"layer-text\"\n [ngStyle]=\"getTextStyles()\"\n [innerHTML]=\"getTextContent()\">\n </div>\n }\n\n <!-- Hotspot Layer -->\n @if (layer.kind === 'hotspot') {\n <div \n class=\"layer-hotspot\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'Interactive hotspot'\">\n <div class=\"hotspot-indicator\"></div>\n </div>\n }\n\n <!-- Button Layer -->\n @if (layer.kind === 'button') {\n <button \n class=\"layer-button\"\n type=\"button\">\n {{ getTextContent() }}\n </button>\n }\n\n <!-- SVG Layer -->\n @if (layer.kind === 'svg') {\n <div \n class=\"layer-svg\"\n [innerHTML]=\"getSvgContent()\">\n </div>\n }\n\n <!-- Shape Layer -->\n @if (layer.kind === 'shape') {\n <div class=\"layer-shape\" [attr.data-shape]=\"getShapeType()\">\n <!-- Shape rendering would be implemented here -->\n </div>\n }\n\n <!-- Group Layer (container for nested layers) -->\n @if (layer.kind === 'group') {\n <div class=\"layer-group\">\n <!-- Nested layers would be rendered here -->\n </div>\n }\n</div>\n", styles: [".layer-wrapper{position:absolute;pointer-events:auto}.layer-wrapper.interactive{cursor:pointer}.layer-image,.layer-video{width:100%;height:100%;object-fit:contain;display:block}.layer-text{width:100%;height:100%;overflow:hidden;word-wrap:break-word}.layer-hotspot{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#0078d71a;border:2px solid rgba(0,120,215,.5);border-radius:4px;transition:all .2s ease}.layer-hotspot:hover{background:#0078d733;border-color:#0078d7cc}.hotspot-indicator{width:12px;height:12px;background:#0078d7;border-radius:50%;animation:pulse 2s infinite}@keyframes pulse{0%,to{transform:scale(1);opacity:1}50%{transform:scale(1.5);opacity:.5}}.layer-button{width:100%;height:100%;padding:12px 24px;background:#0078d7;color:#fff;border:none;border-radius:4px;font-size:16px;font-weight:500;cursor:pointer;transition:background .2s ease}.layer-button:hover{background:#005a9e}.layer-button:active{background:#004578}.layer-svg{width:100%;height:100%}.layer-svg svg{width:100%;height:100%}.layer-shape{width:100%;height:100%;background:#0000001a;border:1px solid rgba(0,0,0,.2)}.layer-group{width:100%;height:100%;position:relative}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: VideoLayerComponent, selector: "pw-video-layer", inputs: ["src", "reverseSrc", "baseUrl", "poster", "controls", "playMode", "startMode", "muted", "startAtMs", "loopFromMs", "streaming", "viewMode", "viewActive", "reducedMotion", "ariaLabel", "videoId", "panelId", "assetId", "placementId", "readingOrderIndex", "placementZ", "placementY", "placementX"], outputs: ["videoPlay", "videoPause", "videoEnd", "videoLoop", "passComplete", "videoError"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
10199
10199
|
}
|
|
10200
10200
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: LayerRendererComponent, decorators: [{
|
|
10201
10201
|
type: Component,
|
|
10202
|
-
args: [{ selector: 'pw-layer-renderer', imports: [CommonModule, VideoLayerComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div \
|
|
10202
|
+
args: [{ selector: 'pw-layer-renderer', imports: [CommonModule, VideoLayerComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div \n class=\"layer-wrapper\"\n [ngStyle]=\"getLayerStyles()\"\n [style.transform]=\"getTransformStyle()\"\n [class.interactive]=\"isInteractive()\"\n [attr.data-layer-id]=\"layer.id\"\n [attr.data-layer-kind]=\"layer.kind\"\n [tabindex]=\"isInteractive() ? 0 : -1\"\n (click)=\"isInteractive() ? onLayerClick($event) : null\"\n (keydown.enter)=\"isInteractive() ? onLayerClick($any($event)) : null\">\n\n <!-- Image Layer -->\n @if (layer.kind === 'image') {\n <img \n [src]=\"getImageSrc()\"\n [alt]=\"getAltText()\"\n class=\"layer-image\"\n [attr.loading]=\"viewActive ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"viewActive ? 'high' : null\" />\n }\n\n <!-- Video Layer -->\n @if (layer.kind === 'video') {\n <pw-video-layer\n class=\"layer-video\"\n [src]=\"getVideoSrc()\"\n [reverseSrc]=\"getVideoReverseSrc()\"\n [baseUrl]=\"baseUrl\"\n [poster]=\"getVideoPoster()\"\n [controls]=\"getVideoControls()\"\n [playMode]=\"getVideoPlayMode()\"\n [startMode]=\"getVideoStartMode()\"\n [muted]=\"getVideoMuted()\"\n [startAtMs]=\"getVideoStartAtMs()\"\n [loopFromMs]=\"getVideoLoopFromMs()\"\n [streaming]=\"getVideoStreaming()\"\n [viewMode]=\"viewMode\"\n [viewActive]=\"viewActive\"\n [reducedMotion]=\"reducedMotion\"\n [videoId]=\"layer.id\"\n [panelId]=\"panelId\"\n [assetId]=\"getVideoAssetId()\"\n [placementId]=\"placementId || panelId\"\n [readingOrderIndex]=\"readingOrderIndex\"\n [placementZ]=\"placementZ\"\n [placementY]=\"placementY\"\n [placementX]=\"placementX\"\n [ariaLabel]=\"getAltText()\">\n </pw-video-layer>\n }\n\n <!-- Text Layer -->\n @if (layer.kind === 'text') {\n <div \n class=\"layer-text\"\n [ngStyle]=\"getTextStyles()\"\n [innerHTML]=\"getTextContent()\">\n </div>\n }\n\n <!-- Hotspot Layer -->\n @if (layer.kind === 'hotspot') {\n <div \n class=\"layer-hotspot\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'Interactive hotspot'\">\n <div class=\"hotspot-indicator\"></div>\n </div>\n }\n\n <!-- Button Layer -->\n @if (layer.kind === 'button') {\n <button \n class=\"layer-button\"\n type=\"button\">\n {{ getTextContent() }}\n </button>\n }\n\n <!-- SVG Layer -->\n @if (layer.kind === 'svg') {\n <div \n class=\"layer-svg\"\n [innerHTML]=\"getSvgContent()\">\n </div>\n }\n\n <!-- Shape Layer -->\n @if (layer.kind === 'shape') {\n <div class=\"layer-shape\" [attr.data-shape]=\"getShapeType()\">\n <!-- Shape rendering would be implemented here -->\n </div>\n }\n\n <!-- Group Layer (container for nested layers) -->\n @if (layer.kind === 'group') {\n <div class=\"layer-group\">\n <!-- Nested layers would be rendered here -->\n </div>\n }\n</div>\n", styles: [".layer-wrapper{position:absolute;pointer-events:auto}.layer-wrapper.interactive{cursor:pointer}.layer-image,.layer-video{width:100%;height:100%;object-fit:contain;display:block}.layer-text{width:100%;height:100%;overflow:hidden;word-wrap:break-word}.layer-hotspot{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#0078d71a;border:2px solid rgba(0,120,215,.5);border-radius:4px;transition:all .2s ease}.layer-hotspot:hover{background:#0078d733;border-color:#0078d7cc}.hotspot-indicator{width:12px;height:12px;background:#0078d7;border-radius:50%;animation:pulse 2s infinite}@keyframes pulse{0%,to{transform:scale(1);opacity:1}50%{transform:scale(1.5);opacity:.5}}.layer-button{width:100%;height:100%;padding:12px 24px;background:#0078d7;color:#fff;border:none;border-radius:4px;font-size:16px;font-weight:500;cursor:pointer;transition:background .2s ease}.layer-button:hover{background:#005a9e}.layer-button:active{background:#004578}.layer-svg{width:100%;height:100%}.layer-svg svg{width:100%;height:100%}.layer-shape{width:100%;height:100%;background:#0000001a;border:1px solid rgba(0,0,0,.2)}.layer-group{width:100%;height:100%;position:relative}\n"] }]
|
|
10203
10203
|
}], propDecorators: { layer: [{
|
|
10204
10204
|
type: Input
|
|
10205
10205
|
}], locale: [{
|
|
@@ -10681,11 +10681,11 @@ class HotspotsOverlayComponent {
|
|
|
10681
10681
|
return Math.min(1, Math.max(0, v));
|
|
10682
10682
|
}
|
|
10683
10683
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: HotspotsOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10684
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: HotspotsOverlayComponent, isStandalone: true, selector: "pw-hotspots-overlay", inputs: { hotspots: "hotspots", locale: "locale", containerWidth: "containerWidth", containerHeight: "containerHeight", context: "context", interactive: "interactive", showIndicators: "showIndicators" }, outputs: { hotspotActivate: "hotspotActivate" }, usesOnChanges: true, ngImport: i0, template: "<svg\
|
|
10684
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: HotspotsOverlayComponent, isStandalone: true, selector: "pw-hotspots-overlay", inputs: { hotspots: "hotspots", locale: "locale", containerWidth: "containerWidth", containerHeight: "containerHeight", context: "context", interactive: "interactive", showIndicators: "showIndicators" }, outputs: { hotspotActivate: "hotspotActivate" }, usesOnChanges: true, ngImport: i0, template: "<svg\n class=\"hotspots-svg\"\n [class.non-interactive]=\"!interactive\"\n [attr.viewBox]=\"'0 0 ' + containerWidth + ' ' + containerHeight\"\n preserveAspectRatio=\"none\"\n aria-hidden=\"false\"\n>\n @for (h of visibleHotspots; track h.id) {\n <g\n class=\"hotspot-shape\"\n [class.indicator]=\"showIndicators\"\n [attr.role]=\"interactive ? 'button' : null\"\n [attr.tabindex]=\"interactive ? 0 : null\"\n [attr.aria-label]=\"resolveLabel(h)\"\n (click)=\"onShapeClick(h, $event)\"\n (keydown)=\"onKeyDown(h, $event)\"\n >\n @switch (h.shape.type) {\n @case ('rect') {\n <rect\n [attr.x]=\"rectAttrs($any(h.shape)).x\"\n [attr.y]=\"rectAttrs($any(h.shape)).y\"\n [attr.width]=\"rectAttrs($any(h.shape)).width\"\n [attr.height]=\"rectAttrs($any(h.shape)).height\"\n rx=\"6\"\n />\n }\n @case ('circle') {\n <circle\n [attr.cx]=\"circleAttrs($any(h.shape)).cx\"\n [attr.cy]=\"circleAttrs($any(h.shape)).cy\"\n [attr.r]=\"circleAttrs($any(h.shape)).r\"\n />\n }\n @case ('polygon') {\n <polygon [attr.points]=\"polygonPoints($any(h.shape))\" />\n }\n }\n </g>\n }\n</svg>\n", styles: [":host{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:15}.hotspots-svg{width:100%;height:100%;display:block}.hotspot-shape rect,.hotspot-shape circle,.hotspot-shape polygon{fill:#fff0;stroke:transparent;stroke-width:2;pointer-events:auto;cursor:pointer}.hotspots-svg.non-interactive .hotspot-shape rect,.hotspots-svg.non-interactive .hotspot-shape circle,.hotspots-svg.non-interactive .hotspot-shape polygon{pointer-events:none;cursor:default}.hotspot-shape:hover rect,.hotspot-shape:focus-visible rect,.hotspot-shape:hover circle,.hotspot-shape:focus-visible circle,.hotspot-shape:hover polygon,.hotspot-shape:focus-visible polygon{stroke:#ffffffd9;fill:#ffffff14}.hotspot-shape:focus{outline:none}.hotspot-shape.indicator rect,.hotspot-shape.indicator circle,.hotspot-shape.indicator polygon{animation:hotspot-pulse 2.4s ease-in-out infinite}@keyframes hotspot-pulse{0%,to{stroke:#fff0}50%{stroke:#ffffff73}}@media (prefers-reduced-motion: reduce){.hotspot-shape.indicator rect,.hotspot-shape.indicator circle,.hotspot-shape.indicator polygon{animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
10685
10685
|
}
|
|
10686
10686
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: HotspotsOverlayComponent, decorators: [{
|
|
10687
10687
|
type: Component,
|
|
10688
|
-
args: [{ selector: 'pw-hotspots-overlay', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<svg\
|
|
10688
|
+
args: [{ selector: 'pw-hotspots-overlay', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<svg\n class=\"hotspots-svg\"\n [class.non-interactive]=\"!interactive\"\n [attr.viewBox]=\"'0 0 ' + containerWidth + ' ' + containerHeight\"\n preserveAspectRatio=\"none\"\n aria-hidden=\"false\"\n>\n @for (h of visibleHotspots; track h.id) {\n <g\n class=\"hotspot-shape\"\n [class.indicator]=\"showIndicators\"\n [attr.role]=\"interactive ? 'button' : null\"\n [attr.tabindex]=\"interactive ? 0 : null\"\n [attr.aria-label]=\"resolveLabel(h)\"\n (click)=\"onShapeClick(h, $event)\"\n (keydown)=\"onKeyDown(h, $event)\"\n >\n @switch (h.shape.type) {\n @case ('rect') {\n <rect\n [attr.x]=\"rectAttrs($any(h.shape)).x\"\n [attr.y]=\"rectAttrs($any(h.shape)).y\"\n [attr.width]=\"rectAttrs($any(h.shape)).width\"\n [attr.height]=\"rectAttrs($any(h.shape)).height\"\n rx=\"6\"\n />\n }\n @case ('circle') {\n <circle\n [attr.cx]=\"circleAttrs($any(h.shape)).cx\"\n [attr.cy]=\"circleAttrs($any(h.shape)).cy\"\n [attr.r]=\"circleAttrs($any(h.shape)).r\"\n />\n }\n @case ('polygon') {\n <polygon [attr.points]=\"polygonPoints($any(h.shape))\" />\n }\n }\n </g>\n }\n</svg>\n", styles: [":host{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:15}.hotspots-svg{width:100%;height:100%;display:block}.hotspot-shape rect,.hotspot-shape circle,.hotspot-shape polygon{fill:#fff0;stroke:transparent;stroke-width:2;pointer-events:auto;cursor:pointer}.hotspots-svg.non-interactive .hotspot-shape rect,.hotspots-svg.non-interactive .hotspot-shape circle,.hotspots-svg.non-interactive .hotspot-shape polygon{pointer-events:none;cursor:default}.hotspot-shape:hover rect,.hotspot-shape:focus-visible rect,.hotspot-shape:hover circle,.hotspot-shape:focus-visible circle,.hotspot-shape:hover polygon,.hotspot-shape:focus-visible polygon{stroke:#ffffffd9;fill:#ffffff14}.hotspot-shape:focus{outline:none}.hotspot-shape.indicator rect,.hotspot-shape.indicator circle,.hotspot-shape.indicator polygon{animation:hotspot-pulse 2.4s ease-in-out infinite}@keyframes hotspot-pulse{0%,to{stroke:#fff0}50%{stroke:#ffffff73}}@media (prefers-reduced-motion: reduce){.hotspot-shape.indicator rect,.hotspot-shape.indicator circle,.hotspot-shape.indicator polygon{animation:none}}\n"] }]
|
|
10689
10689
|
}], propDecorators: { hotspots: [{
|
|
10690
10690
|
type: Input
|
|
10691
10691
|
}], locale: [{
|
|
@@ -12228,11 +12228,11 @@ class ViewportComponent {
|
|
|
12228
12228
|
console.groupEnd();
|
|
12229
12229
|
}
|
|
12230
12230
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ViewportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
12231
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: ViewportComponent, isStandalone: true, selector: "pw-viewport", inputs: { panel: "panel", page: "page", panels: "panels", viewMode: "viewMode", currentPanelId: "currentPanelId", locale: "locale", variableContext: "variableContext", panX: "panX", panY: "panY", zoom: "zoom", reducedMotion: "reducedMotion", transition: "transition", interactive: "interactive", showOverflowArrows: "showOverflowArrows", enableViewportCulling: "enableViewportCulling", enableLazyLoading: "enableLazyLoading", workBalloonConfig: "workBalloonConfig", characters: "characters", speechEnabled: "speechEnabled", graph: "graph", preload: "preload" }, outputs: { viewportClick: "viewportClick", performanceMetrics: "performanceMetrics", transformChange: "transformChange", layerClick: "layerClick", swipe: "swipe", navigatePrevious: "navigatePrevious", navigateNext: "navigateNext", panelFocus: "panelFocus", hotspotActivate: "hotspotActivate", deadClick: "deadClick" }, host: { listeners: { "window:resize": "onWindowResize()", "mousedown": "onMouseDown($event)", "document:mousemove": "onMouseMove($event)", "document:mouseup": "onMouseUp($event)", "wheel": "onWheel($event)", "touchstart": "onTouchStart($event)", "touchmove": "onTouchMove($event)", "touchend": "onTouchEnd($event)", "touchcancel": "onTouchEnd($event)", "keydown.tab": "onKeydownTab($event)", "keydown.arrowRight": "onKeydownNext($event)", "keydown.arrowDown": "onKeydownNext($event)", "keydown.arrowLeft": "onKeydownPrevious($event)", "keydown.arrowUp": "onKeydownPrevious($event)" } }, usesOnChanges: true, ngImport: i0, template: "<div \r\n class=\"viewport-container\"\r\n [class]=\"getViewModeClass()\"\r\n [class.reduced-motion]=\"reducedMotion\"\r\n [class.interactive]=\"interactive\"\r\n role=\"region\"\r\n [attr.aria-label]=\"viewMode === 'page' ? 'Page viewport' : 'Panel viewport'\"\r\n tabindex=\"0\"\r\n (click)=\"onViewportClick($event)\"\r\n (keydown.enter)=\"onKeyboardActivate($event)\"\r\n (keydown.space)=\"onKeyboardActivate($event)\"\r\n (mousemove)=\"onViewportMouseMove($event)\"\r\n (mouseleave)=\"onViewportMouseLeave()\">\r\n \r\n <!-- Panel View -->\r\n @if (viewMode === 'panel') {\r\n <div\r\n class=\"viewport-content\"\r\n [style.transform]=\"getTransformStyle()\"\r\n [style.transition]=\"reducedMotion ? 'none' : 'transform 0.3s ease'\"\r\n [style.--pw-t-duration]=\"transitionDuration\"\r\n [style.--pw-t-easing]=\"transitionEasing\">\r\n\r\n <!-- Outgoing panel during a manifest edge transition -->\r\n @if (leavingPanel) {\r\n <div [class]=\"'t-frame-leave ' + leaveAnimationClass\" aria-hidden=\"true\">\r\n <div class=\"panel-container\">\r\n @for (layer of leavingPanel.layers; track layer.id) {\r\n <pw-layer-renderer\r\n [layer]=\"layer\"\r\n [locale]=\"locale\"\r\n [viewMode]=\"viewMode\"\r\n [viewActive]=\"false\"\r\n [reducedMotion]=\"reducedMotion\"\r\n [targetWidth]=\"targetWidthFor(leavingPanelId)\"\r\n [panelId]=\"leavingPanelId\">\r\n </pw-layer-renderer>\r\n }\r\n\r\n @if (speechEnabled && leavingPanel.speechBubbles && leavingPanel.speechBubbles.length > 0) {\r\n <pw-speech-bubbles\r\n [bubbles]=\"leavingPanel.speechBubbles\"\r\n [locale]=\"locale\"\r\n [containerWidth]=\"getContainerWidth()\"\r\n [containerHeight]=\"getContainerHeight()\"\r\n [readingScale]=\"getReadingScale()\"\r\n [workBalloonConfig]=\"workBalloonConfig\"\r\n [characters]=\"characters\">\r\n </pw-speech-bubbles>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <div [class]=\"'t-frame ' + enterAnimationClass\">\r\n @if (panel) {\r\n <div class=\"panel-container\" tabindex=\"0\" (click)=\"onPanelContainerClick($event, currentPanelId)\" (keydown.enter)=\"onPanelContainerClick($any($event), currentPanelId)\">\r\n <!-- Render layers using LayerRenderer -->\r\n @for (layer of panel.layers; track layer.id) {\r\n <pw-layer-renderer\r\n [layer]=\"layer\"\r\n [locale]=\"locale\"\r\n [viewMode]=\"viewMode\"\r\n [viewActive]=\"true\"\r\n [reducedMotion]=\"reducedMotion\"\r\n [targetWidth]=\"targetWidthFor(currentPanelId)\"\r\n [panelId]=\"currentPanelId || ''\"\r\n (layerClick)=\"layerClick.emit($event)\">\r\n </pw-layer-renderer>\r\n }\r\n\r\n <!-- Interactive hotspots overlay (below speech bubbles) -->\r\n @if (interactive && panel.hotspots && panel.hotspots.length > 0) {\r\n <pw-hotspots-overlay\r\n [hotspots]=\"panel.hotspots\"\r\n [locale]=\"locale\"\r\n [containerWidth]=\"getContainerWidth()\"\r\n [containerHeight]=\"getContainerHeight()\"\r\n [context]=\"variableContext\"\r\n (hotspotActivate)=\"onHotspotActivate($event, currentPanelId)\">\r\n </pw-hotspots-overlay>\r\n }\r\n\r\n <!-- Speech bubbles overlay for panel view -->\r\n @if (speechEnabled && panel.speechBubbles && panel.speechBubbles.length > 0) {\r\n <pw-speech-bubbles\r\n [bubbles]=\"panel.speechBubbles\"\r\n [locale]=\"locale\"\r\n [containerWidth]=\"getContainerWidth()\"\r\n [containerHeight]=\"getContainerHeight()\"\r\n [readingScale]=\"getReadingScale()\"\r\n [workBalloonConfig]=\"workBalloonConfig\"\r\n [characters]=\"characters\">\r\n </pw-speech-bubbles>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"no-panel\">\r\n <p>No panel to display</p>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Page View -->\r\n @if (viewMode === 'page' && page) {\r\n <div class=\"viewport-page\">\r\n <div class=\"page-stage\">\r\n <!-- Outgoing page during a Page.transitions in/out change -->\r\n @if (leavingPage) {\r\n <div\r\n [class]=\"'pt-frame-leave ' + pageLeaveClass + (pageLeaveAbove ? ' pt-frame-leave--above' : '')\"\r\n [style.--pw-t-duration]=\"pageLeaveDuration\"\r\n [style.--pw-t-easing]=\"pageLeaveEasing\"\r\n aria-hidden=\"true\">\r\n <div class=\"page-canvas\">\r\n @for (placement of getLeavingPagePlacements(); track placement.panelId) {\r\n <div\r\n class=\"panel-container\"\r\n [style.left.%]=\"toPercent(placement.x)\"\r\n [style.top.%]=\"toPercent(placement.y)\"\r\n [style.width.%]=\"toPercent(placement.w)\"\r\n [style.height.%]=\"toPercent(placement.h)\"\r\n [style.z-index]=\"placement.z || 0\"\r\n [style.transform]=\"getPanelTransform(placement)\"\r\n [style.transform-origin]=\"getTransformOrigin(placement)\">\r\n\r\n @if (getLeavingPanel(placement.panelId); as panelData) {\r\n <div class=\"panel-content\"\r\n [style.clip-path]=\"getPanelContentClipPath(placement)\"\r\n [style.transform]=\"getPanelContentTransform(placement)\">\r\n @for (layer of panelData.layers; track layer.id) {\r\n <pw-layer-renderer\r\n [layer]=\"layer\"\r\n [locale]=\"locale\"\r\n [viewMode]=\"viewMode\"\r\n [viewActive]=\"false\"\r\n [reducedMotion]=\"reducedMotion\"\r\n [targetWidth]=\"targetWidthFor(placement.panelId)\"\r\n [panelId]=\"placement.panelId\"\r\n [placementId]=\"placement.panelId\">\r\n </pw-layer-renderer>\r\n }\r\n\r\n @if (speechEnabled && panelData.speechBubbles && panelData.speechBubbles.length > 0) {\r\n <pw-speech-bubbles\r\n [bubbles]=\"panelData.speechBubbles\"\r\n [locale]=\"locale\"\r\n [containerWidth]=\"getPagePanelWidth(placement)\"\r\n [containerHeight]=\"getPagePanelHeight(placement)\"\r\n [readingScale]=\"getReadingScale()\"\r\n [workBalloonConfig]=\"workBalloonConfig\"\r\n [characters]=\"characters\">\r\n </pw-speech-bubbles>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <div\r\n [class]=\"'pt-frame ' + pageEnterClass\"\r\n [style.--pw-t-duration]=\"pageEnterDuration\"\r\n [style.--pw-t-easing]=\"pageEnterEasing\">\r\n <div\r\n class=\"page-canvas\"\r\n [attr.tabindex]=\"-1\"\r\n (click)=\"onPanelClickWithFocus($event)\"\r\n (keydown.enter)=\"onCanvasKeyboardActivate($event)\"\r\n (keydown.space)=\"onCanvasKeyboardActivate($event)\">\r\n \r\n @for (placement of getSortedPanels(); track placement.panelId) {\r\n @if (isPanelVisible(placement)) {\r\n <div \r\n class=\"panel-container\"\r\n [class.panel-focused]=\"isPanelFocused(placement.panelId)\"\r\n [style.left.%]=\"toPercent(placement.x)\"\r\n [style.top.%]=\"toPercent(placement.y)\"\r\n [style.width.%]=\"toPercent(placement.w)\"\r\n [style.height.%]=\"toPercent(placement.h)\"\r\n [style.z-index]=\"placement.z || 0\"\r\n [style.transform]=\"getPanelTransform(placement)\"\r\n [style.transform-origin]=\"getTransformOrigin(placement)\"\r\n [attr.data-panel-id]=\"placement.panelId\"\r\n [attr.tabindex]=\"panelHasHotspots(placement.panelId) ? null : 0\"\r\n [attr.role]=\"panelHasHotspots(placement.panelId) ? null : 'button'\"\r\n [attr.aria-label]=\"panelHasHotspots(placement.panelId) ? null : 'Panel ' + placement.panelId\"\r\n [attr.loading]=\"getImageLoadingStrategy()\"\r\n (click)=\"onPanelClick($event, placement.panelId)\"\r\n (keydown.enter)=\"onPanelKeyboardActivate($event, placement.panelId)\"\r\n (keydown.space)=\"onPanelKeyboardActivate($event, placement.panelId)\">\r\n \r\n @if (getPanel(placement.panelId); as panelData) {\r\n <div class=\"panel-content\"\r\n [style.clip-path]=\"getPanelContentClipPath(placement)\"\r\n [style.transform]=\"getPanelContentTransform(placement)\"\r\n tabindex=\"0\"\r\n (click)=\"onPanelContainerClick($event, placement.panelId)\"\r\n (keydown.enter)=\"onPanelContainerClick($any($event), placement.panelId)\">\r\n <!-- Render panel layers -->\r\n @for (layer of panelData.layers; track layer.id) {\r\n <pw-layer-renderer\r\n [layer]=\"layer\"\r\n [locale]=\"locale\"\r\n [altText]=\"getAltText(layer)\"\r\n [viewMode]=\"viewMode\"\r\n [reducedMotion]=\"reducedMotion\"\r\n [targetWidth]=\"targetWidthFor(placement.panelId)\"\r\n [panelId]=\"placement.panelId\"\r\n [placementId]=\"placement.panelId\"\r\n [readingOrderIndex]=\"readingOrderIndexOf(placement.panelId)\"\r\n [placementZ]=\"placement.z || 0\"\r\n [placementY]=\"placement.y\"\r\n [placementX]=\"placement.x\"\r\n (layerClick)=\"layerClick.emit($event)\">\r\n </pw-layer-renderer>\r\n }\r\n\r\n <!-- Interactive hotspots overlay for page view panels -->\r\n @if (interactive && panelData.hotspots && panelData.hotspots.length > 0) {\r\n <pw-hotspots-overlay\r\n [hotspots]=\"panelData.hotspots\"\r\n [locale]=\"locale\"\r\n [containerWidth]=\"getPagePanelWidth(placement)\"\r\n [containerHeight]=\"getPagePanelHeight(placement)\"\r\n [context]=\"variableContext\"\r\n (hotspotActivate)=\"onHotspotActivate($event, placement.panelId)\">\r\n </pw-hotspots-overlay>\r\n }\r\n\r\n <!-- Speech bubbles overlay for page view panels -->\r\n @if (speechEnabled && panelData.speechBubbles && panelData.speechBubbles.length > 0) {\r\n <pw-speech-bubbles\r\n [bubbles]=\"panelData.speechBubbles\"\r\n [locale]=\"locale\"\r\n [containerWidth]=\"getPagePanelWidth(placement)\"\r\n [containerHeight]=\"getPagePanelHeight(placement)\"\r\n [readingScale]=\"getReadingScale()\"\r\n [workBalloonConfig]=\"workBalloonConfig\"\r\n [characters]=\"characters\">\r\n </pw-speech-bubbles>\r\n }\r\n </div>\r\n }\r\n \r\n <!-- Focus indicator for rotated panels -->\r\n @if (isPanelFocused(placement.panelId)) {\r\n <div class=\"focus-rect\" aria-hidden=\"true\"></div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (viewMode === 'page' && !page) {\r\n <div class=\"no-panel\">\r\n <p>No page to display</p>\r\n </div>\r\n }\r\n\r\n <!-- Overflow navigation arrows -->\r\n @if (showOverflowArrows && panel) {\r\n @if (hasOverflowLeft) {\r\n <button \r\n class=\"overflow-arrow overflow-left\"\r\n type=\"button\"\r\n aria-label=\"Navigate left\"\r\n (click)=\"navigateLeft()\">\r\n <pw-icon name=\"lucideChevronLeft\" />\r\n </button>\r\n }\r\n\r\n @if (hasOverflowRight) {\r\n <button \r\n class=\"overflow-arrow overflow-right\"\r\n type=\"button\"\r\n aria-label=\"Navigate right\"\r\n (click)=\"navigateRight()\">\r\n <pw-icon name=\"lucideChevronRight\" />\r\n </button>\r\n }\r\n\r\n @if (hasOverflowTop) {\r\n <button \r\n class=\"overflow-arrow overflow-top\"\r\n type=\"button\"\r\n aria-label=\"Navigate up\"\r\n (click)=\"navigateUp()\">\r\n <pw-icon name=\"lucideChevronUp\" />\r\n </button>\r\n }\r\n\r\n @if (hasOverflowBottom) {\r\n <button \r\n class=\"overflow-arrow overflow-bottom\"\r\n type=\"button\"\r\n aria-label=\"Navigate down\"\r\n (click)=\"navigateDown()\">\r\n <pw-icon name=\"lucideChevronDown\" />\r\n </button>\r\n }\r\n }\r\n\r\n <!-- Navigation arrows (hover zones) -->\r\n @if (viewMode === 'panel') {\r\n @if (showLeftArrow) {\r\n <button \r\n class=\"nav-arrow nav-arrow-left\"\r\n type=\"button\"\r\n aria-label=\"Previous panel\"\r\n (click)=\"onNavigatePrevious(); $event.stopPropagation()\">\r\n <span class=\"arrow-icon\"><pw-icon name=\"lucideChevronLeft\" /></span>\r\n </button>\r\n }\r\n\r\n @if (showRightArrow) {\r\n <button \r\n class=\"nav-arrow nav-arrow-right\"\r\n type=\"button\"\r\n aria-label=\"Next panel\"\r\n (click)=\"onNavigateNext(); $event.stopPropagation()\">\r\n <span class=\"arrow-icon\"><pw-icon name=\"lucideChevronRight\" /></span>\r\n </button>\r\n }\r\n }\r\n</div>\r\n", styles: [".viewport-container{position:relative;width:100%;height:100%;overflow:hidden;background-color:#f5f5f5;cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none}.viewport-container:active{cursor:grabbing}.viewport-container.interactive{cursor:default}.viewport-content{position:absolute;top:50%;left:50%;transform-origin:center center;will-change:transform}.viewport-content.reduced-motion{transition:none!important}.panel-container{position:relative;background:#fff;box-shadow:0 2px 8px #0000001a;width:1280px;height:720px;max-width:100%;max-height:100%}.t-frame{position:relative;z-index:1}.t-frame-leave{position:absolute;inset:0;z-index:0;pointer-events:none}.t-fade-in{animation:pw-t-fade-in var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-zoom-in{animation:pw-t-zoom-in var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-right{animation:pw-t-from-right var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-left{animation:pw-t-from-left var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-bottom{animation:pw-t-from-bottom var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-top{animation:pw-t-from-top var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-left{animation:pw-t-to-left var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-right{animation:pw-t-to-right var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-top{animation:pw-t-to-top var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-bottom{animation:pw-t-to-bottom var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}@keyframes pw-t-fade-in{0%{opacity:0}to{opacity:1}}@keyframes pw-t-zoom-in{0%{opacity:0;transform:scale(.94)}to{opacity:1;transform:scale(1)}}@keyframes pw-t-from-right{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes pw-t-from-left{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes pw-t-from-bottom{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes pw-t-from-top{0%{transform:translateY(-100%)}to{transform:translateY(0)}}@keyframes pw-t-to-left{0%{transform:translate(0)}to{transform:translate(-100%)}}@keyframes pw-t-to-right{0%{transform:translate(0)}to{transform:translate(100%)}}@keyframes pw-t-to-top{0%{transform:translateY(0)}to{transform:translateY(-100%)}}@keyframes pw-t-to-bottom{0%{transform:translateY(0)}to{transform:translateY(100%)}}.layer{position:absolute;top:0;left:0}.layer-placeholder{padding:20px;background:#0078d71a;border:1px dashed rgba(0,120,215,.3);color:#0078d7;font-size:14px}.no-panel{display:flex;align-items:center;justify-content:center;min-width:400px;min-height:300px;color:#666}.reduced-motion *{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}.overflow-arrow{position:absolute;z-index:100;background:#0009;color:#fff;border:none;border-radius:4px;width:40px;height:40px;font-size:20px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;box-shadow:0 2px 8px #0000004d}.overflow-arrow:hover{background:#000c;transform:scale(1.1)}.overflow-arrow:active{transform:scale(.95)}.overflow-left{left:20px;top:50%;transform:translateY(-50%)}.overflow-left:hover{transform:translateY(-50%) scale(1.1)}.overflow-right{right:20px;top:50%;transform:translateY(-50%)}.overflow-right:hover{transform:translateY(-50%) scale(1.1)}.overflow-top{top:20px;left:50%;transform:translate(-50%)}.overflow-top:hover{transform:translate(-50%) scale(1.1)}.overflow-bottom{bottom:20px;left:50%;transform:translate(-50%)}.overflow-bottom:hover{transform:translate(-50%) scale(1.1)}.viewport-page{width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:auto;padding:16px;box-sizing:border-box}.page-canvas{position:relative;width:100%;max-width:1400px;aspect-ratio:16 / 9;background:#1a1a1a;border-radius:4px;overflow:hidden}.page-stage{position:relative;width:100%;max-width:1400px;aspect-ratio:16 / 9;overflow:hidden;border-radius:4px}.page-stage .page-canvas{max-width:none;height:100%;aspect-ratio:auto}.pt-frame{position:relative;z-index:1;width:100%;height:100%}.pt-frame-leave{position:absolute;inset:0;z-index:0;pointer-events:none}.pt-frame-leave--above{z-index:2}.pt-frame.t-fade-in,.pt-frame.t-zoom-in,.pt-frame.t-from-right,.pt-frame.t-from-left,.pt-frame.t-from-bottom,.pt-frame.t-from-top{will-change:transform,opacity}.t-fade-out{animation:pw-t-fade-out var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-zoom-out{animation:pw-t-zoom-out var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}@keyframes pw-t-fade-out{0%{opacity:1}to{opacity:0}}@keyframes pw-t-zoom-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(1.06)}}.viewport-page .panel-container{position:absolute;overflow:hidden;background:#000;border:1px solid rgba(255,255,255,.1);border-radius:2px;transition:transform .2s ease,border-color .2s ease,box-shadow .2s ease;transform-origin:center center;outline:none}.viewport-page .panel-container:hover{border-color:#ffffff4d;cursor:pointer}.viewport-page .panel-container.panel-focused{border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d74d,0 4px 12px #0006;z-index:9999!important}.focus-rect{position:absolute;inset:-4px;border:3px solid rgba(0,120,215,.9);border-radius:4px;pointer-events:none;animation:focusPulse 1.5s ease-in-out infinite}@keyframes focusPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.7;transform:scale(1.02)}}@media (prefers-reduced-motion: reduce){.focus-rect{animation:none}.viewport-page .panel-container{transition:none}}.viewport-page .panel-content{position:relative;width:100%;height:100%}@media (max-width: 1024px){.page-canvas,.page-stage{max-width:900px}}@media (max-width: 768px){.viewport-page{padding:8px}.page-canvas,.page-stage{max-width:100%}}.nav-arrow{position:absolute;top:50%;transform:translateY(-50%);width:60px;height:120px;background:#0006;border:none;color:#fff;font-size:48px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s ease;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);z-index:50;animation:fadeInArrow .2s ease}.nav-arrow:hover{background:#0009;width:70px}.nav-arrow:active{background:#000000b3;transform:translateY(-50%) scale(.95)}.nav-arrow-left{left:0;border-radius:0 8px 8px 0}.nav-arrow-right{right:0;border-radius:8px 0 0 8px}.arrow-icon{font-size:48px;line-height:1;font-weight:300}@keyframes fadeInArrow{0%{opacity:0}to{opacity:1}}@media (prefers-reduced-motion: reduce){.nav-arrow{animation:none;transition:none}}@media (max-width: 768px){.nav-arrow{width:50px;height:100px;font-size:36px}.nav-arrow:hover{width:55px}.arrow-icon{font-size:36px}}\n"], dependencies: [{ kind: "component", type: LayerRendererComponent, selector: "pw-layer-renderer", inputs: ["layer", "locale", "baseUrl", "targetWidth", "altText", "viewMode", "viewActive", "reducedMotion", "panelId", "placementId", "readingOrderIndex", "placementZ", "placementY", "placementX"], outputs: ["layerClick"] }, { kind: "component", type: SpeechBubblesComponent, selector: "pw-speech-bubbles", inputs: ["bubbles", "locale", "containerWidth", "containerHeight", "readingScale", "workBalloonConfig", "characters"], outputs: ["bubbleClick", "bubbleAudioPlay"] }, { kind: "component", type: HotspotsOverlayComponent, selector: "pw-hotspots-overlay", inputs: ["hotspots", "locale", "containerWidth", "containerHeight", "context", "interactive", "showIndicators"], outputs: ["hotspotActivate"] }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
12231
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: ViewportComponent, isStandalone: true, selector: "pw-viewport", inputs: { panel: "panel", page: "page", panels: "panels", viewMode: "viewMode", currentPanelId: "currentPanelId", locale: "locale", variableContext: "variableContext", panX: "panX", panY: "panY", zoom: "zoom", reducedMotion: "reducedMotion", transition: "transition", interactive: "interactive", showOverflowArrows: "showOverflowArrows", enableViewportCulling: "enableViewportCulling", enableLazyLoading: "enableLazyLoading", workBalloonConfig: "workBalloonConfig", characters: "characters", speechEnabled: "speechEnabled", graph: "graph", preload: "preload" }, outputs: { viewportClick: "viewportClick", performanceMetrics: "performanceMetrics", transformChange: "transformChange", layerClick: "layerClick", swipe: "swipe", navigatePrevious: "navigatePrevious", navigateNext: "navigateNext", panelFocus: "panelFocus", hotspotActivate: "hotspotActivate", deadClick: "deadClick" }, host: { listeners: { "window:resize": "onWindowResize()", "mousedown": "onMouseDown($event)", "document:mousemove": "onMouseMove($event)", "document:mouseup": "onMouseUp($event)", "wheel": "onWheel($event)", "touchstart": "onTouchStart($event)", "touchmove": "onTouchMove($event)", "touchend": "onTouchEnd($event)", "touchcancel": "onTouchEnd($event)", "keydown.tab": "onKeydownTab($event)", "keydown.arrowRight": "onKeydownNext($event)", "keydown.arrowDown": "onKeydownNext($event)", "keydown.arrowLeft": "onKeydownPrevious($event)", "keydown.arrowUp": "onKeydownPrevious($event)" } }, usesOnChanges: true, ngImport: i0, template: "<div \n class=\"viewport-container\"\n [class]=\"getViewModeClass()\"\n [class.reduced-motion]=\"reducedMotion\"\n [class.interactive]=\"interactive\"\n role=\"region\"\n [attr.aria-label]=\"viewMode === 'page' ? 'Page viewport' : 'Panel viewport'\"\n tabindex=\"0\"\n (click)=\"onViewportClick($event)\"\n (keydown.enter)=\"onKeyboardActivate($event)\"\n (keydown.space)=\"onKeyboardActivate($event)\"\n (mousemove)=\"onViewportMouseMove($event)\"\n (mouseleave)=\"onViewportMouseLeave()\">\n \n <!-- Panel View -->\n @if (viewMode === 'panel') {\n <div\n class=\"viewport-content\"\n [style.transform]=\"getTransformStyle()\"\n [style.transition]=\"reducedMotion ? 'none' : 'transform 0.3s ease'\"\n [style.--pw-t-duration]=\"transitionDuration\"\n [style.--pw-t-easing]=\"transitionEasing\">\n\n <!-- Outgoing panel during a manifest edge transition -->\n @if (leavingPanel) {\n <div [class]=\"'t-frame-leave ' + leaveAnimationClass\" aria-hidden=\"true\">\n <div class=\"panel-container\">\n @for (layer of leavingPanel.layers; track layer.id) {\n <pw-layer-renderer\n [layer]=\"layer\"\n [locale]=\"locale\"\n [viewMode]=\"viewMode\"\n [viewActive]=\"false\"\n [reducedMotion]=\"reducedMotion\"\n [targetWidth]=\"targetWidthFor(leavingPanelId)\"\n [panelId]=\"leavingPanelId\">\n </pw-layer-renderer>\n }\n\n @if (speechEnabled && leavingPanel.speechBubbles && leavingPanel.speechBubbles.length > 0) {\n <pw-speech-bubbles\n [bubbles]=\"leavingPanel.speechBubbles\"\n [locale]=\"locale\"\n [containerWidth]=\"getContainerWidth()\"\n [containerHeight]=\"getContainerHeight()\"\n [readingScale]=\"getReadingScale()\"\n [workBalloonConfig]=\"workBalloonConfig\"\n [characters]=\"characters\">\n </pw-speech-bubbles>\n }\n </div>\n </div>\n }\n\n <div [class]=\"'t-frame ' + enterAnimationClass\">\n @if (panel) {\n <div class=\"panel-container\" tabindex=\"0\" (click)=\"onPanelContainerClick($event, currentPanelId)\" (keydown.enter)=\"onPanelContainerClick($any($event), currentPanelId)\">\n <!-- Render layers using LayerRenderer -->\n @for (layer of panel.layers; track layer.id) {\n <pw-layer-renderer\n [layer]=\"layer\"\n [locale]=\"locale\"\n [viewMode]=\"viewMode\"\n [viewActive]=\"true\"\n [reducedMotion]=\"reducedMotion\"\n [targetWidth]=\"targetWidthFor(currentPanelId)\"\n [panelId]=\"currentPanelId || ''\"\n (layerClick)=\"layerClick.emit($event)\">\n </pw-layer-renderer>\n }\n\n <!-- Interactive hotspots overlay (below speech bubbles) -->\n @if (interactive && panel.hotspots && panel.hotspots.length > 0) {\n <pw-hotspots-overlay\n [hotspots]=\"panel.hotspots\"\n [locale]=\"locale\"\n [containerWidth]=\"getContainerWidth()\"\n [containerHeight]=\"getContainerHeight()\"\n [context]=\"variableContext\"\n (hotspotActivate)=\"onHotspotActivate($event, currentPanelId)\">\n </pw-hotspots-overlay>\n }\n\n <!-- Speech bubbles overlay for panel view -->\n @if (speechEnabled && panel.speechBubbles && panel.speechBubbles.length > 0) {\n <pw-speech-bubbles\n [bubbles]=\"panel.speechBubbles\"\n [locale]=\"locale\"\n [containerWidth]=\"getContainerWidth()\"\n [containerHeight]=\"getContainerHeight()\"\n [readingScale]=\"getReadingScale()\"\n [workBalloonConfig]=\"workBalloonConfig\"\n [characters]=\"characters\">\n </pw-speech-bubbles>\n }\n </div>\n } @else {\n <div class=\"no-panel\">\n <p>No panel to display</p>\n </div>\n }\n </div>\n </div>\n }\n \n <!-- Page View -->\n @if (viewMode === 'page' && page) {\n <div class=\"viewport-page\">\n <div class=\"page-stage\">\n <!-- Outgoing page during a Page.transitions in/out change -->\n @if (leavingPage) {\n <div\n [class]=\"'pt-frame-leave ' + pageLeaveClass + (pageLeaveAbove ? ' pt-frame-leave--above' : '')\"\n [style.--pw-t-duration]=\"pageLeaveDuration\"\n [style.--pw-t-easing]=\"pageLeaveEasing\"\n aria-hidden=\"true\">\n <div class=\"page-canvas\">\n @for (placement of getLeavingPagePlacements(); track placement.panelId) {\n <div\n class=\"panel-container\"\n [style.left.%]=\"toPercent(placement.x)\"\n [style.top.%]=\"toPercent(placement.y)\"\n [style.width.%]=\"toPercent(placement.w)\"\n [style.height.%]=\"toPercent(placement.h)\"\n [style.z-index]=\"placement.z || 0\"\n [style.transform]=\"getPanelTransform(placement)\"\n [style.transform-origin]=\"getTransformOrigin(placement)\">\n\n @if (getLeavingPanel(placement.panelId); as panelData) {\n <div class=\"panel-content\"\n [style.clip-path]=\"getPanelContentClipPath(placement)\"\n [style.transform]=\"getPanelContentTransform(placement)\">\n @for (layer of panelData.layers; track layer.id) {\n <pw-layer-renderer\n [layer]=\"layer\"\n [locale]=\"locale\"\n [viewMode]=\"viewMode\"\n [viewActive]=\"false\"\n [reducedMotion]=\"reducedMotion\"\n [targetWidth]=\"targetWidthFor(placement.panelId)\"\n [panelId]=\"placement.panelId\"\n [placementId]=\"placement.panelId\">\n </pw-layer-renderer>\n }\n\n @if (speechEnabled && panelData.speechBubbles && panelData.speechBubbles.length > 0) {\n <pw-speech-bubbles\n [bubbles]=\"panelData.speechBubbles\"\n [locale]=\"locale\"\n [containerWidth]=\"getPagePanelWidth(placement)\"\n [containerHeight]=\"getPagePanelHeight(placement)\"\n [readingScale]=\"getReadingScale()\"\n [workBalloonConfig]=\"workBalloonConfig\"\n [characters]=\"characters\">\n </pw-speech-bubbles>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n\n <div\n [class]=\"'pt-frame ' + pageEnterClass\"\n [style.--pw-t-duration]=\"pageEnterDuration\"\n [style.--pw-t-easing]=\"pageEnterEasing\">\n <div\n class=\"page-canvas\"\n [attr.tabindex]=\"-1\"\n (click)=\"onPanelClickWithFocus($event)\"\n (keydown.enter)=\"onCanvasKeyboardActivate($event)\"\n (keydown.space)=\"onCanvasKeyboardActivate($event)\">\n \n @for (placement of getSortedPanels(); track placement.panelId) {\n @if (isPanelVisible(placement)) {\n <div \n class=\"panel-container\"\n [class.panel-focused]=\"isPanelFocused(placement.panelId)\"\n [style.left.%]=\"toPercent(placement.x)\"\n [style.top.%]=\"toPercent(placement.y)\"\n [style.width.%]=\"toPercent(placement.w)\"\n [style.height.%]=\"toPercent(placement.h)\"\n [style.z-index]=\"placement.z || 0\"\n [style.transform]=\"getPanelTransform(placement)\"\n [style.transform-origin]=\"getTransformOrigin(placement)\"\n [attr.data-panel-id]=\"placement.panelId\"\n [attr.tabindex]=\"panelHasHotspots(placement.panelId) ? null : 0\"\n [attr.role]=\"panelHasHotspots(placement.panelId) ? null : 'button'\"\n [attr.aria-label]=\"panelHasHotspots(placement.panelId) ? null : 'Panel ' + placement.panelId\"\n [attr.loading]=\"getImageLoadingStrategy()\"\n (click)=\"onPanelClick($event, placement.panelId)\"\n (keydown.enter)=\"onPanelKeyboardActivate($event, placement.panelId)\"\n (keydown.space)=\"onPanelKeyboardActivate($event, placement.panelId)\">\n \n @if (getPanel(placement.panelId); as panelData) {\n <div class=\"panel-content\"\n [style.clip-path]=\"getPanelContentClipPath(placement)\"\n [style.transform]=\"getPanelContentTransform(placement)\"\n tabindex=\"0\"\n (click)=\"onPanelContainerClick($event, placement.panelId)\"\n (keydown.enter)=\"onPanelContainerClick($any($event), placement.panelId)\">\n <!-- Render panel layers -->\n @for (layer of panelData.layers; track layer.id) {\n <pw-layer-renderer\n [layer]=\"layer\"\n [locale]=\"locale\"\n [altText]=\"getAltText(layer)\"\n [viewMode]=\"viewMode\"\n [reducedMotion]=\"reducedMotion\"\n [targetWidth]=\"targetWidthFor(placement.panelId)\"\n [panelId]=\"placement.panelId\"\n [placementId]=\"placement.panelId\"\n [readingOrderIndex]=\"readingOrderIndexOf(placement.panelId)\"\n [placementZ]=\"placement.z || 0\"\n [placementY]=\"placement.y\"\n [placementX]=\"placement.x\"\n (layerClick)=\"layerClick.emit($event)\">\n </pw-layer-renderer>\n }\n\n <!-- Interactive hotspots overlay for page view panels -->\n @if (interactive && panelData.hotspots && panelData.hotspots.length > 0) {\n <pw-hotspots-overlay\n [hotspots]=\"panelData.hotspots\"\n [locale]=\"locale\"\n [containerWidth]=\"getPagePanelWidth(placement)\"\n [containerHeight]=\"getPagePanelHeight(placement)\"\n [context]=\"variableContext\"\n (hotspotActivate)=\"onHotspotActivate($event, placement.panelId)\">\n </pw-hotspots-overlay>\n }\n\n <!-- Speech bubbles overlay for page view panels -->\n @if (speechEnabled && panelData.speechBubbles && panelData.speechBubbles.length > 0) {\n <pw-speech-bubbles\n [bubbles]=\"panelData.speechBubbles\"\n [locale]=\"locale\"\n [containerWidth]=\"getPagePanelWidth(placement)\"\n [containerHeight]=\"getPagePanelHeight(placement)\"\n [readingScale]=\"getReadingScale()\"\n [workBalloonConfig]=\"workBalloonConfig\"\n [characters]=\"characters\">\n </pw-speech-bubbles>\n }\n </div>\n }\n \n <!-- Focus indicator for rotated panels -->\n @if (isPanelFocused(placement.panelId)) {\n <div class=\"focus-rect\" aria-hidden=\"true\"></div>\n }\n </div>\n }\n }\n </div>\n </div>\n </div>\n </div>\n }\n\n @if (viewMode === 'page' && !page) {\n <div class=\"no-panel\">\n <p>No page to display</p>\n </div>\n }\n\n <!-- Overflow navigation arrows -->\n @if (showOverflowArrows && panel) {\n @if (hasOverflowLeft) {\n <button \n class=\"overflow-arrow overflow-left\"\n type=\"button\"\n aria-label=\"Navigate left\"\n (click)=\"navigateLeft()\">\n <pw-icon name=\"lucideChevronLeft\" />\n </button>\n }\n\n @if (hasOverflowRight) {\n <button \n class=\"overflow-arrow overflow-right\"\n type=\"button\"\n aria-label=\"Navigate right\"\n (click)=\"navigateRight()\">\n <pw-icon name=\"lucideChevronRight\" />\n </button>\n }\n\n @if (hasOverflowTop) {\n <button \n class=\"overflow-arrow overflow-top\"\n type=\"button\"\n aria-label=\"Navigate up\"\n (click)=\"navigateUp()\">\n <pw-icon name=\"lucideChevronUp\" />\n </button>\n }\n\n @if (hasOverflowBottom) {\n <button \n class=\"overflow-arrow overflow-bottom\"\n type=\"button\"\n aria-label=\"Navigate down\"\n (click)=\"navigateDown()\">\n <pw-icon name=\"lucideChevronDown\" />\n </button>\n }\n }\n\n <!-- Navigation arrows (hover zones) -->\n @if (viewMode === 'panel') {\n @if (showLeftArrow) {\n <button \n class=\"nav-arrow nav-arrow-left\"\n type=\"button\"\n aria-label=\"Previous panel\"\n (click)=\"onNavigatePrevious(); $event.stopPropagation()\">\n <span class=\"arrow-icon\"><pw-icon name=\"lucideChevronLeft\" /></span>\n </button>\n }\n\n @if (showRightArrow) {\n <button \n class=\"nav-arrow nav-arrow-right\"\n type=\"button\"\n aria-label=\"Next panel\"\n (click)=\"onNavigateNext(); $event.stopPropagation()\">\n <span class=\"arrow-icon\"><pw-icon name=\"lucideChevronRight\" /></span>\n </button>\n }\n }\n</div>\n", styles: [".viewport-container{position:relative;width:100%;height:100%;overflow:hidden;background-color:#f5f5f5;cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none}.viewport-container:active{cursor:grabbing}.viewport-container.interactive{cursor:default}.viewport-content{position:absolute;top:50%;left:50%;transform-origin:center center;will-change:transform}.viewport-content.reduced-motion{transition:none!important}.panel-container{position:relative;background:#fff;box-shadow:0 2px 8px #0000001a;width:1280px;height:720px;max-width:100%;max-height:100%}.t-frame{position:relative;z-index:1}.t-frame-leave{position:absolute;inset:0;z-index:0;pointer-events:none}.t-fade-in{animation:pw-t-fade-in var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-zoom-in{animation:pw-t-zoom-in var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-right{animation:pw-t-from-right var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-left{animation:pw-t-from-left var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-bottom{animation:pw-t-from-bottom var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-top{animation:pw-t-from-top var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-left{animation:pw-t-to-left var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-right{animation:pw-t-to-right var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-top{animation:pw-t-to-top var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-bottom{animation:pw-t-to-bottom var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}@keyframes pw-t-fade-in{0%{opacity:0}to{opacity:1}}@keyframes pw-t-zoom-in{0%{opacity:0;transform:scale(.94)}to{opacity:1;transform:scale(1)}}@keyframes pw-t-from-right{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes pw-t-from-left{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes pw-t-from-bottom{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes pw-t-from-top{0%{transform:translateY(-100%)}to{transform:translateY(0)}}@keyframes pw-t-to-left{0%{transform:translate(0)}to{transform:translate(-100%)}}@keyframes pw-t-to-right{0%{transform:translate(0)}to{transform:translate(100%)}}@keyframes pw-t-to-top{0%{transform:translateY(0)}to{transform:translateY(-100%)}}@keyframes pw-t-to-bottom{0%{transform:translateY(0)}to{transform:translateY(100%)}}.layer{position:absolute;top:0;left:0}.layer-placeholder{padding:20px;background:#0078d71a;border:1px dashed rgba(0,120,215,.3);color:#0078d7;font-size:14px}.no-panel{display:flex;align-items:center;justify-content:center;min-width:400px;min-height:300px;color:#666}.reduced-motion *{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}.overflow-arrow{position:absolute;z-index:100;background:#0009;color:#fff;border:none;border-radius:4px;width:40px;height:40px;font-size:20px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;box-shadow:0 2px 8px #0000004d}.overflow-arrow:hover{background:#000c;transform:scale(1.1)}.overflow-arrow:active{transform:scale(.95)}.overflow-left{left:20px;top:50%;transform:translateY(-50%)}.overflow-left:hover{transform:translateY(-50%) scale(1.1)}.overflow-right{right:20px;top:50%;transform:translateY(-50%)}.overflow-right:hover{transform:translateY(-50%) scale(1.1)}.overflow-top{top:20px;left:50%;transform:translate(-50%)}.overflow-top:hover{transform:translate(-50%) scale(1.1)}.overflow-bottom{bottom:20px;left:50%;transform:translate(-50%)}.overflow-bottom:hover{transform:translate(-50%) scale(1.1)}.viewport-page{width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:auto;padding:16px;box-sizing:border-box}.page-canvas{position:relative;width:100%;max-width:1400px;aspect-ratio:16 / 9;background:#1a1a1a;border-radius:4px;overflow:hidden}.page-stage{position:relative;width:100%;max-width:1400px;aspect-ratio:16 / 9;overflow:hidden;border-radius:4px}.page-stage .page-canvas{max-width:none;height:100%;aspect-ratio:auto}.pt-frame{position:relative;z-index:1;width:100%;height:100%}.pt-frame-leave{position:absolute;inset:0;z-index:0;pointer-events:none}.pt-frame-leave--above{z-index:2}.pt-frame.t-fade-in,.pt-frame.t-zoom-in,.pt-frame.t-from-right,.pt-frame.t-from-left,.pt-frame.t-from-bottom,.pt-frame.t-from-top{will-change:transform,opacity}.t-fade-out{animation:pw-t-fade-out var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-zoom-out{animation:pw-t-zoom-out var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}@keyframes pw-t-fade-out{0%{opacity:1}to{opacity:0}}@keyframes pw-t-zoom-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(1.06)}}.viewport-page .panel-container{position:absolute;overflow:hidden;background:#000;border:1px solid rgba(255,255,255,.1);border-radius:2px;transition:transform .2s ease,border-color .2s ease,box-shadow .2s ease;transform-origin:center center;outline:none}.viewport-page .panel-container:hover{border-color:#ffffff4d;cursor:pointer}.viewport-page .panel-container.panel-focused{border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d74d,0 4px 12px #0006;z-index:9999!important}.focus-rect{position:absolute;inset:-4px;border:3px solid rgba(0,120,215,.9);border-radius:4px;pointer-events:none;animation:focusPulse 1.5s ease-in-out infinite}@keyframes focusPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.7;transform:scale(1.02)}}@media (prefers-reduced-motion: reduce){.focus-rect{animation:none}.viewport-page .panel-container{transition:none}}.viewport-page .panel-content{position:relative;width:100%;height:100%}@media (max-width: 1024px){.page-canvas,.page-stage{max-width:900px}}@media (max-width: 768px){.viewport-page{padding:8px}.page-canvas,.page-stage{max-width:100%}}.nav-arrow{position:absolute;top:50%;transform:translateY(-50%);width:60px;height:120px;background:#0006;border:none;color:#fff;font-size:48px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s ease;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);z-index:50;animation:fadeInArrow .2s ease}.nav-arrow:hover{background:#0009;width:70px}.nav-arrow:active{background:#000000b3;transform:translateY(-50%) scale(.95)}.nav-arrow-left{left:0;border-radius:0 8px 8px 0}.nav-arrow-right{right:0;border-radius:8px 0 0 8px}.arrow-icon{font-size:48px;line-height:1;font-weight:300}@keyframes fadeInArrow{0%{opacity:0}to{opacity:1}}@media (prefers-reduced-motion: reduce){.nav-arrow{animation:none;transition:none}}@media (max-width: 768px){.nav-arrow{width:50px;height:100px;font-size:36px}.nav-arrow:hover{width:55px}.arrow-icon{font-size:36px}}\n"], dependencies: [{ kind: "component", type: LayerRendererComponent, selector: "pw-layer-renderer", inputs: ["layer", "locale", "baseUrl", "targetWidth", "altText", "viewMode", "viewActive", "reducedMotion", "panelId", "placementId", "readingOrderIndex", "placementZ", "placementY", "placementX"], outputs: ["layerClick"] }, { kind: "component", type: SpeechBubblesComponent, selector: "pw-speech-bubbles", inputs: ["bubbles", "locale", "containerWidth", "containerHeight", "readingScale", "workBalloonConfig", "characters"], outputs: ["bubbleClick", "bubbleAudioPlay"] }, { kind: "component", type: HotspotsOverlayComponent, selector: "pw-hotspots-overlay", inputs: ["hotspots", "locale", "containerWidth", "containerHeight", "context", "interactive", "showIndicators"], outputs: ["hotspotActivate"] }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
12232
12232
|
}
|
|
12233
12233
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ViewportComponent, decorators: [{
|
|
12234
12234
|
type: Component,
|
|
12235
|
-
args: [{ selector: 'pw-viewport', imports: [LayerRendererComponent, SpeechBubblesComponent, HotspotsOverlayComponent, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div \r\n class=\"viewport-container\"\r\n [class]=\"getViewModeClass()\"\r\n [class.reduced-motion]=\"reducedMotion\"\r\n [class.interactive]=\"interactive\"\r\n role=\"region\"\r\n [attr.aria-label]=\"viewMode === 'page' ? 'Page viewport' : 'Panel viewport'\"\r\n tabindex=\"0\"\r\n (click)=\"onViewportClick($event)\"\r\n (keydown.enter)=\"onKeyboardActivate($event)\"\r\n (keydown.space)=\"onKeyboardActivate($event)\"\r\n (mousemove)=\"onViewportMouseMove($event)\"\r\n (mouseleave)=\"onViewportMouseLeave()\">\r\n \r\n <!-- Panel View -->\r\n @if (viewMode === 'panel') {\r\n <div\r\n class=\"viewport-content\"\r\n [style.transform]=\"getTransformStyle()\"\r\n [style.transition]=\"reducedMotion ? 'none' : 'transform 0.3s ease'\"\r\n [style.--pw-t-duration]=\"transitionDuration\"\r\n [style.--pw-t-easing]=\"transitionEasing\">\r\n\r\n <!-- Outgoing panel during a manifest edge transition -->\r\n @if (leavingPanel) {\r\n <div [class]=\"'t-frame-leave ' + leaveAnimationClass\" aria-hidden=\"true\">\r\n <div class=\"panel-container\">\r\n @for (layer of leavingPanel.layers; track layer.id) {\r\n <pw-layer-renderer\r\n [layer]=\"layer\"\r\n [locale]=\"locale\"\r\n [viewMode]=\"viewMode\"\r\n [viewActive]=\"false\"\r\n [reducedMotion]=\"reducedMotion\"\r\n [targetWidth]=\"targetWidthFor(leavingPanelId)\"\r\n [panelId]=\"leavingPanelId\">\r\n </pw-layer-renderer>\r\n }\r\n\r\n @if (speechEnabled && leavingPanel.speechBubbles && leavingPanel.speechBubbles.length > 0) {\r\n <pw-speech-bubbles\r\n [bubbles]=\"leavingPanel.speechBubbles\"\r\n [locale]=\"locale\"\r\n [containerWidth]=\"getContainerWidth()\"\r\n [containerHeight]=\"getContainerHeight()\"\r\n [readingScale]=\"getReadingScale()\"\r\n [workBalloonConfig]=\"workBalloonConfig\"\r\n [characters]=\"characters\">\r\n </pw-speech-bubbles>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <div [class]=\"'t-frame ' + enterAnimationClass\">\r\n @if (panel) {\r\n <div class=\"panel-container\" tabindex=\"0\" (click)=\"onPanelContainerClick($event, currentPanelId)\" (keydown.enter)=\"onPanelContainerClick($any($event), currentPanelId)\">\r\n <!-- Render layers using LayerRenderer -->\r\n @for (layer of panel.layers; track layer.id) {\r\n <pw-layer-renderer\r\n [layer]=\"layer\"\r\n [locale]=\"locale\"\r\n [viewMode]=\"viewMode\"\r\n [viewActive]=\"true\"\r\n [reducedMotion]=\"reducedMotion\"\r\n [targetWidth]=\"targetWidthFor(currentPanelId)\"\r\n [panelId]=\"currentPanelId || ''\"\r\n (layerClick)=\"layerClick.emit($event)\">\r\n </pw-layer-renderer>\r\n }\r\n\r\n <!-- Interactive hotspots overlay (below speech bubbles) -->\r\n @if (interactive && panel.hotspots && panel.hotspots.length > 0) {\r\n <pw-hotspots-overlay\r\n [hotspots]=\"panel.hotspots\"\r\n [locale]=\"locale\"\r\n [containerWidth]=\"getContainerWidth()\"\r\n [containerHeight]=\"getContainerHeight()\"\r\n [context]=\"variableContext\"\r\n (hotspotActivate)=\"onHotspotActivate($event, currentPanelId)\">\r\n </pw-hotspots-overlay>\r\n }\r\n\r\n <!-- Speech bubbles overlay for panel view -->\r\n @if (speechEnabled && panel.speechBubbles && panel.speechBubbles.length > 0) {\r\n <pw-speech-bubbles\r\n [bubbles]=\"panel.speechBubbles\"\r\n [locale]=\"locale\"\r\n [containerWidth]=\"getContainerWidth()\"\r\n [containerHeight]=\"getContainerHeight()\"\r\n [readingScale]=\"getReadingScale()\"\r\n [workBalloonConfig]=\"workBalloonConfig\"\r\n [characters]=\"characters\">\r\n </pw-speech-bubbles>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"no-panel\">\r\n <p>No panel to display</p>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Page View -->\r\n @if (viewMode === 'page' && page) {\r\n <div class=\"viewport-page\">\r\n <div class=\"page-stage\">\r\n <!-- Outgoing page during a Page.transitions in/out change -->\r\n @if (leavingPage) {\r\n <div\r\n [class]=\"'pt-frame-leave ' + pageLeaveClass + (pageLeaveAbove ? ' pt-frame-leave--above' : '')\"\r\n [style.--pw-t-duration]=\"pageLeaveDuration\"\r\n [style.--pw-t-easing]=\"pageLeaveEasing\"\r\n aria-hidden=\"true\">\r\n <div class=\"page-canvas\">\r\n @for (placement of getLeavingPagePlacements(); track placement.panelId) {\r\n <div\r\n class=\"panel-container\"\r\n [style.left.%]=\"toPercent(placement.x)\"\r\n [style.top.%]=\"toPercent(placement.y)\"\r\n [style.width.%]=\"toPercent(placement.w)\"\r\n [style.height.%]=\"toPercent(placement.h)\"\r\n [style.z-index]=\"placement.z || 0\"\r\n [style.transform]=\"getPanelTransform(placement)\"\r\n [style.transform-origin]=\"getTransformOrigin(placement)\">\r\n\r\n @if (getLeavingPanel(placement.panelId); as panelData) {\r\n <div class=\"panel-content\"\r\n [style.clip-path]=\"getPanelContentClipPath(placement)\"\r\n [style.transform]=\"getPanelContentTransform(placement)\">\r\n @for (layer of panelData.layers; track layer.id) {\r\n <pw-layer-renderer\r\n [layer]=\"layer\"\r\n [locale]=\"locale\"\r\n [viewMode]=\"viewMode\"\r\n [viewActive]=\"false\"\r\n [reducedMotion]=\"reducedMotion\"\r\n [targetWidth]=\"targetWidthFor(placement.panelId)\"\r\n [panelId]=\"placement.panelId\"\r\n [placementId]=\"placement.panelId\">\r\n </pw-layer-renderer>\r\n }\r\n\r\n @if (speechEnabled && panelData.speechBubbles && panelData.speechBubbles.length > 0) {\r\n <pw-speech-bubbles\r\n [bubbles]=\"panelData.speechBubbles\"\r\n [locale]=\"locale\"\r\n [containerWidth]=\"getPagePanelWidth(placement)\"\r\n [containerHeight]=\"getPagePanelHeight(placement)\"\r\n [readingScale]=\"getReadingScale()\"\r\n [workBalloonConfig]=\"workBalloonConfig\"\r\n [characters]=\"characters\">\r\n </pw-speech-bubbles>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <div\r\n [class]=\"'pt-frame ' + pageEnterClass\"\r\n [style.--pw-t-duration]=\"pageEnterDuration\"\r\n [style.--pw-t-easing]=\"pageEnterEasing\">\r\n <div\r\n class=\"page-canvas\"\r\n [attr.tabindex]=\"-1\"\r\n (click)=\"onPanelClickWithFocus($event)\"\r\n (keydown.enter)=\"onCanvasKeyboardActivate($event)\"\r\n (keydown.space)=\"onCanvasKeyboardActivate($event)\">\r\n \r\n @for (placement of getSortedPanels(); track placement.panelId) {\r\n @if (isPanelVisible(placement)) {\r\n <div \r\n class=\"panel-container\"\r\n [class.panel-focused]=\"isPanelFocused(placement.panelId)\"\r\n [style.left.%]=\"toPercent(placement.x)\"\r\n [style.top.%]=\"toPercent(placement.y)\"\r\n [style.width.%]=\"toPercent(placement.w)\"\r\n [style.height.%]=\"toPercent(placement.h)\"\r\n [style.z-index]=\"placement.z || 0\"\r\n [style.transform]=\"getPanelTransform(placement)\"\r\n [style.transform-origin]=\"getTransformOrigin(placement)\"\r\n [attr.data-panel-id]=\"placement.panelId\"\r\n [attr.tabindex]=\"panelHasHotspots(placement.panelId) ? null : 0\"\r\n [attr.role]=\"panelHasHotspots(placement.panelId) ? null : 'button'\"\r\n [attr.aria-label]=\"panelHasHotspots(placement.panelId) ? null : 'Panel ' + placement.panelId\"\r\n [attr.loading]=\"getImageLoadingStrategy()\"\r\n (click)=\"onPanelClick($event, placement.panelId)\"\r\n (keydown.enter)=\"onPanelKeyboardActivate($event, placement.panelId)\"\r\n (keydown.space)=\"onPanelKeyboardActivate($event, placement.panelId)\">\r\n \r\n @if (getPanel(placement.panelId); as panelData) {\r\n <div class=\"panel-content\"\r\n [style.clip-path]=\"getPanelContentClipPath(placement)\"\r\n [style.transform]=\"getPanelContentTransform(placement)\"\r\n tabindex=\"0\"\r\n (click)=\"onPanelContainerClick($event, placement.panelId)\"\r\n (keydown.enter)=\"onPanelContainerClick($any($event), placement.panelId)\">\r\n <!-- Render panel layers -->\r\n @for (layer of panelData.layers; track layer.id) {\r\n <pw-layer-renderer\r\n [layer]=\"layer\"\r\n [locale]=\"locale\"\r\n [altText]=\"getAltText(layer)\"\r\n [viewMode]=\"viewMode\"\r\n [reducedMotion]=\"reducedMotion\"\r\n [targetWidth]=\"targetWidthFor(placement.panelId)\"\r\n [panelId]=\"placement.panelId\"\r\n [placementId]=\"placement.panelId\"\r\n [readingOrderIndex]=\"readingOrderIndexOf(placement.panelId)\"\r\n [placementZ]=\"placement.z || 0\"\r\n [placementY]=\"placement.y\"\r\n [placementX]=\"placement.x\"\r\n (layerClick)=\"layerClick.emit($event)\">\r\n </pw-layer-renderer>\r\n }\r\n\r\n <!-- Interactive hotspots overlay for page view panels -->\r\n @if (interactive && panelData.hotspots && panelData.hotspots.length > 0) {\r\n <pw-hotspots-overlay\r\n [hotspots]=\"panelData.hotspots\"\r\n [locale]=\"locale\"\r\n [containerWidth]=\"getPagePanelWidth(placement)\"\r\n [containerHeight]=\"getPagePanelHeight(placement)\"\r\n [context]=\"variableContext\"\r\n (hotspotActivate)=\"onHotspotActivate($event, placement.panelId)\">\r\n </pw-hotspots-overlay>\r\n }\r\n\r\n <!-- Speech bubbles overlay for page view panels -->\r\n @if (speechEnabled && panelData.speechBubbles && panelData.speechBubbles.length > 0) {\r\n <pw-speech-bubbles\r\n [bubbles]=\"panelData.speechBubbles\"\r\n [locale]=\"locale\"\r\n [containerWidth]=\"getPagePanelWidth(placement)\"\r\n [containerHeight]=\"getPagePanelHeight(placement)\"\r\n [readingScale]=\"getReadingScale()\"\r\n [workBalloonConfig]=\"workBalloonConfig\"\r\n [characters]=\"characters\">\r\n </pw-speech-bubbles>\r\n }\r\n </div>\r\n }\r\n \r\n <!-- Focus indicator for rotated panels -->\r\n @if (isPanelFocused(placement.panelId)) {\r\n <div class=\"focus-rect\" aria-hidden=\"true\"></div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (viewMode === 'page' && !page) {\r\n <div class=\"no-panel\">\r\n <p>No page to display</p>\r\n </div>\r\n }\r\n\r\n <!-- Overflow navigation arrows -->\r\n @if (showOverflowArrows && panel) {\r\n @if (hasOverflowLeft) {\r\n <button \r\n class=\"overflow-arrow overflow-left\"\r\n type=\"button\"\r\n aria-label=\"Navigate left\"\r\n (click)=\"navigateLeft()\">\r\n <pw-icon name=\"lucideChevronLeft\" />\r\n </button>\r\n }\r\n\r\n @if (hasOverflowRight) {\r\n <button \r\n class=\"overflow-arrow overflow-right\"\r\n type=\"button\"\r\n aria-label=\"Navigate right\"\r\n (click)=\"navigateRight()\">\r\n <pw-icon name=\"lucideChevronRight\" />\r\n </button>\r\n }\r\n\r\n @if (hasOverflowTop) {\r\n <button \r\n class=\"overflow-arrow overflow-top\"\r\n type=\"button\"\r\n aria-label=\"Navigate up\"\r\n (click)=\"navigateUp()\">\r\n <pw-icon name=\"lucideChevronUp\" />\r\n </button>\r\n }\r\n\r\n @if (hasOverflowBottom) {\r\n <button \r\n class=\"overflow-arrow overflow-bottom\"\r\n type=\"button\"\r\n aria-label=\"Navigate down\"\r\n (click)=\"navigateDown()\">\r\n <pw-icon name=\"lucideChevronDown\" />\r\n </button>\r\n }\r\n }\r\n\r\n <!-- Navigation arrows (hover zones) -->\r\n @if (viewMode === 'panel') {\r\n @if (showLeftArrow) {\r\n <button \r\n class=\"nav-arrow nav-arrow-left\"\r\n type=\"button\"\r\n aria-label=\"Previous panel\"\r\n (click)=\"onNavigatePrevious(); $event.stopPropagation()\">\r\n <span class=\"arrow-icon\"><pw-icon name=\"lucideChevronLeft\" /></span>\r\n </button>\r\n }\r\n\r\n @if (showRightArrow) {\r\n <button \r\n class=\"nav-arrow nav-arrow-right\"\r\n type=\"button\"\r\n aria-label=\"Next panel\"\r\n (click)=\"onNavigateNext(); $event.stopPropagation()\">\r\n <span class=\"arrow-icon\"><pw-icon name=\"lucideChevronRight\" /></span>\r\n </button>\r\n }\r\n }\r\n</div>\r\n", styles: [".viewport-container{position:relative;width:100%;height:100%;overflow:hidden;background-color:#f5f5f5;cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none}.viewport-container:active{cursor:grabbing}.viewport-container.interactive{cursor:default}.viewport-content{position:absolute;top:50%;left:50%;transform-origin:center center;will-change:transform}.viewport-content.reduced-motion{transition:none!important}.panel-container{position:relative;background:#fff;box-shadow:0 2px 8px #0000001a;width:1280px;height:720px;max-width:100%;max-height:100%}.t-frame{position:relative;z-index:1}.t-frame-leave{position:absolute;inset:0;z-index:0;pointer-events:none}.t-fade-in{animation:pw-t-fade-in var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-zoom-in{animation:pw-t-zoom-in var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-right{animation:pw-t-from-right var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-left{animation:pw-t-from-left var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-bottom{animation:pw-t-from-bottom var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-top{animation:pw-t-from-top var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-left{animation:pw-t-to-left var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-right{animation:pw-t-to-right var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-top{animation:pw-t-to-top var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-bottom{animation:pw-t-to-bottom var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}@keyframes pw-t-fade-in{0%{opacity:0}to{opacity:1}}@keyframes pw-t-zoom-in{0%{opacity:0;transform:scale(.94)}to{opacity:1;transform:scale(1)}}@keyframes pw-t-from-right{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes pw-t-from-left{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes pw-t-from-bottom{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes pw-t-from-top{0%{transform:translateY(-100%)}to{transform:translateY(0)}}@keyframes pw-t-to-left{0%{transform:translate(0)}to{transform:translate(-100%)}}@keyframes pw-t-to-right{0%{transform:translate(0)}to{transform:translate(100%)}}@keyframes pw-t-to-top{0%{transform:translateY(0)}to{transform:translateY(-100%)}}@keyframes pw-t-to-bottom{0%{transform:translateY(0)}to{transform:translateY(100%)}}.layer{position:absolute;top:0;left:0}.layer-placeholder{padding:20px;background:#0078d71a;border:1px dashed rgba(0,120,215,.3);color:#0078d7;font-size:14px}.no-panel{display:flex;align-items:center;justify-content:center;min-width:400px;min-height:300px;color:#666}.reduced-motion *{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}.overflow-arrow{position:absolute;z-index:100;background:#0009;color:#fff;border:none;border-radius:4px;width:40px;height:40px;font-size:20px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;box-shadow:0 2px 8px #0000004d}.overflow-arrow:hover{background:#000c;transform:scale(1.1)}.overflow-arrow:active{transform:scale(.95)}.overflow-left{left:20px;top:50%;transform:translateY(-50%)}.overflow-left:hover{transform:translateY(-50%) scale(1.1)}.overflow-right{right:20px;top:50%;transform:translateY(-50%)}.overflow-right:hover{transform:translateY(-50%) scale(1.1)}.overflow-top{top:20px;left:50%;transform:translate(-50%)}.overflow-top:hover{transform:translate(-50%) scale(1.1)}.overflow-bottom{bottom:20px;left:50%;transform:translate(-50%)}.overflow-bottom:hover{transform:translate(-50%) scale(1.1)}.viewport-page{width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:auto;padding:16px;box-sizing:border-box}.page-canvas{position:relative;width:100%;max-width:1400px;aspect-ratio:16 / 9;background:#1a1a1a;border-radius:4px;overflow:hidden}.page-stage{position:relative;width:100%;max-width:1400px;aspect-ratio:16 / 9;overflow:hidden;border-radius:4px}.page-stage .page-canvas{max-width:none;height:100%;aspect-ratio:auto}.pt-frame{position:relative;z-index:1;width:100%;height:100%}.pt-frame-leave{position:absolute;inset:0;z-index:0;pointer-events:none}.pt-frame-leave--above{z-index:2}.pt-frame.t-fade-in,.pt-frame.t-zoom-in,.pt-frame.t-from-right,.pt-frame.t-from-left,.pt-frame.t-from-bottom,.pt-frame.t-from-top{will-change:transform,opacity}.t-fade-out{animation:pw-t-fade-out var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-zoom-out{animation:pw-t-zoom-out var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}@keyframes pw-t-fade-out{0%{opacity:1}to{opacity:0}}@keyframes pw-t-zoom-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(1.06)}}.viewport-page .panel-container{position:absolute;overflow:hidden;background:#000;border:1px solid rgba(255,255,255,.1);border-radius:2px;transition:transform .2s ease,border-color .2s ease,box-shadow .2s ease;transform-origin:center center;outline:none}.viewport-page .panel-container:hover{border-color:#ffffff4d;cursor:pointer}.viewport-page .panel-container.panel-focused{border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d74d,0 4px 12px #0006;z-index:9999!important}.focus-rect{position:absolute;inset:-4px;border:3px solid rgba(0,120,215,.9);border-radius:4px;pointer-events:none;animation:focusPulse 1.5s ease-in-out infinite}@keyframes focusPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.7;transform:scale(1.02)}}@media (prefers-reduced-motion: reduce){.focus-rect{animation:none}.viewport-page .panel-container{transition:none}}.viewport-page .panel-content{position:relative;width:100%;height:100%}@media (max-width: 1024px){.page-canvas,.page-stage{max-width:900px}}@media (max-width: 768px){.viewport-page{padding:8px}.page-canvas,.page-stage{max-width:100%}}.nav-arrow{position:absolute;top:50%;transform:translateY(-50%);width:60px;height:120px;background:#0006;border:none;color:#fff;font-size:48px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s ease;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);z-index:50;animation:fadeInArrow .2s ease}.nav-arrow:hover{background:#0009;width:70px}.nav-arrow:active{background:#000000b3;transform:translateY(-50%) scale(.95)}.nav-arrow-left{left:0;border-radius:0 8px 8px 0}.nav-arrow-right{right:0;border-radius:8px 0 0 8px}.arrow-icon{font-size:48px;line-height:1;font-weight:300}@keyframes fadeInArrow{0%{opacity:0}to{opacity:1}}@media (prefers-reduced-motion: reduce){.nav-arrow{animation:none;transition:none}}@media (max-width: 768px){.nav-arrow{width:50px;height:100px;font-size:36px}.nav-arrow:hover{width:55px}.arrow-icon{font-size:36px}}\n"] }]
|
|
12235
|
+
args: [{ selector: 'pw-viewport', imports: [LayerRendererComponent, SpeechBubblesComponent, HotspotsOverlayComponent, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div \n class=\"viewport-container\"\n [class]=\"getViewModeClass()\"\n [class.reduced-motion]=\"reducedMotion\"\n [class.interactive]=\"interactive\"\n role=\"region\"\n [attr.aria-label]=\"viewMode === 'page' ? 'Page viewport' : 'Panel viewport'\"\n tabindex=\"0\"\n (click)=\"onViewportClick($event)\"\n (keydown.enter)=\"onKeyboardActivate($event)\"\n (keydown.space)=\"onKeyboardActivate($event)\"\n (mousemove)=\"onViewportMouseMove($event)\"\n (mouseleave)=\"onViewportMouseLeave()\">\n \n <!-- Panel View -->\n @if (viewMode === 'panel') {\n <div\n class=\"viewport-content\"\n [style.transform]=\"getTransformStyle()\"\n [style.transition]=\"reducedMotion ? 'none' : 'transform 0.3s ease'\"\n [style.--pw-t-duration]=\"transitionDuration\"\n [style.--pw-t-easing]=\"transitionEasing\">\n\n <!-- Outgoing panel during a manifest edge transition -->\n @if (leavingPanel) {\n <div [class]=\"'t-frame-leave ' + leaveAnimationClass\" aria-hidden=\"true\">\n <div class=\"panel-container\">\n @for (layer of leavingPanel.layers; track layer.id) {\n <pw-layer-renderer\n [layer]=\"layer\"\n [locale]=\"locale\"\n [viewMode]=\"viewMode\"\n [viewActive]=\"false\"\n [reducedMotion]=\"reducedMotion\"\n [targetWidth]=\"targetWidthFor(leavingPanelId)\"\n [panelId]=\"leavingPanelId\">\n </pw-layer-renderer>\n }\n\n @if (speechEnabled && leavingPanel.speechBubbles && leavingPanel.speechBubbles.length > 0) {\n <pw-speech-bubbles\n [bubbles]=\"leavingPanel.speechBubbles\"\n [locale]=\"locale\"\n [containerWidth]=\"getContainerWidth()\"\n [containerHeight]=\"getContainerHeight()\"\n [readingScale]=\"getReadingScale()\"\n [workBalloonConfig]=\"workBalloonConfig\"\n [characters]=\"characters\">\n </pw-speech-bubbles>\n }\n </div>\n </div>\n }\n\n <div [class]=\"'t-frame ' + enterAnimationClass\">\n @if (panel) {\n <div class=\"panel-container\" tabindex=\"0\" (click)=\"onPanelContainerClick($event, currentPanelId)\" (keydown.enter)=\"onPanelContainerClick($any($event), currentPanelId)\">\n <!-- Render layers using LayerRenderer -->\n @for (layer of panel.layers; track layer.id) {\n <pw-layer-renderer\n [layer]=\"layer\"\n [locale]=\"locale\"\n [viewMode]=\"viewMode\"\n [viewActive]=\"true\"\n [reducedMotion]=\"reducedMotion\"\n [targetWidth]=\"targetWidthFor(currentPanelId)\"\n [panelId]=\"currentPanelId || ''\"\n (layerClick)=\"layerClick.emit($event)\">\n </pw-layer-renderer>\n }\n\n <!-- Interactive hotspots overlay (below speech bubbles) -->\n @if (interactive && panel.hotspots && panel.hotspots.length > 0) {\n <pw-hotspots-overlay\n [hotspots]=\"panel.hotspots\"\n [locale]=\"locale\"\n [containerWidth]=\"getContainerWidth()\"\n [containerHeight]=\"getContainerHeight()\"\n [context]=\"variableContext\"\n (hotspotActivate)=\"onHotspotActivate($event, currentPanelId)\">\n </pw-hotspots-overlay>\n }\n\n <!-- Speech bubbles overlay for panel view -->\n @if (speechEnabled && panel.speechBubbles && panel.speechBubbles.length > 0) {\n <pw-speech-bubbles\n [bubbles]=\"panel.speechBubbles\"\n [locale]=\"locale\"\n [containerWidth]=\"getContainerWidth()\"\n [containerHeight]=\"getContainerHeight()\"\n [readingScale]=\"getReadingScale()\"\n [workBalloonConfig]=\"workBalloonConfig\"\n [characters]=\"characters\">\n </pw-speech-bubbles>\n }\n </div>\n } @else {\n <div class=\"no-panel\">\n <p>No panel to display</p>\n </div>\n }\n </div>\n </div>\n }\n \n <!-- Page View -->\n @if (viewMode === 'page' && page) {\n <div class=\"viewport-page\">\n <div class=\"page-stage\">\n <!-- Outgoing page during a Page.transitions in/out change -->\n @if (leavingPage) {\n <div\n [class]=\"'pt-frame-leave ' + pageLeaveClass + (pageLeaveAbove ? ' pt-frame-leave--above' : '')\"\n [style.--pw-t-duration]=\"pageLeaveDuration\"\n [style.--pw-t-easing]=\"pageLeaveEasing\"\n aria-hidden=\"true\">\n <div class=\"page-canvas\">\n @for (placement of getLeavingPagePlacements(); track placement.panelId) {\n <div\n class=\"panel-container\"\n [style.left.%]=\"toPercent(placement.x)\"\n [style.top.%]=\"toPercent(placement.y)\"\n [style.width.%]=\"toPercent(placement.w)\"\n [style.height.%]=\"toPercent(placement.h)\"\n [style.z-index]=\"placement.z || 0\"\n [style.transform]=\"getPanelTransform(placement)\"\n [style.transform-origin]=\"getTransformOrigin(placement)\">\n\n @if (getLeavingPanel(placement.panelId); as panelData) {\n <div class=\"panel-content\"\n [style.clip-path]=\"getPanelContentClipPath(placement)\"\n [style.transform]=\"getPanelContentTransform(placement)\">\n @for (layer of panelData.layers; track layer.id) {\n <pw-layer-renderer\n [layer]=\"layer\"\n [locale]=\"locale\"\n [viewMode]=\"viewMode\"\n [viewActive]=\"false\"\n [reducedMotion]=\"reducedMotion\"\n [targetWidth]=\"targetWidthFor(placement.panelId)\"\n [panelId]=\"placement.panelId\"\n [placementId]=\"placement.panelId\">\n </pw-layer-renderer>\n }\n\n @if (speechEnabled && panelData.speechBubbles && panelData.speechBubbles.length > 0) {\n <pw-speech-bubbles\n [bubbles]=\"panelData.speechBubbles\"\n [locale]=\"locale\"\n [containerWidth]=\"getPagePanelWidth(placement)\"\n [containerHeight]=\"getPagePanelHeight(placement)\"\n [readingScale]=\"getReadingScale()\"\n [workBalloonConfig]=\"workBalloonConfig\"\n [characters]=\"characters\">\n </pw-speech-bubbles>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n\n <div\n [class]=\"'pt-frame ' + pageEnterClass\"\n [style.--pw-t-duration]=\"pageEnterDuration\"\n [style.--pw-t-easing]=\"pageEnterEasing\">\n <div\n class=\"page-canvas\"\n [attr.tabindex]=\"-1\"\n (click)=\"onPanelClickWithFocus($event)\"\n (keydown.enter)=\"onCanvasKeyboardActivate($event)\"\n (keydown.space)=\"onCanvasKeyboardActivate($event)\">\n \n @for (placement of getSortedPanels(); track placement.panelId) {\n @if (isPanelVisible(placement)) {\n <div \n class=\"panel-container\"\n [class.panel-focused]=\"isPanelFocused(placement.panelId)\"\n [style.left.%]=\"toPercent(placement.x)\"\n [style.top.%]=\"toPercent(placement.y)\"\n [style.width.%]=\"toPercent(placement.w)\"\n [style.height.%]=\"toPercent(placement.h)\"\n [style.z-index]=\"placement.z || 0\"\n [style.transform]=\"getPanelTransform(placement)\"\n [style.transform-origin]=\"getTransformOrigin(placement)\"\n [attr.data-panel-id]=\"placement.panelId\"\n [attr.tabindex]=\"panelHasHotspots(placement.panelId) ? null : 0\"\n [attr.role]=\"panelHasHotspots(placement.panelId) ? null : 'button'\"\n [attr.aria-label]=\"panelHasHotspots(placement.panelId) ? null : 'Panel ' + placement.panelId\"\n [attr.loading]=\"getImageLoadingStrategy()\"\n (click)=\"onPanelClick($event, placement.panelId)\"\n (keydown.enter)=\"onPanelKeyboardActivate($event, placement.panelId)\"\n (keydown.space)=\"onPanelKeyboardActivate($event, placement.panelId)\">\n \n @if (getPanel(placement.panelId); as panelData) {\n <div class=\"panel-content\"\n [style.clip-path]=\"getPanelContentClipPath(placement)\"\n [style.transform]=\"getPanelContentTransform(placement)\"\n tabindex=\"0\"\n (click)=\"onPanelContainerClick($event, placement.panelId)\"\n (keydown.enter)=\"onPanelContainerClick($any($event), placement.panelId)\">\n <!-- Render panel layers -->\n @for (layer of panelData.layers; track layer.id) {\n <pw-layer-renderer\n [layer]=\"layer\"\n [locale]=\"locale\"\n [altText]=\"getAltText(layer)\"\n [viewMode]=\"viewMode\"\n [reducedMotion]=\"reducedMotion\"\n [targetWidth]=\"targetWidthFor(placement.panelId)\"\n [panelId]=\"placement.panelId\"\n [placementId]=\"placement.panelId\"\n [readingOrderIndex]=\"readingOrderIndexOf(placement.panelId)\"\n [placementZ]=\"placement.z || 0\"\n [placementY]=\"placement.y\"\n [placementX]=\"placement.x\"\n (layerClick)=\"layerClick.emit($event)\">\n </pw-layer-renderer>\n }\n\n <!-- Interactive hotspots overlay for page view panels -->\n @if (interactive && panelData.hotspots && panelData.hotspots.length > 0) {\n <pw-hotspots-overlay\n [hotspots]=\"panelData.hotspots\"\n [locale]=\"locale\"\n [containerWidth]=\"getPagePanelWidth(placement)\"\n [containerHeight]=\"getPagePanelHeight(placement)\"\n [context]=\"variableContext\"\n (hotspotActivate)=\"onHotspotActivate($event, placement.panelId)\">\n </pw-hotspots-overlay>\n }\n\n <!-- Speech bubbles overlay for page view panels -->\n @if (speechEnabled && panelData.speechBubbles && panelData.speechBubbles.length > 0) {\n <pw-speech-bubbles\n [bubbles]=\"panelData.speechBubbles\"\n [locale]=\"locale\"\n [containerWidth]=\"getPagePanelWidth(placement)\"\n [containerHeight]=\"getPagePanelHeight(placement)\"\n [readingScale]=\"getReadingScale()\"\n [workBalloonConfig]=\"workBalloonConfig\"\n [characters]=\"characters\">\n </pw-speech-bubbles>\n }\n </div>\n }\n \n <!-- Focus indicator for rotated panels -->\n @if (isPanelFocused(placement.panelId)) {\n <div class=\"focus-rect\" aria-hidden=\"true\"></div>\n }\n </div>\n }\n }\n </div>\n </div>\n </div>\n </div>\n }\n\n @if (viewMode === 'page' && !page) {\n <div class=\"no-panel\">\n <p>No page to display</p>\n </div>\n }\n\n <!-- Overflow navigation arrows -->\n @if (showOverflowArrows && panel) {\n @if (hasOverflowLeft) {\n <button \n class=\"overflow-arrow overflow-left\"\n type=\"button\"\n aria-label=\"Navigate left\"\n (click)=\"navigateLeft()\">\n <pw-icon name=\"lucideChevronLeft\" />\n </button>\n }\n\n @if (hasOverflowRight) {\n <button \n class=\"overflow-arrow overflow-right\"\n type=\"button\"\n aria-label=\"Navigate right\"\n (click)=\"navigateRight()\">\n <pw-icon name=\"lucideChevronRight\" />\n </button>\n }\n\n @if (hasOverflowTop) {\n <button \n class=\"overflow-arrow overflow-top\"\n type=\"button\"\n aria-label=\"Navigate up\"\n (click)=\"navigateUp()\">\n <pw-icon name=\"lucideChevronUp\" />\n </button>\n }\n\n @if (hasOverflowBottom) {\n <button \n class=\"overflow-arrow overflow-bottom\"\n type=\"button\"\n aria-label=\"Navigate down\"\n (click)=\"navigateDown()\">\n <pw-icon name=\"lucideChevronDown\" />\n </button>\n }\n }\n\n <!-- Navigation arrows (hover zones) -->\n @if (viewMode === 'panel') {\n @if (showLeftArrow) {\n <button \n class=\"nav-arrow nav-arrow-left\"\n type=\"button\"\n aria-label=\"Previous panel\"\n (click)=\"onNavigatePrevious(); $event.stopPropagation()\">\n <span class=\"arrow-icon\"><pw-icon name=\"lucideChevronLeft\" /></span>\n </button>\n }\n\n @if (showRightArrow) {\n <button \n class=\"nav-arrow nav-arrow-right\"\n type=\"button\"\n aria-label=\"Next panel\"\n (click)=\"onNavigateNext(); $event.stopPropagation()\">\n <span class=\"arrow-icon\"><pw-icon name=\"lucideChevronRight\" /></span>\n </button>\n }\n }\n</div>\n", styles: [".viewport-container{position:relative;width:100%;height:100%;overflow:hidden;background-color:#f5f5f5;cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none}.viewport-container:active{cursor:grabbing}.viewport-container.interactive{cursor:default}.viewport-content{position:absolute;top:50%;left:50%;transform-origin:center center;will-change:transform}.viewport-content.reduced-motion{transition:none!important}.panel-container{position:relative;background:#fff;box-shadow:0 2px 8px #0000001a;width:1280px;height:720px;max-width:100%;max-height:100%}.t-frame{position:relative;z-index:1}.t-frame-leave{position:absolute;inset:0;z-index:0;pointer-events:none}.t-fade-in{animation:pw-t-fade-in var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-zoom-in{animation:pw-t-zoom-in var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-right{animation:pw-t-from-right var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-left{animation:pw-t-from-left var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-bottom{animation:pw-t-from-bottom var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-from-top{animation:pw-t-from-top var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-left{animation:pw-t-to-left var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-right{animation:pw-t-to-right var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-top{animation:pw-t-to-top var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-to-bottom{animation:pw-t-to-bottom var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}@keyframes pw-t-fade-in{0%{opacity:0}to{opacity:1}}@keyframes pw-t-zoom-in{0%{opacity:0;transform:scale(.94)}to{opacity:1;transform:scale(1)}}@keyframes pw-t-from-right{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes pw-t-from-left{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes pw-t-from-bottom{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes pw-t-from-top{0%{transform:translateY(-100%)}to{transform:translateY(0)}}@keyframes pw-t-to-left{0%{transform:translate(0)}to{transform:translate(-100%)}}@keyframes pw-t-to-right{0%{transform:translate(0)}to{transform:translate(100%)}}@keyframes pw-t-to-top{0%{transform:translateY(0)}to{transform:translateY(-100%)}}@keyframes pw-t-to-bottom{0%{transform:translateY(0)}to{transform:translateY(100%)}}.layer{position:absolute;top:0;left:0}.layer-placeholder{padding:20px;background:#0078d71a;border:1px dashed rgba(0,120,215,.3);color:#0078d7;font-size:14px}.no-panel{display:flex;align-items:center;justify-content:center;min-width:400px;min-height:300px;color:#666}.reduced-motion *{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}.overflow-arrow{position:absolute;z-index:100;background:#0009;color:#fff;border:none;border-radius:4px;width:40px;height:40px;font-size:20px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;box-shadow:0 2px 8px #0000004d}.overflow-arrow:hover{background:#000c;transform:scale(1.1)}.overflow-arrow:active{transform:scale(.95)}.overflow-left{left:20px;top:50%;transform:translateY(-50%)}.overflow-left:hover{transform:translateY(-50%) scale(1.1)}.overflow-right{right:20px;top:50%;transform:translateY(-50%)}.overflow-right:hover{transform:translateY(-50%) scale(1.1)}.overflow-top{top:20px;left:50%;transform:translate(-50%)}.overflow-top:hover{transform:translate(-50%) scale(1.1)}.overflow-bottom{bottom:20px;left:50%;transform:translate(-50%)}.overflow-bottom:hover{transform:translate(-50%) scale(1.1)}.viewport-page{width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:auto;padding:16px;box-sizing:border-box}.page-canvas{position:relative;width:100%;max-width:1400px;aspect-ratio:16 / 9;background:#1a1a1a;border-radius:4px;overflow:hidden}.page-stage{position:relative;width:100%;max-width:1400px;aspect-ratio:16 / 9;overflow:hidden;border-radius:4px}.page-stage .page-canvas{max-width:none;height:100%;aspect-ratio:auto}.pt-frame{position:relative;z-index:1;width:100%;height:100%}.pt-frame-leave{position:absolute;inset:0;z-index:0;pointer-events:none}.pt-frame-leave--above{z-index:2}.pt-frame.t-fade-in,.pt-frame.t-zoom-in,.pt-frame.t-from-right,.pt-frame.t-from-left,.pt-frame.t-from-bottom,.pt-frame.t-from-top{will-change:transform,opacity}.t-fade-out{animation:pw-t-fade-out var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}.t-zoom-out{animation:pw-t-zoom-out var(--pw-t-duration, .4s) var(--pw-t-easing, ease) both}@keyframes pw-t-fade-out{0%{opacity:1}to{opacity:0}}@keyframes pw-t-zoom-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(1.06)}}.viewport-page .panel-container{position:absolute;overflow:hidden;background:#000;border:1px solid rgba(255,255,255,.1);border-radius:2px;transition:transform .2s ease,border-color .2s ease,box-shadow .2s ease;transform-origin:center center;outline:none}.viewport-page .panel-container:hover{border-color:#ffffff4d;cursor:pointer}.viewport-page .panel-container.panel-focused{border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d74d,0 4px 12px #0006;z-index:9999!important}.focus-rect{position:absolute;inset:-4px;border:3px solid rgba(0,120,215,.9);border-radius:4px;pointer-events:none;animation:focusPulse 1.5s ease-in-out infinite}@keyframes focusPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.7;transform:scale(1.02)}}@media (prefers-reduced-motion: reduce){.focus-rect{animation:none}.viewport-page .panel-container{transition:none}}.viewport-page .panel-content{position:relative;width:100%;height:100%}@media (max-width: 1024px){.page-canvas,.page-stage{max-width:900px}}@media (max-width: 768px){.viewport-page{padding:8px}.page-canvas,.page-stage{max-width:100%}}.nav-arrow{position:absolute;top:50%;transform:translateY(-50%);width:60px;height:120px;background:#0006;border:none;color:#fff;font-size:48px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s ease;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);z-index:50;animation:fadeInArrow .2s ease}.nav-arrow:hover{background:#0009;width:70px}.nav-arrow:active{background:#000000b3;transform:translateY(-50%) scale(.95)}.nav-arrow-left{left:0;border-radius:0 8px 8px 0}.nav-arrow-right{right:0;border-radius:8px 0 0 8px}.arrow-icon{font-size:48px;line-height:1;font-weight:300}@keyframes fadeInArrow{0%{opacity:0}to{opacity:1}}@media (prefers-reduced-motion: reduce){.nav-arrow{animation:none;transition:none}}@media (max-width: 768px){.nav-arrow{width:50px;height:100px;font-size:36px}.nav-arrow:hover{width:55px}.arrow-icon{font-size:36px}}\n"] }]
|
|
12236
12236
|
}], propDecorators: { panel: [{
|
|
12237
12237
|
type: Input
|
|
12238
12238
|
}], page: [{
|
|
@@ -13416,11 +13416,11 @@ class CanvasStageComponent {
|
|
|
13416
13416
|
return Math.hypot(points[1].x - points[0].x, points[1].y - points[0].y);
|
|
13417
13417
|
}
|
|
13418
13418
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CanvasStageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
13419
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: CanvasStageComponent, isStandalone: true, selector: "pw-canvas-stage", inputs: { canvas: "canvas", panels: "panels", graph: "graph", currentPanelId: "currentPanelId", visitedPanelIds: "visitedPanelIds", locale: "locale", variableContext: "variableContext", speechEnabled: "speechEnabled", workBalloonConfig: "workBalloonConfig", characters: "characters", reducedMotion: "reducedMotion", interactive: "interactive", preload: "preload" }, outputs: { panelTap: "panelTap", navigateNext: "navigateNext", navigatePrevious: "navigatePrevious", viewportClick: "viewportClick", hotspotActivate: "hotspotActivate", cameraChange: "cameraChange" }, usesOnChanges: true, ngImport: i0, template: "<div\
|
|
13419
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: CanvasStageComponent, isStandalone: true, selector: "pw-canvas-stage", inputs: { canvas: "canvas", panels: "panels", graph: "graph", currentPanelId: "currentPanelId", visitedPanelIds: "visitedPanelIds", locale: "locale", variableContext: "variableContext", speechEnabled: "speechEnabled", workBalloonConfig: "workBalloonConfig", characters: "characters", reducedMotion: "reducedMotion", interactive: "interactive", preload: "preload" }, outputs: { panelTap: "panelTap", navigateNext: "navigateNext", navigatePrevious: "navigatePrevious", viewportClick: "viewportClick", hotspotActivate: "hotspotActivate", cameraChange: "cameraChange" }, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"canvas-viewport\"\n [class.reduced-motion]=\"reducedMotion\"\n [style]=\"backgroundStyle()\"\n role=\"region\"\n aria-label=\"Canvas viewport\"\n tabindex=\"0\"\n (pointerdown)=\"onPointerDown($event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n (wheel)=\"onWheel($event)\">\n\n <div class=\"canvas-world\" [style.transform]=\"worldTransform\">\n\n <!-- Tiled/stretched backdrop covering the canvas bounds -->\n @if (worldBackgroundStyle(); as bgStyle) {\n <div class=\"canvas-backdrop\" [style]=\"bgStyle\" aria-hidden=\"true\"></div>\n }\n\n <!-- Non-panel set dressing -->\n @for (decoration of visibleDecorations; track decoration.assetId + decoration.x + decoration.y) {\n <img\n class=\"canvas-decoration\"\n [src]=\"assetUrl(decoration.assetId)\"\n [style]=\"decorationStyle(decoration)\"\n alt=\"\"\n aria-hidden=\"true\"\n loading=\"lazy\" />\n }\n\n <!-- Placed panels (virtualized) -->\n @for (entry of stagePlacements; track trackPlacement($index, entry)) {\n <div\n class=\"canvas-panel\"\n [class.current]=\"isCurrent(entry.panelId)\"\n [class.unrevealed]=\"!entry.revealed\"\n [style]=\"placementStyle(entry)\"\n [attr.data-panel-id]=\"entry.panelId\"\n [attr.aria-hidden]=\"isCurrent(entry.panelId) ? null : true\"\n [attr.inert]=\"isCurrent(entry.panelId) ? null : ''\">\n\n @if (entry.revealed) {\n @for (layer of entry.panel.layers; track layer.id) {\n <pw-layer-renderer\n [layer]=\"layer\"\n [locale]=\"locale\"\n viewMode=\"panel\"\n [viewActive]=\"isCurrent(entry.panelId)\"\n [reducedMotion]=\"reducedMotion\"\n [targetWidth]=\"targetWidthFor(entry.panelId)\"\n [panelId]=\"entry.panelId\">\n </pw-layer-renderer>\n }\n\n <!-- Hotspots are interactive on the CURRENT panel (the one the\n camera frames); other panels show them render-only, and a\n tap there keeps its panel-navigation meaning. -->\n @if (entry.panel.hotspots && entry.panel.hotspots.length > 0) {\n <pw-hotspots-overlay\n [hotspots]=\"entry.panel.hotspots\"\n [locale]=\"locale\"\n [containerWidth]=\"entry.placement.w\"\n [containerHeight]=\"entry.placement.h\"\n [context]=\"variableContext\"\n [interactive]=\"interactive && isCurrent(entry.panelId)\"\n (hotspotActivate)=\"hotspotActivate.emit({ hotspot: $event.hotspot, x: $event.x, y: $event.y, panelId: entry.panelId })\">\n </pw-hotspots-overlay>\n }\n\n @if (speechEnabled && entry.panel.speechBubbles && entry.panel.speechBubbles.length > 0) {\n <pw-speech-bubbles\n [bubbles]=\"entry.panel.speechBubbles\"\n [locale]=\"locale\"\n [containerWidth]=\"entry.placement.w\"\n [containerHeight]=\"entry.placement.h\"\n [readingScale]=\"getReadingScale()\"\n [workBalloonConfig]=\"workBalloonConfig\"\n [characters]=\"characters\">\n </pw-speech-bubbles>\n }\n } @else {\n <!-- Unrevealed panel: dark veil, no content (spoiler protection) -->\n <div class=\"canvas-panel-veil\" aria-hidden=\"true\"></div>\n }\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.canvas-viewport{position:relative;width:100%;height:100%;overflow:hidden;background-color:#101014;touch-action:none;cursor:grab;outline:none}.canvas-viewport:active{cursor:grabbing}.canvas-world{position:absolute;top:0;left:0;width:0;height:0;transform-origin:0 0;will-change:transform}.canvas-backdrop{position:absolute;pointer-events:none}.canvas-decoration{position:absolute;pointer-events:none;max-width:none}.canvas-panel{position:absolute;overflow:hidden;background:#000;contain:layout paint;content-visibility:auto;box-shadow:0 8px 40px #0000008c}.canvas-panel.current{box-shadow:0 0 0 2px #ffffff2e,0 8px 48px #0009}.canvas-panel-veil{position:absolute;inset:0;background:radial-gradient(ellipse at center,#1e1e26eb,#0a0a0efa)}.reduced-motion .canvas-world{will-change:auto}\n"], dependencies: [{ kind: "component", type: LayerRendererComponent, selector: "pw-layer-renderer", inputs: ["layer", "locale", "baseUrl", "targetWidth", "altText", "viewMode", "viewActive", "reducedMotion", "panelId", "placementId", "readingOrderIndex", "placementZ", "placementY", "placementX"], outputs: ["layerClick"] }, { kind: "component", type: SpeechBubblesComponent, selector: "pw-speech-bubbles", inputs: ["bubbles", "locale", "containerWidth", "containerHeight", "readingScale", "workBalloonConfig", "characters"], outputs: ["bubbleClick", "bubbleAudioPlay"] }, { kind: "component", type: HotspotsOverlayComponent, selector: "pw-hotspots-overlay", inputs: ["hotspots", "locale", "containerWidth", "containerHeight", "context", "interactive", "showIndicators"], outputs: ["hotspotActivate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
13420
13420
|
}
|
|
13421
13421
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CanvasStageComponent, decorators: [{
|
|
13422
13422
|
type: Component,
|
|
13423
|
-
args: [{ selector: 'pw-canvas-stage', imports: [LayerRendererComponent, SpeechBubblesComponent, HotspotsOverlayComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\
|
|
13423
|
+
args: [{ selector: 'pw-canvas-stage', imports: [LayerRendererComponent, SpeechBubblesComponent, HotspotsOverlayComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"canvas-viewport\"\n [class.reduced-motion]=\"reducedMotion\"\n [style]=\"backgroundStyle()\"\n role=\"region\"\n aria-label=\"Canvas viewport\"\n tabindex=\"0\"\n (pointerdown)=\"onPointerDown($event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n (wheel)=\"onWheel($event)\">\n\n <div class=\"canvas-world\" [style.transform]=\"worldTransform\">\n\n <!-- Tiled/stretched backdrop covering the canvas bounds -->\n @if (worldBackgroundStyle(); as bgStyle) {\n <div class=\"canvas-backdrop\" [style]=\"bgStyle\" aria-hidden=\"true\"></div>\n }\n\n <!-- Non-panel set dressing -->\n @for (decoration of visibleDecorations; track decoration.assetId + decoration.x + decoration.y) {\n <img\n class=\"canvas-decoration\"\n [src]=\"assetUrl(decoration.assetId)\"\n [style]=\"decorationStyle(decoration)\"\n alt=\"\"\n aria-hidden=\"true\"\n loading=\"lazy\" />\n }\n\n <!-- Placed panels (virtualized) -->\n @for (entry of stagePlacements; track trackPlacement($index, entry)) {\n <div\n class=\"canvas-panel\"\n [class.current]=\"isCurrent(entry.panelId)\"\n [class.unrevealed]=\"!entry.revealed\"\n [style]=\"placementStyle(entry)\"\n [attr.data-panel-id]=\"entry.panelId\"\n [attr.aria-hidden]=\"isCurrent(entry.panelId) ? null : true\"\n [attr.inert]=\"isCurrent(entry.panelId) ? null : ''\">\n\n @if (entry.revealed) {\n @for (layer of entry.panel.layers; track layer.id) {\n <pw-layer-renderer\n [layer]=\"layer\"\n [locale]=\"locale\"\n viewMode=\"panel\"\n [viewActive]=\"isCurrent(entry.panelId)\"\n [reducedMotion]=\"reducedMotion\"\n [targetWidth]=\"targetWidthFor(entry.panelId)\"\n [panelId]=\"entry.panelId\">\n </pw-layer-renderer>\n }\n\n <!-- Hotspots are interactive on the CURRENT panel (the one the\n camera frames); other panels show them render-only, and a\n tap there keeps its panel-navigation meaning. -->\n @if (entry.panel.hotspots && entry.panel.hotspots.length > 0) {\n <pw-hotspots-overlay\n [hotspots]=\"entry.panel.hotspots\"\n [locale]=\"locale\"\n [containerWidth]=\"entry.placement.w\"\n [containerHeight]=\"entry.placement.h\"\n [context]=\"variableContext\"\n [interactive]=\"interactive && isCurrent(entry.panelId)\"\n (hotspotActivate)=\"hotspotActivate.emit({ hotspot: $event.hotspot, x: $event.x, y: $event.y, panelId: entry.panelId })\">\n </pw-hotspots-overlay>\n }\n\n @if (speechEnabled && entry.panel.speechBubbles && entry.panel.speechBubbles.length > 0) {\n <pw-speech-bubbles\n [bubbles]=\"entry.panel.speechBubbles\"\n [locale]=\"locale\"\n [containerWidth]=\"entry.placement.w\"\n [containerHeight]=\"entry.placement.h\"\n [readingScale]=\"getReadingScale()\"\n [workBalloonConfig]=\"workBalloonConfig\"\n [characters]=\"characters\">\n </pw-speech-bubbles>\n }\n } @else {\n <!-- Unrevealed panel: dark veil, no content (spoiler protection) -->\n <div class=\"canvas-panel-veil\" aria-hidden=\"true\"></div>\n }\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.canvas-viewport{position:relative;width:100%;height:100%;overflow:hidden;background-color:#101014;touch-action:none;cursor:grab;outline:none}.canvas-viewport:active{cursor:grabbing}.canvas-world{position:absolute;top:0;left:0;width:0;height:0;transform-origin:0 0;will-change:transform}.canvas-backdrop{position:absolute;pointer-events:none}.canvas-decoration{position:absolute;pointer-events:none;max-width:none}.canvas-panel{position:absolute;overflow:hidden;background:#000;contain:layout paint;content-visibility:auto;box-shadow:0 8px 40px #0000008c}.canvas-panel.current{box-shadow:0 0 0 2px #ffffff2e,0 8px 48px #0009}.canvas-panel-veil{position:absolute;inset:0;background:radial-gradient(ellipse at center,#1e1e26eb,#0a0a0efa)}.reduced-motion .canvas-world{will-change:auto}\n"] }]
|
|
13424
13424
|
}], propDecorators: { canvas: [{
|
|
13425
13425
|
type: Input
|
|
13426
13426
|
}], panels: [{
|
|
@@ -13735,11 +13735,11 @@ class ToolbarComponent {
|
|
|
13735
13735
|
this.close.emit();
|
|
13736
13736
|
}
|
|
13737
13737
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
13738
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: ToolbarComponent, isStandalone: true, selector: "pw-toolbar", inputs: { locale: "locale", availableLocales: "availableLocales", viewMode: "viewMode", pageViewAvailable: "pageViewAvailable", speechEnabled: "speechEnabled", audioEnabled: "audioEnabled", sfxEnabled: "sfxEnabled", autoplayEnabled: "autoplayEnabled", secondsPerPanel: "secondsPerPanel", autoplayProgress: "autoplayProgress", thumbnailsVisible: "thumbnailsVisible", hasAlternatives: "hasAlternatives", hasBranches: "hasBranches", liked: "liked", bookmarked: "bookmarked", showSocial: "showSocial", visible: "visible" }, outputs: { toggleView: "toggleView", localeChange: "localeChange", toggleSpeech: "toggleSpeech", toggleAudio: "toggleAudio", toggleSfx: "toggleSfx", toggleAutoplay: "toggleAutoplay", secondsPerPanelChange: "secondsPerPanelChange", toggleThumbnails: "toggleThumbnails", openToc: "openToc", openSettings: "openSettings", openCharacters: "openCharacters", cycleAlternative: "cycleAlternative", showBranches: "showBranches", openExtras: "openExtras", like: "like", bookmark: "bookmark", share: "share", openComments: "openComments", close: "close", openLanguage: "openLanguage" }, ngImport: i0, template: "<div class=\"toolbar-container\" [class.visible]=\"visible\">\r\n <div class=\"toolbar\">\r\n <!-- Left section: View and navigation controls -->\r\n <div class=\"toolbar-section toolbar-left\">\r\n <!-- View toggle (page/panel) -->\r\n <button\r\n class=\"toolbar-btn\"\r\n [class.disabled]=\"!pageViewAvailable\"\r\n [disabled]=\"!pageViewAvailable\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.view_toggle' | translate\"\r\n [attr.title]=\"'toolbar.view_toggle' | translate\"\r\n (click)=\"onToggleView()\">\r\n <span class=\"btn-icon\"><pw-icon [name]=\"viewMode === 'page' ? 'lucideLayoutGrid' : viewMode === 'canvas' ? 'lucideGlobe' : 'lucideSquare'\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.view' | translate }}</span>\r\n </button>\r\n\r\n <!-- Language selector -->\r\n @if (availableLocales.length > 1) {\r\n <button\r\n class=\"toolbar-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.select_language' | translate\"\r\n (click)=\"openLanguage.emit()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideGlobe\" /></span>\r\n <span class=\"btn-label\">{{ locale }}</span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Center section: Content controls -->\r\n <div class=\"toolbar-section toolbar-center\">\r\n <!-- Speech bubbles toggle -->\r\n <button\r\n class=\"toolbar-btn toggle-btn\"\r\n [class.active]=\"speechEnabled\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.speech_toggle' | translate\"\r\n [attr.aria-pressed]=\"speechEnabled\"\r\n [attr.title]=\"'toolbar.speech_toggle' | translate\"\r\n (click)=\"onToggleSpeech()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideMessageSquare\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.speech' | translate }}</span>\r\n </button>\r\n\r\n <!-- Audio toggle -->\r\n <button\r\n class=\"toolbar-btn toggle-btn\"\r\n [class.active]=\"audioEnabled\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.audio_toggle' | translate\"\r\n [attr.aria-pressed]=\"audioEnabled\"\r\n [attr.title]=\"'toolbar.audio_toggle' | translate\"\r\n (click)=\"onToggleAudio()\">\r\n <span class=\"btn-icon\"><pw-icon [name]=\"audioEnabled ? 'lucideVolume2' : 'lucideVolumeX'\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.audio' | translate }}</span>\r\n </button>\r\n\r\n <!-- SFX toggle -->\r\n <button\r\n class=\"toolbar-btn toggle-btn\"\r\n [class.active]=\"sfxEnabled\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.sfx_toggle' | translate\"\r\n [attr.aria-pressed]=\"sfxEnabled\"\r\n [attr.title]=\"'toolbar.sfx_toggle' | translate\"\r\n (click)=\"onToggleSfx()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideMusic\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.sfx' | translate }}</span>\r\n </button>\r\n\r\n <!-- Autoplay control -->\r\n <div class=\"toolbar-dropdown\">\r\n <button\r\n class=\"toolbar-btn toggle-btn autoplay-btn\"\r\n [class.active]=\"autoplayEnabled\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.autoplay_toggle' | translate\"\r\n [attr.aria-pressed]=\"autoplayEnabled\"\r\n [attr.title]=\"'toolbar.autoplay_toggle' | translate\"\r\n (click)=\"onToggleAutoplay()\">\r\n <span class=\"btn-icon\"><pw-icon [name]=\"autoplayEnabled ? 'lucidePause' : 'lucidePlay'\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.autoplay' | translate }}</span>\r\n @if (autoplayEnabled) {\r\n <div class=\"autoplay-progress-bar\">\r\n <div class=\"autoplay-progress-fill\" [style.width.%]=\"autoplayProgress\"></div>\r\n </div>\r\n }\r\n </button>\r\n\r\n @if (autoplayEnabled) {\r\n <div class=\"autoplay-controls\">\r\n <button\r\n class=\"control-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.decrease_speed' | translate\"\r\n (click)=\"adjustSecondsPerPanel(-1)\">\r\n -\r\n </button>\r\n <span class=\"control-value\">{{ 'toolbar.seconds' | translate: {value: secondsPerPanel} }}</span>\r\n <button\r\n class=\"control-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.increase_speed' | translate\"\r\n (click)=\"adjustSecondsPerPanel(1)\">\r\n +\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Thumbnails toggle -->\r\n <button\r\n class=\"toolbar-btn toggle-btn\"\r\n [class.active]=\"thumbnailsVisible\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.thumbs_toggle' | translate\"\r\n [attr.aria-pressed]=\"thumbnailsVisible\"\r\n [attr.title]=\"'toolbar.thumbs_toggle' | translate\"\r\n (click)=\"onToggleThumbnails()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideImages\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.thumbs' | translate }}</span>\r\n </button>\r\n </div>\r\n\r\n <!-- Right section: Navigation and info controls -->\r\n <div class=\"toolbar-section toolbar-right\">\r\n <!-- Table of Contents -->\r\n <button\r\n class=\"toolbar-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.toc_open' | translate\"\r\n [attr.title]=\"'toolbar.toc_open' | translate\"\r\n (click)=\"onOpenToc()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideList\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.toc' | translate }}</span>\r\n </button>\r\n\r\n <!-- Settings -->\r\n <button\r\n class=\"toolbar-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.settings_open' | translate\"\r\n [attr.title]=\"'toolbar.settings_open' | translate\"\r\n (click)=\"onOpenSettings()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideSettings\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.settings' | translate }}</span>\r\n </button>\r\n\r\n <!-- Characters -->\r\n <button\r\n class=\"toolbar-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.characters_open' | translate\"\r\n [attr.title]=\"'toolbar.characters_open' | translate\"\r\n (click)=\"onOpenCharacters()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideUsers\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.characters' | translate }}</span>\r\n </button>\r\n\r\n <!-- Alternatives (if available) -->\r\n @if (hasAlternatives) {\r\n <button\r\n class=\"toolbar-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.alt_label' | translate\"\r\n [attr.title]=\"'toolbar.alt_cycle' | translate\"\r\n (click)=\"onCycleAlternative()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideRefreshCw\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.alt' | translate }}</span>\r\n </button>\r\n }\r\n\r\n <!-- Branch indicator (if choices ahead) -->\r\n @if (hasBranches) {\r\n <button\r\n class=\"toolbar-btn branch-indicator\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.choices_label' | translate\"\r\n [attr.title]=\"'toolbar.choices_view' | translate\"\r\n (click)=\"onShowBranches()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideSplit\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.choices' | translate }}</span>\r\n </button>\r\n }\r\n\r\n <!-- Extras -->\r\n <button\r\n class=\"toolbar-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.extras' | translate\"\r\n [attr.title]=\"'toolbar.extras_open' | translate\"\r\n (click)=\"onOpenExtras()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideSparkles\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.extras' | translate }}</span>\r\n </button>\r\n </div>\r\n\r\n <!-- Social cluster (rightmost) -->\r\n @if (showSocial) {\r\n <div class=\"toolbar-section toolbar-social\">\r\n <button\r\n class=\"toolbar-btn icon-only\"\r\n type=\"button\"\r\n [class.active]=\"liked\"\r\n [attr.aria-pressed]=\"liked\"\r\n [attr.aria-label]=\"'toolbar.like' | translate\"\r\n [attr.title]=\"(liked ? 'toolbar.like_remove' : 'toolbar.like_work') | translate\"\r\n (click)=\"onLike()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideHeart\" /></span>\r\n </button>\r\n\r\n <button\r\n class=\"toolbar-btn icon-only\"\r\n type=\"button\"\r\n [class.active]=\"bookmarked\"\r\n [attr.aria-pressed]=\"bookmarked\"\r\n [attr.aria-label]=\"'toolbar.bookmark' | translate\"\r\n [attr.title]=\"(bookmarked ? 'toolbar.bookmark_remove' : 'toolbar.bookmark_panel') | translate\"\r\n (click)=\"onBookmark()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideBookmark\" /></span>\r\n </button>\r\n\r\n <button\r\n class=\"toolbar-btn icon-only\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.share' | translate\"\r\n [attr.title]=\"'toolbar.share_panel' | translate\"\r\n (click)=\"onShare()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideShare2\" /></span>\r\n </button>\r\n\r\n <button\r\n class=\"toolbar-btn icon-only\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.comments' | translate\"\r\n [attr.title]=\"'toolbar.comments_view' | translate\"\r\n (click)=\"onOpenComments()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideMessageCircle\" /></span>\r\n </button>\r\n </div>\r\n }\r\n\r\n <!-- Close button -->\r\n <button\r\n class=\"toolbar-btn close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.close' | translate\"\r\n [attr.title]=\"'toolbar.close' | translate\"\r\n (click)=\"onClose()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideX\" /></span>\r\n </button>\r\n </div>\r\n</div>\r\n", styles: [".toolbar-container{position:fixed;bottom:0;left:0;right:0;z-index:100;background:linear-gradient(to top,rgba(0,0,0,.9),rgba(0,0,0,.7) 70%,transparent);padding:24px 16px 16px;transform:translateY(100%);opacity:0;transition:all .3s cubic-bezier(.4,0,.2,1);pointer-events:none;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);overflow:visible}.toolbar-container.visible{transform:translateY(0);opacity:1;pointer-events:auto;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}.toolbar{display:flex;align-items:center;gap:8px;max-width:1400px;margin:0 auto;overflow-x:auto;overflow-y:visible;padding:8px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.3) transparent}.toolbar::-webkit-scrollbar{height:6px}.toolbar::-webkit-scrollbar-track{background:transparent}.toolbar::-webkit-scrollbar-thumb{background:#ffffff4d;border-radius:3px}.toolbar-section{display:flex;align-items:center;gap:6px;flex-shrink:0}.toolbar-left{margin-right:8px}.toolbar-center{flex:1 0 auto;justify-content:center}.toolbar-right{margin-left:8px}.toolbar-social{margin-left:16px;padding-left:16px;border-left:1px solid rgba(255,255,255,.2)}.toolbar-btn{display:flex;align-items:center;gap:6px;padding:8px 12px;min-height:40px;font-size:14px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease;white-space:nowrap;flex-shrink:0}.toolbar-btn:hover{background:#fff3;border-color:#ffffff4d;transform:translateY(-1px)}.toolbar-btn:active{background:#ffffff26;transform:translateY(0)}.toolbar-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}.toolbar-btn:disabled:hover{background:#ffffff1a;border-color:#fff3}.toolbar-btn.toggle-btn.active{background:#0078d7cc;border-color:#0078d7}.toolbar-btn.toggle-btn.active:hover{background:#0078d7e6}.toolbar-btn.icon-only{padding:8px;min-width:40px;justify-content:center}.toolbar-btn.icon-only .btn-label{display:none}.toolbar-btn.close-btn{margin-left:8px;background:#ff00004d;border-color:#ff000080}.toolbar-btn.close-btn:hover{background:#ff000080;border-color:#ff0000b3}.toolbar-btn.branch-indicator{background:#ffa5004d;border-color:#ffa50080;animation:pulse 2s ease-in-out infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.7}}.btn-icon{font-size:18px;line-height:1;display:flex;align-items:center;justify-content:center;flex-shrink:0}.btn-label{font-size:13px;line-height:1}.toolbar-dropdown{position:relative;display:flex;flex-direction:column;align-items:stretch;gap:8px}.dropdown-menu{position:absolute;bottom:100%;left:0;margin-bottom:8px;min-width:120px;background:#1e1e1ef2;border:1px solid rgba(255,255,255,.2);border-radius:6px;padding:4px;box-shadow:0 4px 12px #0006;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);z-index:1000;animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.dropdown-item{display:block;width:100%;padding:8px 12px;font-size:14px;color:#ffffffe6;background:transparent;border:none;border-radius:4px;cursor:pointer;text-align:left;transition:background .2s ease}.dropdown-item:hover{background:#ffffff1a}.dropdown-item.active{background:#0078d780;font-weight:600}.autoplay-controls{display:flex;align-items:center;gap:6px;padding:6px 8px;background:#0006;border:1px solid rgba(255,255,255,.2);border-radius:6px}.control-btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:4px;cursor:pointer;transition:all .2s ease}.control-btn:hover{background:#fff3}.control-btn:active{background:#ffffff26;transform:scale(.95)}.control-value{min-width:40px;text-align:center;font-size:13px;font-weight:600;color:#ffffffe6}@media (max-width: 768px){.toolbar-container{padding:16px 8px 8px}.toolbar{gap:4px;padding:4px}.toolbar-section{gap:4px}.toolbar-btn{padding:6px 10px;min-height:36px;font-size:12px}.btn-icon{font-size:16px}.btn-label{font-size:11px}.toolbar-center .btn-label,.toolbar-right .btn-label{display:none}.toolbar-btn{padding:6px 8px;min-width:36px;justify-content:center}.toolbar-left .toolbar-btn{padding:6px 10px}.toolbar-left .btn-label{display:inline}.autoplay-controls{padding:4px 6px;gap:4px}.control-btn{width:24px;height:24px;font-size:14px}.control-value{min-width:32px;font-size:11px}.toolbar-social{margin-left:8px;padding-left:8px}}@media (min-width: 769px) and (max-width: 1024px){.toolbar-btn{padding:7px 11px;min-height:38px}.btn-icon{font-size:17px}.btn-label{font-size:12px}}@media (prefers-reduced-motion: reduce){.toolbar-container,.toolbar-btn,.dropdown-menu,.dropdown-item,.control-btn{transition:none;animation:none}.toolbar-container.visible{animation:none}.toolbar-btn:hover{transform:none}.toolbar-btn.branch-indicator{animation:none}}.toolbar-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}.dropdown-item:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:-2px}@media (prefers-contrast: high){.toolbar-btn{border-width:2px}.toolbar-btn.toggle-btn.active{background:#0078d7}}.autoplay-btn{position:relative;overflow:visible}.autoplay-progress-bar{position:absolute;bottom:-4px;left:4px;right:4px;height:3px;background:#fff3;border-radius:2px;overflow:hidden}.autoplay-progress-fill{height:100%;background:linear-gradient(90deg,#0078d7cc,#00b4ffe6);border-radius:2px;transition:width .1s linear;box-shadow:0 0 8px #00b4ff80}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
13738
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: ToolbarComponent, isStandalone: true, selector: "pw-toolbar", inputs: { locale: "locale", availableLocales: "availableLocales", viewMode: "viewMode", pageViewAvailable: "pageViewAvailable", speechEnabled: "speechEnabled", audioEnabled: "audioEnabled", sfxEnabled: "sfxEnabled", autoplayEnabled: "autoplayEnabled", secondsPerPanel: "secondsPerPanel", autoplayProgress: "autoplayProgress", thumbnailsVisible: "thumbnailsVisible", hasAlternatives: "hasAlternatives", hasBranches: "hasBranches", liked: "liked", bookmarked: "bookmarked", showSocial: "showSocial", visible: "visible" }, outputs: { toggleView: "toggleView", localeChange: "localeChange", toggleSpeech: "toggleSpeech", toggleAudio: "toggleAudio", toggleSfx: "toggleSfx", toggleAutoplay: "toggleAutoplay", secondsPerPanelChange: "secondsPerPanelChange", toggleThumbnails: "toggleThumbnails", openToc: "openToc", openSettings: "openSettings", openCharacters: "openCharacters", cycleAlternative: "cycleAlternative", showBranches: "showBranches", openExtras: "openExtras", like: "like", bookmark: "bookmark", share: "share", openComments: "openComments", close: "close", openLanguage: "openLanguage" }, ngImport: i0, template: "<div class=\"toolbar-container\" [class.visible]=\"visible\">\n <div class=\"toolbar\">\n <!-- Left section: View and navigation controls -->\n <div class=\"toolbar-section toolbar-left\">\n <!-- View toggle (page/panel) -->\n <button\n class=\"toolbar-btn\"\n [class.disabled]=\"!pageViewAvailable\"\n [disabled]=\"!pageViewAvailable\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.view_toggle' | translate\"\n [attr.title]=\"'toolbar.view_toggle' | translate\"\n (click)=\"onToggleView()\">\n <span class=\"btn-icon\"><pw-icon [name]=\"viewMode === 'page' ? 'lucideLayoutGrid' : viewMode === 'canvas' ? 'lucideGlobe' : 'lucideSquare'\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.view' | translate }}</span>\n </button>\n\n <!-- Language selector -->\n @if (availableLocales.length > 1) {\n <button\n class=\"toolbar-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.select_language' | translate\"\n (click)=\"openLanguage.emit()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideGlobe\" /></span>\n <span class=\"btn-label\">{{ locale }}</span>\n </button>\n }\n </div>\n\n <!-- Center section: Content controls -->\n <div class=\"toolbar-section toolbar-center\">\n <!-- Speech bubbles toggle -->\n <button\n class=\"toolbar-btn toggle-btn\"\n [class.active]=\"speechEnabled\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.speech_toggle' | translate\"\n [attr.aria-pressed]=\"speechEnabled\"\n [attr.title]=\"'toolbar.speech_toggle' | translate\"\n (click)=\"onToggleSpeech()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideMessageSquare\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.speech' | translate }}</span>\n </button>\n\n <!-- Audio toggle -->\n <button\n class=\"toolbar-btn toggle-btn\"\n [class.active]=\"audioEnabled\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.audio_toggle' | translate\"\n [attr.aria-pressed]=\"audioEnabled\"\n [attr.title]=\"'toolbar.audio_toggle' | translate\"\n (click)=\"onToggleAudio()\">\n <span class=\"btn-icon\"><pw-icon [name]=\"audioEnabled ? 'lucideVolume2' : 'lucideVolumeX'\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.audio' | translate }}</span>\n </button>\n\n <!-- SFX toggle -->\n <button\n class=\"toolbar-btn toggle-btn\"\n [class.active]=\"sfxEnabled\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.sfx_toggle' | translate\"\n [attr.aria-pressed]=\"sfxEnabled\"\n [attr.title]=\"'toolbar.sfx_toggle' | translate\"\n (click)=\"onToggleSfx()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideMusic\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.sfx' | translate }}</span>\n </button>\n\n <!-- Autoplay control -->\n <div class=\"toolbar-dropdown\">\n <button\n class=\"toolbar-btn toggle-btn autoplay-btn\"\n [class.active]=\"autoplayEnabled\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.autoplay_toggle' | translate\"\n [attr.aria-pressed]=\"autoplayEnabled\"\n [attr.title]=\"'toolbar.autoplay_toggle' | translate\"\n (click)=\"onToggleAutoplay()\">\n <span class=\"btn-icon\"><pw-icon [name]=\"autoplayEnabled ? 'lucidePause' : 'lucidePlay'\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.autoplay' | translate }}</span>\n @if (autoplayEnabled) {\n <div class=\"autoplay-progress-bar\">\n <div class=\"autoplay-progress-fill\" [style.width.%]=\"autoplayProgress\"></div>\n </div>\n }\n </button>\n\n @if (autoplayEnabled) {\n <div class=\"autoplay-controls\">\n <button\n class=\"control-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.decrease_speed' | translate\"\n (click)=\"adjustSecondsPerPanel(-1)\">\n -\n </button>\n <span class=\"control-value\">{{ 'toolbar.seconds' | translate: {value: secondsPerPanel} }}</span>\n <button\n class=\"control-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.increase_speed' | translate\"\n (click)=\"adjustSecondsPerPanel(1)\">\n +\n </button>\n </div>\n }\n </div>\n\n <!-- Thumbnails toggle -->\n <button\n class=\"toolbar-btn toggle-btn\"\n [class.active]=\"thumbnailsVisible\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.thumbs_toggle' | translate\"\n [attr.aria-pressed]=\"thumbnailsVisible\"\n [attr.title]=\"'toolbar.thumbs_toggle' | translate\"\n (click)=\"onToggleThumbnails()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideImages\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.thumbs' | translate }}</span>\n </button>\n </div>\n\n <!-- Right section: Navigation and info controls -->\n <div class=\"toolbar-section toolbar-right\">\n <!-- Table of Contents -->\n <button\n class=\"toolbar-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.toc_open' | translate\"\n [attr.title]=\"'toolbar.toc_open' | translate\"\n (click)=\"onOpenToc()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideList\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.toc' | translate }}</span>\n </button>\n\n <!-- Settings -->\n <button\n class=\"toolbar-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.settings_open' | translate\"\n [attr.title]=\"'toolbar.settings_open' | translate\"\n (click)=\"onOpenSettings()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideSettings\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.settings' | translate }}</span>\n </button>\n\n <!-- Characters -->\n <button\n class=\"toolbar-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.characters_open' | translate\"\n [attr.title]=\"'toolbar.characters_open' | translate\"\n (click)=\"onOpenCharacters()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideUsers\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.characters' | translate }}</span>\n </button>\n\n <!-- Alternatives (if available) -->\n @if (hasAlternatives) {\n <button\n class=\"toolbar-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.alt_label' | translate\"\n [attr.title]=\"'toolbar.alt_cycle' | translate\"\n (click)=\"onCycleAlternative()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideRefreshCw\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.alt' | translate }}</span>\n </button>\n }\n\n <!-- Branch indicator (if choices ahead) -->\n @if (hasBranches) {\n <button\n class=\"toolbar-btn branch-indicator\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.choices_label' | translate\"\n [attr.title]=\"'toolbar.choices_view' | translate\"\n (click)=\"onShowBranches()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideSplit\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.choices' | translate }}</span>\n </button>\n }\n\n <!-- Extras -->\n <button\n class=\"toolbar-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.extras' | translate\"\n [attr.title]=\"'toolbar.extras_open' | translate\"\n (click)=\"onOpenExtras()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideSparkles\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.extras' | translate }}</span>\n </button>\n </div>\n\n <!-- Social cluster (rightmost) -->\n @if (showSocial) {\n <div class=\"toolbar-section toolbar-social\">\n <button\n class=\"toolbar-btn icon-only\"\n type=\"button\"\n [class.active]=\"liked\"\n [attr.aria-pressed]=\"liked\"\n [attr.aria-label]=\"'toolbar.like' | translate\"\n [attr.title]=\"(liked ? 'toolbar.like_remove' : 'toolbar.like_work') | translate\"\n (click)=\"onLike()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideHeart\" /></span>\n </button>\n\n <button\n class=\"toolbar-btn icon-only\"\n type=\"button\"\n [class.active]=\"bookmarked\"\n [attr.aria-pressed]=\"bookmarked\"\n [attr.aria-label]=\"'toolbar.bookmark' | translate\"\n [attr.title]=\"(bookmarked ? 'toolbar.bookmark_remove' : 'toolbar.bookmark_panel') | translate\"\n (click)=\"onBookmark()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideBookmark\" /></span>\n </button>\n\n <button\n class=\"toolbar-btn icon-only\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.share' | translate\"\n [attr.title]=\"'toolbar.share_panel' | translate\"\n (click)=\"onShare()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideShare2\" /></span>\n </button>\n\n <button\n class=\"toolbar-btn icon-only\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.comments' | translate\"\n [attr.title]=\"'toolbar.comments_view' | translate\"\n (click)=\"onOpenComments()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideMessageCircle\" /></span>\n </button>\n </div>\n }\n\n <!-- Close button -->\n <button\n class=\"toolbar-btn close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.close' | translate\"\n [attr.title]=\"'toolbar.close' | translate\"\n (click)=\"onClose()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideX\" /></span>\n </button>\n </div>\n</div>\n", styles: [".toolbar-container{position:fixed;bottom:0;left:0;right:0;z-index:100;background:linear-gradient(to top,rgba(0,0,0,.9),rgba(0,0,0,.7) 70%,transparent);padding:24px 16px 16px;transform:translateY(100%);opacity:0;transition:all .3s cubic-bezier(.4,0,.2,1);pointer-events:none;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);overflow:visible}.toolbar-container.visible{transform:translateY(0);opacity:1;pointer-events:auto;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}.toolbar{display:flex;align-items:center;gap:8px;max-width:1400px;margin:0 auto;overflow-x:auto;overflow-y:visible;padding:8px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.3) transparent}.toolbar::-webkit-scrollbar{height:6px}.toolbar::-webkit-scrollbar-track{background:transparent}.toolbar::-webkit-scrollbar-thumb{background:#ffffff4d;border-radius:3px}.toolbar-section{display:flex;align-items:center;gap:6px;flex-shrink:0}.toolbar-left{margin-right:8px}.toolbar-center{flex:1 0 auto;justify-content:center}.toolbar-right{margin-left:8px}.toolbar-social{margin-left:16px;padding-left:16px;border-left:1px solid rgba(255,255,255,.2)}.toolbar-btn{display:flex;align-items:center;gap:6px;padding:8px 12px;min-height:40px;font-size:14px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease;white-space:nowrap;flex-shrink:0}.toolbar-btn:hover{background:#fff3;border-color:#ffffff4d;transform:translateY(-1px)}.toolbar-btn:active{background:#ffffff26;transform:translateY(0)}.toolbar-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}.toolbar-btn:disabled:hover{background:#ffffff1a;border-color:#fff3}.toolbar-btn.toggle-btn.active{background:#0078d7cc;border-color:#0078d7}.toolbar-btn.toggle-btn.active:hover{background:#0078d7e6}.toolbar-btn.icon-only{padding:8px;min-width:40px;justify-content:center}.toolbar-btn.icon-only .btn-label{display:none}.toolbar-btn.close-btn{margin-left:8px;background:#ff00004d;border-color:#ff000080}.toolbar-btn.close-btn:hover{background:#ff000080;border-color:#ff0000b3}.toolbar-btn.branch-indicator{background:#ffa5004d;border-color:#ffa50080;animation:pulse 2s ease-in-out infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.7}}.btn-icon{font-size:18px;line-height:1;display:flex;align-items:center;justify-content:center;flex-shrink:0}.btn-label{font-size:13px;line-height:1}.toolbar-dropdown{position:relative;display:flex;flex-direction:column;align-items:stretch;gap:8px}.dropdown-menu{position:absolute;bottom:100%;left:0;margin-bottom:8px;min-width:120px;background:#1e1e1ef2;border:1px solid rgba(255,255,255,.2);border-radius:6px;padding:4px;box-shadow:0 4px 12px #0006;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);z-index:1000;animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.dropdown-item{display:block;width:100%;padding:8px 12px;font-size:14px;color:#ffffffe6;background:transparent;border:none;border-radius:4px;cursor:pointer;text-align:left;transition:background .2s ease}.dropdown-item:hover{background:#ffffff1a}.dropdown-item.active{background:#0078d780;font-weight:600}.autoplay-controls{display:flex;align-items:center;gap:6px;padding:6px 8px;background:#0006;border:1px solid rgba(255,255,255,.2);border-radius:6px}.control-btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:4px;cursor:pointer;transition:all .2s ease}.control-btn:hover{background:#fff3}.control-btn:active{background:#ffffff26;transform:scale(.95)}.control-value{min-width:40px;text-align:center;font-size:13px;font-weight:600;color:#ffffffe6}@media (max-width: 768px){.toolbar-container{padding:16px 8px 8px}.toolbar{gap:4px;padding:4px}.toolbar-section{gap:4px}.toolbar-btn{padding:6px 10px;min-height:36px;font-size:12px}.btn-icon{font-size:16px}.btn-label{font-size:11px}.toolbar-center .btn-label,.toolbar-right .btn-label{display:none}.toolbar-btn{padding:6px 8px;min-width:36px;justify-content:center}.toolbar-left .toolbar-btn{padding:6px 10px}.toolbar-left .btn-label{display:inline}.autoplay-controls{padding:4px 6px;gap:4px}.control-btn{width:24px;height:24px;font-size:14px}.control-value{min-width:32px;font-size:11px}.toolbar-social{margin-left:8px;padding-left:8px}}@media (min-width: 769px) and (max-width: 1024px){.toolbar-btn{padding:7px 11px;min-height:38px}.btn-icon{font-size:17px}.btn-label{font-size:12px}}@media (prefers-reduced-motion: reduce){.toolbar-container,.toolbar-btn,.dropdown-menu,.dropdown-item,.control-btn{transition:none;animation:none}.toolbar-container.visible{animation:none}.toolbar-btn:hover{transform:none}.toolbar-btn.branch-indicator{animation:none}}.toolbar-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}.dropdown-item:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:-2px}@media (prefers-contrast: high){.toolbar-btn{border-width:2px}.toolbar-btn.toggle-btn.active{background:#0078d7}}.autoplay-btn{position:relative;overflow:visible}.autoplay-progress-bar{position:absolute;bottom:-4px;left:4px;right:4px;height:3px;background:#fff3;border-radius:2px;overflow:hidden}.autoplay-progress-fill{height:100%;background:linear-gradient(90deg,#0078d7cc,#00b4ffe6);border-radius:2px;transition:width .1s linear;box-shadow:0 0 8px #00b4ff80}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
13739
13739
|
}
|
|
13740
13740
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ToolbarComponent, decorators: [{
|
|
13741
13741
|
type: Component,
|
|
13742
|
-
args: [{ selector: 'pw-toolbar', imports: [TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"toolbar-container\" [class.visible]=\"visible\">\r\n <div class=\"toolbar\">\r\n <!-- Left section: View and navigation controls -->\r\n <div class=\"toolbar-section toolbar-left\">\r\n <!-- View toggle (page/panel) -->\r\n <button\r\n class=\"toolbar-btn\"\r\n [class.disabled]=\"!pageViewAvailable\"\r\n [disabled]=\"!pageViewAvailable\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.view_toggle' | translate\"\r\n [attr.title]=\"'toolbar.view_toggle' | translate\"\r\n (click)=\"onToggleView()\">\r\n <span class=\"btn-icon\"><pw-icon [name]=\"viewMode === 'page' ? 'lucideLayoutGrid' : viewMode === 'canvas' ? 'lucideGlobe' : 'lucideSquare'\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.view' | translate }}</span>\r\n </button>\r\n\r\n <!-- Language selector -->\r\n @if (availableLocales.length > 1) {\r\n <button\r\n class=\"toolbar-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.select_language' | translate\"\r\n (click)=\"openLanguage.emit()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideGlobe\" /></span>\r\n <span class=\"btn-label\">{{ locale }}</span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Center section: Content controls -->\r\n <div class=\"toolbar-section toolbar-center\">\r\n <!-- Speech bubbles toggle -->\r\n <button\r\n class=\"toolbar-btn toggle-btn\"\r\n [class.active]=\"speechEnabled\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.speech_toggle' | translate\"\r\n [attr.aria-pressed]=\"speechEnabled\"\r\n [attr.title]=\"'toolbar.speech_toggle' | translate\"\r\n (click)=\"onToggleSpeech()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideMessageSquare\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.speech' | translate }}</span>\r\n </button>\r\n\r\n <!-- Audio toggle -->\r\n <button\r\n class=\"toolbar-btn toggle-btn\"\r\n [class.active]=\"audioEnabled\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.audio_toggle' | translate\"\r\n [attr.aria-pressed]=\"audioEnabled\"\r\n [attr.title]=\"'toolbar.audio_toggle' | translate\"\r\n (click)=\"onToggleAudio()\">\r\n <span class=\"btn-icon\"><pw-icon [name]=\"audioEnabled ? 'lucideVolume2' : 'lucideVolumeX'\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.audio' | translate }}</span>\r\n </button>\r\n\r\n <!-- SFX toggle -->\r\n <button\r\n class=\"toolbar-btn toggle-btn\"\r\n [class.active]=\"sfxEnabled\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.sfx_toggle' | translate\"\r\n [attr.aria-pressed]=\"sfxEnabled\"\r\n [attr.title]=\"'toolbar.sfx_toggle' | translate\"\r\n (click)=\"onToggleSfx()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideMusic\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.sfx' | translate }}</span>\r\n </button>\r\n\r\n <!-- Autoplay control -->\r\n <div class=\"toolbar-dropdown\">\r\n <button\r\n class=\"toolbar-btn toggle-btn autoplay-btn\"\r\n [class.active]=\"autoplayEnabled\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.autoplay_toggle' | translate\"\r\n [attr.aria-pressed]=\"autoplayEnabled\"\r\n [attr.title]=\"'toolbar.autoplay_toggle' | translate\"\r\n (click)=\"onToggleAutoplay()\">\r\n <span class=\"btn-icon\"><pw-icon [name]=\"autoplayEnabled ? 'lucidePause' : 'lucidePlay'\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.autoplay' | translate }}</span>\r\n @if (autoplayEnabled) {\r\n <div class=\"autoplay-progress-bar\">\r\n <div class=\"autoplay-progress-fill\" [style.width.%]=\"autoplayProgress\"></div>\r\n </div>\r\n }\r\n </button>\r\n\r\n @if (autoplayEnabled) {\r\n <div class=\"autoplay-controls\">\r\n <button\r\n class=\"control-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.decrease_speed' | translate\"\r\n (click)=\"adjustSecondsPerPanel(-1)\">\r\n -\r\n </button>\r\n <span class=\"control-value\">{{ 'toolbar.seconds' | translate: {value: secondsPerPanel} }}</span>\r\n <button\r\n class=\"control-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.increase_speed' | translate\"\r\n (click)=\"adjustSecondsPerPanel(1)\">\r\n +\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Thumbnails toggle -->\r\n <button\r\n class=\"toolbar-btn toggle-btn\"\r\n [class.active]=\"thumbnailsVisible\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.thumbs_toggle' | translate\"\r\n [attr.aria-pressed]=\"thumbnailsVisible\"\r\n [attr.title]=\"'toolbar.thumbs_toggle' | translate\"\r\n (click)=\"onToggleThumbnails()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideImages\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.thumbs' | translate }}</span>\r\n </button>\r\n </div>\r\n\r\n <!-- Right section: Navigation and info controls -->\r\n <div class=\"toolbar-section toolbar-right\">\r\n <!-- Table of Contents -->\r\n <button\r\n class=\"toolbar-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.toc_open' | translate\"\r\n [attr.title]=\"'toolbar.toc_open' | translate\"\r\n (click)=\"onOpenToc()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideList\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.toc' | translate }}</span>\r\n </button>\r\n\r\n <!-- Settings -->\r\n <button\r\n class=\"toolbar-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.settings_open' | translate\"\r\n [attr.title]=\"'toolbar.settings_open' | translate\"\r\n (click)=\"onOpenSettings()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideSettings\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.settings' | translate }}</span>\r\n </button>\r\n\r\n <!-- Characters -->\r\n <button\r\n class=\"toolbar-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.characters_open' | translate\"\r\n [attr.title]=\"'toolbar.characters_open' | translate\"\r\n (click)=\"onOpenCharacters()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideUsers\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.characters' | translate }}</span>\r\n </button>\r\n\r\n <!-- Alternatives (if available) -->\r\n @if (hasAlternatives) {\r\n <button\r\n class=\"toolbar-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.alt_label' | translate\"\r\n [attr.title]=\"'toolbar.alt_cycle' | translate\"\r\n (click)=\"onCycleAlternative()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideRefreshCw\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.alt' | translate }}</span>\r\n </button>\r\n }\r\n\r\n <!-- Branch indicator (if choices ahead) -->\r\n @if (hasBranches) {\r\n <button\r\n class=\"toolbar-btn branch-indicator\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.choices_label' | translate\"\r\n [attr.title]=\"'toolbar.choices_view' | translate\"\r\n (click)=\"onShowBranches()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideSplit\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.choices' | translate }}</span>\r\n </button>\r\n }\r\n\r\n <!-- Extras -->\r\n <button\r\n class=\"toolbar-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.extras' | translate\"\r\n [attr.title]=\"'toolbar.extras_open' | translate\"\r\n (click)=\"onOpenExtras()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideSparkles\" /></span>\r\n <span class=\"btn-label\">{{ 'toolbar.extras' | translate }}</span>\r\n </button>\r\n </div>\r\n\r\n <!-- Social cluster (rightmost) -->\r\n @if (showSocial) {\r\n <div class=\"toolbar-section toolbar-social\">\r\n <button\r\n class=\"toolbar-btn icon-only\"\r\n type=\"button\"\r\n [class.active]=\"liked\"\r\n [attr.aria-pressed]=\"liked\"\r\n [attr.aria-label]=\"'toolbar.like' | translate\"\r\n [attr.title]=\"(liked ? 'toolbar.like_remove' : 'toolbar.like_work') | translate\"\r\n (click)=\"onLike()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideHeart\" /></span>\r\n </button>\r\n\r\n <button\r\n class=\"toolbar-btn icon-only\"\r\n type=\"button\"\r\n [class.active]=\"bookmarked\"\r\n [attr.aria-pressed]=\"bookmarked\"\r\n [attr.aria-label]=\"'toolbar.bookmark' | translate\"\r\n [attr.title]=\"(bookmarked ? 'toolbar.bookmark_remove' : 'toolbar.bookmark_panel') | translate\"\r\n (click)=\"onBookmark()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideBookmark\" /></span>\r\n </button>\r\n\r\n <button\r\n class=\"toolbar-btn icon-only\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.share' | translate\"\r\n [attr.title]=\"'toolbar.share_panel' | translate\"\r\n (click)=\"onShare()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideShare2\" /></span>\r\n </button>\r\n\r\n <button\r\n class=\"toolbar-btn icon-only\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.comments' | translate\"\r\n [attr.title]=\"'toolbar.comments_view' | translate\"\r\n (click)=\"onOpenComments()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideMessageCircle\" /></span>\r\n </button>\r\n </div>\r\n }\r\n\r\n <!-- Close button -->\r\n <button\r\n class=\"toolbar-btn close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toolbar.close' | translate\"\r\n [attr.title]=\"'toolbar.close' | translate\"\r\n (click)=\"onClose()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideX\" /></span>\r\n </button>\r\n </div>\r\n</div>\r\n", styles: [".toolbar-container{position:fixed;bottom:0;left:0;right:0;z-index:100;background:linear-gradient(to top,rgba(0,0,0,.9),rgba(0,0,0,.7) 70%,transparent);padding:24px 16px 16px;transform:translateY(100%);opacity:0;transition:all .3s cubic-bezier(.4,0,.2,1);pointer-events:none;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);overflow:visible}.toolbar-container.visible{transform:translateY(0);opacity:1;pointer-events:auto;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}.toolbar{display:flex;align-items:center;gap:8px;max-width:1400px;margin:0 auto;overflow-x:auto;overflow-y:visible;padding:8px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.3) transparent}.toolbar::-webkit-scrollbar{height:6px}.toolbar::-webkit-scrollbar-track{background:transparent}.toolbar::-webkit-scrollbar-thumb{background:#ffffff4d;border-radius:3px}.toolbar-section{display:flex;align-items:center;gap:6px;flex-shrink:0}.toolbar-left{margin-right:8px}.toolbar-center{flex:1 0 auto;justify-content:center}.toolbar-right{margin-left:8px}.toolbar-social{margin-left:16px;padding-left:16px;border-left:1px solid rgba(255,255,255,.2)}.toolbar-btn{display:flex;align-items:center;gap:6px;padding:8px 12px;min-height:40px;font-size:14px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease;white-space:nowrap;flex-shrink:0}.toolbar-btn:hover{background:#fff3;border-color:#ffffff4d;transform:translateY(-1px)}.toolbar-btn:active{background:#ffffff26;transform:translateY(0)}.toolbar-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}.toolbar-btn:disabled:hover{background:#ffffff1a;border-color:#fff3}.toolbar-btn.toggle-btn.active{background:#0078d7cc;border-color:#0078d7}.toolbar-btn.toggle-btn.active:hover{background:#0078d7e6}.toolbar-btn.icon-only{padding:8px;min-width:40px;justify-content:center}.toolbar-btn.icon-only .btn-label{display:none}.toolbar-btn.close-btn{margin-left:8px;background:#ff00004d;border-color:#ff000080}.toolbar-btn.close-btn:hover{background:#ff000080;border-color:#ff0000b3}.toolbar-btn.branch-indicator{background:#ffa5004d;border-color:#ffa50080;animation:pulse 2s ease-in-out infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.7}}.btn-icon{font-size:18px;line-height:1;display:flex;align-items:center;justify-content:center;flex-shrink:0}.btn-label{font-size:13px;line-height:1}.toolbar-dropdown{position:relative;display:flex;flex-direction:column;align-items:stretch;gap:8px}.dropdown-menu{position:absolute;bottom:100%;left:0;margin-bottom:8px;min-width:120px;background:#1e1e1ef2;border:1px solid rgba(255,255,255,.2);border-radius:6px;padding:4px;box-shadow:0 4px 12px #0006;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);z-index:1000;animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.dropdown-item{display:block;width:100%;padding:8px 12px;font-size:14px;color:#ffffffe6;background:transparent;border:none;border-radius:4px;cursor:pointer;text-align:left;transition:background .2s ease}.dropdown-item:hover{background:#ffffff1a}.dropdown-item.active{background:#0078d780;font-weight:600}.autoplay-controls{display:flex;align-items:center;gap:6px;padding:6px 8px;background:#0006;border:1px solid rgba(255,255,255,.2);border-radius:6px}.control-btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:4px;cursor:pointer;transition:all .2s ease}.control-btn:hover{background:#fff3}.control-btn:active{background:#ffffff26;transform:scale(.95)}.control-value{min-width:40px;text-align:center;font-size:13px;font-weight:600;color:#ffffffe6}@media (max-width: 768px){.toolbar-container{padding:16px 8px 8px}.toolbar{gap:4px;padding:4px}.toolbar-section{gap:4px}.toolbar-btn{padding:6px 10px;min-height:36px;font-size:12px}.btn-icon{font-size:16px}.btn-label{font-size:11px}.toolbar-center .btn-label,.toolbar-right .btn-label{display:none}.toolbar-btn{padding:6px 8px;min-width:36px;justify-content:center}.toolbar-left .toolbar-btn{padding:6px 10px}.toolbar-left .btn-label{display:inline}.autoplay-controls{padding:4px 6px;gap:4px}.control-btn{width:24px;height:24px;font-size:14px}.control-value{min-width:32px;font-size:11px}.toolbar-social{margin-left:8px;padding-left:8px}}@media (min-width: 769px) and (max-width: 1024px){.toolbar-btn{padding:7px 11px;min-height:38px}.btn-icon{font-size:17px}.btn-label{font-size:12px}}@media (prefers-reduced-motion: reduce){.toolbar-container,.toolbar-btn,.dropdown-menu,.dropdown-item,.control-btn{transition:none;animation:none}.toolbar-container.visible{animation:none}.toolbar-btn:hover{transform:none}.toolbar-btn.branch-indicator{animation:none}}.toolbar-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}.dropdown-item:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:-2px}@media (prefers-contrast: high){.toolbar-btn{border-width:2px}.toolbar-btn.toggle-btn.active{background:#0078d7}}.autoplay-btn{position:relative;overflow:visible}.autoplay-progress-bar{position:absolute;bottom:-4px;left:4px;right:4px;height:3px;background:#fff3;border-radius:2px;overflow:hidden}.autoplay-progress-fill{height:100%;background:linear-gradient(90deg,#0078d7cc,#00b4ffe6);border-radius:2px;transition:width .1s linear;box-shadow:0 0 8px #00b4ff80}\n"] }]
|
|
13742
|
+
args: [{ selector: 'pw-toolbar', imports: [TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"toolbar-container\" [class.visible]=\"visible\">\n <div class=\"toolbar\">\n <!-- Left section: View and navigation controls -->\n <div class=\"toolbar-section toolbar-left\">\n <!-- View toggle (page/panel) -->\n <button\n class=\"toolbar-btn\"\n [class.disabled]=\"!pageViewAvailable\"\n [disabled]=\"!pageViewAvailable\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.view_toggle' | translate\"\n [attr.title]=\"'toolbar.view_toggle' | translate\"\n (click)=\"onToggleView()\">\n <span class=\"btn-icon\"><pw-icon [name]=\"viewMode === 'page' ? 'lucideLayoutGrid' : viewMode === 'canvas' ? 'lucideGlobe' : 'lucideSquare'\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.view' | translate }}</span>\n </button>\n\n <!-- Language selector -->\n @if (availableLocales.length > 1) {\n <button\n class=\"toolbar-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.select_language' | translate\"\n (click)=\"openLanguage.emit()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideGlobe\" /></span>\n <span class=\"btn-label\">{{ locale }}</span>\n </button>\n }\n </div>\n\n <!-- Center section: Content controls -->\n <div class=\"toolbar-section toolbar-center\">\n <!-- Speech bubbles toggle -->\n <button\n class=\"toolbar-btn toggle-btn\"\n [class.active]=\"speechEnabled\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.speech_toggle' | translate\"\n [attr.aria-pressed]=\"speechEnabled\"\n [attr.title]=\"'toolbar.speech_toggle' | translate\"\n (click)=\"onToggleSpeech()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideMessageSquare\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.speech' | translate }}</span>\n </button>\n\n <!-- Audio toggle -->\n <button\n class=\"toolbar-btn toggle-btn\"\n [class.active]=\"audioEnabled\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.audio_toggle' | translate\"\n [attr.aria-pressed]=\"audioEnabled\"\n [attr.title]=\"'toolbar.audio_toggle' | translate\"\n (click)=\"onToggleAudio()\">\n <span class=\"btn-icon\"><pw-icon [name]=\"audioEnabled ? 'lucideVolume2' : 'lucideVolumeX'\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.audio' | translate }}</span>\n </button>\n\n <!-- SFX toggle -->\n <button\n class=\"toolbar-btn toggle-btn\"\n [class.active]=\"sfxEnabled\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.sfx_toggle' | translate\"\n [attr.aria-pressed]=\"sfxEnabled\"\n [attr.title]=\"'toolbar.sfx_toggle' | translate\"\n (click)=\"onToggleSfx()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideMusic\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.sfx' | translate }}</span>\n </button>\n\n <!-- Autoplay control -->\n <div class=\"toolbar-dropdown\">\n <button\n class=\"toolbar-btn toggle-btn autoplay-btn\"\n [class.active]=\"autoplayEnabled\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.autoplay_toggle' | translate\"\n [attr.aria-pressed]=\"autoplayEnabled\"\n [attr.title]=\"'toolbar.autoplay_toggle' | translate\"\n (click)=\"onToggleAutoplay()\">\n <span class=\"btn-icon\"><pw-icon [name]=\"autoplayEnabled ? 'lucidePause' : 'lucidePlay'\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.autoplay' | translate }}</span>\n @if (autoplayEnabled) {\n <div class=\"autoplay-progress-bar\">\n <div class=\"autoplay-progress-fill\" [style.width.%]=\"autoplayProgress\"></div>\n </div>\n }\n </button>\n\n @if (autoplayEnabled) {\n <div class=\"autoplay-controls\">\n <button\n class=\"control-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.decrease_speed' | translate\"\n (click)=\"adjustSecondsPerPanel(-1)\">\n -\n </button>\n <span class=\"control-value\">{{ 'toolbar.seconds' | translate: {value: secondsPerPanel} }}</span>\n <button\n class=\"control-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.increase_speed' | translate\"\n (click)=\"adjustSecondsPerPanel(1)\">\n +\n </button>\n </div>\n }\n </div>\n\n <!-- Thumbnails toggle -->\n <button\n class=\"toolbar-btn toggle-btn\"\n [class.active]=\"thumbnailsVisible\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.thumbs_toggle' | translate\"\n [attr.aria-pressed]=\"thumbnailsVisible\"\n [attr.title]=\"'toolbar.thumbs_toggle' | translate\"\n (click)=\"onToggleThumbnails()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideImages\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.thumbs' | translate }}</span>\n </button>\n </div>\n\n <!-- Right section: Navigation and info controls -->\n <div class=\"toolbar-section toolbar-right\">\n <!-- Table of Contents -->\n <button\n class=\"toolbar-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.toc_open' | translate\"\n [attr.title]=\"'toolbar.toc_open' | translate\"\n (click)=\"onOpenToc()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideList\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.toc' | translate }}</span>\n </button>\n\n <!-- Settings -->\n <button\n class=\"toolbar-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.settings_open' | translate\"\n [attr.title]=\"'toolbar.settings_open' | translate\"\n (click)=\"onOpenSettings()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideSettings\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.settings' | translate }}</span>\n </button>\n\n <!-- Characters -->\n <button\n class=\"toolbar-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.characters_open' | translate\"\n [attr.title]=\"'toolbar.characters_open' | translate\"\n (click)=\"onOpenCharacters()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideUsers\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.characters' | translate }}</span>\n </button>\n\n <!-- Alternatives (if available) -->\n @if (hasAlternatives) {\n <button\n class=\"toolbar-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.alt_label' | translate\"\n [attr.title]=\"'toolbar.alt_cycle' | translate\"\n (click)=\"onCycleAlternative()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideRefreshCw\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.alt' | translate }}</span>\n </button>\n }\n\n <!-- Branch indicator (if choices ahead) -->\n @if (hasBranches) {\n <button\n class=\"toolbar-btn branch-indicator\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.choices_label' | translate\"\n [attr.title]=\"'toolbar.choices_view' | translate\"\n (click)=\"onShowBranches()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideSplit\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.choices' | translate }}</span>\n </button>\n }\n\n <!-- Extras -->\n <button\n class=\"toolbar-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.extras' | translate\"\n [attr.title]=\"'toolbar.extras_open' | translate\"\n (click)=\"onOpenExtras()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideSparkles\" /></span>\n <span class=\"btn-label\">{{ 'toolbar.extras' | translate }}</span>\n </button>\n </div>\n\n <!-- Social cluster (rightmost) -->\n @if (showSocial) {\n <div class=\"toolbar-section toolbar-social\">\n <button\n class=\"toolbar-btn icon-only\"\n type=\"button\"\n [class.active]=\"liked\"\n [attr.aria-pressed]=\"liked\"\n [attr.aria-label]=\"'toolbar.like' | translate\"\n [attr.title]=\"(liked ? 'toolbar.like_remove' : 'toolbar.like_work') | translate\"\n (click)=\"onLike()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideHeart\" /></span>\n </button>\n\n <button\n class=\"toolbar-btn icon-only\"\n type=\"button\"\n [class.active]=\"bookmarked\"\n [attr.aria-pressed]=\"bookmarked\"\n [attr.aria-label]=\"'toolbar.bookmark' | translate\"\n [attr.title]=\"(bookmarked ? 'toolbar.bookmark_remove' : 'toolbar.bookmark_panel') | translate\"\n (click)=\"onBookmark()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideBookmark\" /></span>\n </button>\n\n <button\n class=\"toolbar-btn icon-only\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.share' | translate\"\n [attr.title]=\"'toolbar.share_panel' | translate\"\n (click)=\"onShare()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideShare2\" /></span>\n </button>\n\n <button\n class=\"toolbar-btn icon-only\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.comments' | translate\"\n [attr.title]=\"'toolbar.comments_view' | translate\"\n (click)=\"onOpenComments()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideMessageCircle\" /></span>\n </button>\n </div>\n }\n\n <!-- Close button -->\n <button\n class=\"toolbar-btn close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toolbar.close' | translate\"\n [attr.title]=\"'toolbar.close' | translate\"\n (click)=\"onClose()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideX\" /></span>\n </button>\n </div>\n</div>\n", styles: [".toolbar-container{position:fixed;bottom:0;left:0;right:0;z-index:100;background:linear-gradient(to top,rgba(0,0,0,.9),rgba(0,0,0,.7) 70%,transparent);padding:24px 16px 16px;transform:translateY(100%);opacity:0;transition:all .3s cubic-bezier(.4,0,.2,1);pointer-events:none;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);overflow:visible}.toolbar-container.visible{transform:translateY(0);opacity:1;pointer-events:auto;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}.toolbar{display:flex;align-items:center;gap:8px;max-width:1400px;margin:0 auto;overflow-x:auto;overflow-y:visible;padding:8px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.3) transparent}.toolbar::-webkit-scrollbar{height:6px}.toolbar::-webkit-scrollbar-track{background:transparent}.toolbar::-webkit-scrollbar-thumb{background:#ffffff4d;border-radius:3px}.toolbar-section{display:flex;align-items:center;gap:6px;flex-shrink:0}.toolbar-left{margin-right:8px}.toolbar-center{flex:1 0 auto;justify-content:center}.toolbar-right{margin-left:8px}.toolbar-social{margin-left:16px;padding-left:16px;border-left:1px solid rgba(255,255,255,.2)}.toolbar-btn{display:flex;align-items:center;gap:6px;padding:8px 12px;min-height:40px;font-size:14px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease;white-space:nowrap;flex-shrink:0}.toolbar-btn:hover{background:#fff3;border-color:#ffffff4d;transform:translateY(-1px)}.toolbar-btn:active{background:#ffffff26;transform:translateY(0)}.toolbar-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}.toolbar-btn:disabled:hover{background:#ffffff1a;border-color:#fff3}.toolbar-btn.toggle-btn.active{background:#0078d7cc;border-color:#0078d7}.toolbar-btn.toggle-btn.active:hover{background:#0078d7e6}.toolbar-btn.icon-only{padding:8px;min-width:40px;justify-content:center}.toolbar-btn.icon-only .btn-label{display:none}.toolbar-btn.close-btn{margin-left:8px;background:#ff00004d;border-color:#ff000080}.toolbar-btn.close-btn:hover{background:#ff000080;border-color:#ff0000b3}.toolbar-btn.branch-indicator{background:#ffa5004d;border-color:#ffa50080;animation:pulse 2s ease-in-out infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.7}}.btn-icon{font-size:18px;line-height:1;display:flex;align-items:center;justify-content:center;flex-shrink:0}.btn-label{font-size:13px;line-height:1}.toolbar-dropdown{position:relative;display:flex;flex-direction:column;align-items:stretch;gap:8px}.dropdown-menu{position:absolute;bottom:100%;left:0;margin-bottom:8px;min-width:120px;background:#1e1e1ef2;border:1px solid rgba(255,255,255,.2);border-radius:6px;padding:4px;box-shadow:0 4px 12px #0006;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);z-index:1000;animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.dropdown-item{display:block;width:100%;padding:8px 12px;font-size:14px;color:#ffffffe6;background:transparent;border:none;border-radius:4px;cursor:pointer;text-align:left;transition:background .2s ease}.dropdown-item:hover{background:#ffffff1a}.dropdown-item.active{background:#0078d780;font-weight:600}.autoplay-controls{display:flex;align-items:center;gap:6px;padding:6px 8px;background:#0006;border:1px solid rgba(255,255,255,.2);border-radius:6px}.control-btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:4px;cursor:pointer;transition:all .2s ease}.control-btn:hover{background:#fff3}.control-btn:active{background:#ffffff26;transform:scale(.95)}.control-value{min-width:40px;text-align:center;font-size:13px;font-weight:600;color:#ffffffe6}@media (max-width: 768px){.toolbar-container{padding:16px 8px 8px}.toolbar{gap:4px;padding:4px}.toolbar-section{gap:4px}.toolbar-btn{padding:6px 10px;min-height:36px;font-size:12px}.btn-icon{font-size:16px}.btn-label{font-size:11px}.toolbar-center .btn-label,.toolbar-right .btn-label{display:none}.toolbar-btn{padding:6px 8px;min-width:36px;justify-content:center}.toolbar-left .toolbar-btn{padding:6px 10px}.toolbar-left .btn-label{display:inline}.autoplay-controls{padding:4px 6px;gap:4px}.control-btn{width:24px;height:24px;font-size:14px}.control-value{min-width:32px;font-size:11px}.toolbar-social{margin-left:8px;padding-left:8px}}@media (min-width: 769px) and (max-width: 1024px){.toolbar-btn{padding:7px 11px;min-height:38px}.btn-icon{font-size:17px}.btn-label{font-size:12px}}@media (prefers-reduced-motion: reduce){.toolbar-container,.toolbar-btn,.dropdown-menu,.dropdown-item,.control-btn{transition:none;animation:none}.toolbar-container.visible{animation:none}.toolbar-btn:hover{transform:none}.toolbar-btn.branch-indicator{animation:none}}.toolbar-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}.dropdown-item:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:-2px}@media (prefers-contrast: high){.toolbar-btn{border-width:2px}.toolbar-btn.toggle-btn.active{background:#0078d7}}.autoplay-btn{position:relative;overflow:visible}.autoplay-progress-bar{position:absolute;bottom:-4px;left:4px;right:4px;height:3px;background:#fff3;border-radius:2px;overflow:hidden}.autoplay-progress-fill{height:100%;background:linear-gradient(90deg,#0078d7cc,#00b4ffe6);border-radius:2px;transition:width .1s linear;box-shadow:0 0 8px #00b4ff80}\n"] }]
|
|
13743
13743
|
}], propDecorators: { locale: [{
|
|
13744
13744
|
type: Input
|
|
13745
13745
|
}], availableLocales: [{
|
|
@@ -13870,11 +13870,11 @@ class LanguageModalComponent {
|
|
|
13870
13870
|
}
|
|
13871
13871
|
}
|
|
13872
13872
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: LanguageModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
13873
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: LanguageModalComponent, isStandalone: true, selector: "pw-language-modal", inputs: { visible: "visible", currentLocale: "currentLocale", availableLocales: "availableLocales" }, outputs: { closeModal: "closeModal", localeSelected: "localeSelected" }, ngImport: i0, template: "@if (visible) {\
|
|
13873
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: LanguageModalComponent, isStandalone: true, selector: "pw-language-modal", inputs: { visible: "visible", currentLocale: "currentLocale", availableLocales: "availableLocales" }, outputs: { closeModal: "closeModal", localeSelected: "localeSelected" }, ngImport: i0, template: "@if (visible) {\n <div \n class=\"language-overlay\" \n (click)=\"onBackdropClick($event)\"\n (keydown)=\"onBackdropKeydown($event)\"\n tabindex=\"-1\">\n <div class=\"language-container\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"language-title\">\n <!-- Header -->\n <div class=\"language-header\">\n <h2 id=\"language-title\" class=\"language-title\">\n {{ 'toolbar.select_language' | translate }}\n </h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'settings.close' | translate\"\n (click)=\"closeModal.emit()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Language list -->\n <div class=\"language-content\">\n <div class=\"language-list\">\n @for (locale of availableLocales; track locale) {\n <button\n class=\"language-item\"\n [class.active]=\"locale === currentLocale\"\n type=\"button\"\n (click)=\"onSelectLocale(locale)\">\n <span class=\"language-icon\"><pw-icon name=\"lucideGlobe\" /></span>\n <span class=\"language-name\">{{ locale }}</span>\n @if (locale === currentLocale) {\n <span class=\"language-check\"><pw-icon name=\"lucideCheck\" /></span>\n }\n </button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [".language-overlay{position:fixed;inset:0;background:#000c;display:flex;align-items:center;justify-content:center;z-index:1000;padding:20px;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.language-container{background:linear-gradient(135deg,#1a1a1a,#2d2d2d);border:1px solid rgba(255,255,255,.1);border-radius:12px;box-shadow:0 8px 32px #00000080;max-width:400px;width:100%;max-height:80vh;display:flex;flex-direction:column;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.language-header{display:flex;align-items:center;justify-content:space-between;padding:24px;border-bottom:1px solid rgba(255,255,255,.1)}.language-title{margin:0;font-size:20px;font-weight:600;color:#fff}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;color:#ffffffb3;font-size:20px;cursor:pointer;border-radius:6px;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{transform:scale(.95)}.language-content{padding:16px;overflow-y:auto;flex:1}.language-list{display:flex;flex-direction:column;gap:8px}.language-item{display:flex;align-items:center;gap:12px;padding:16px;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:8px;color:#ffffffe6;font-size:16px;cursor:pointer;transition:all .2s ease;text-align:left}.language-item:hover{background:#ffffff1a;border-color:#fff3;transform:translate(4px)}.language-item:active{transform:translate(4px) scale(.98)}.language-item.active{background:#0078d74d;border-color:#0078d780;color:#fff}.language-icon{font-size:24px;flex-shrink:0}.language-name{flex:1;font-weight:500}.language-check{font-size:18px;color:#00c864;flex-shrink:0}.language-content::-webkit-scrollbar{width:8px}.language-content::-webkit-scrollbar-track{background:#ffffff0d;border-radius:4px}.language-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.language-content::-webkit-scrollbar-thumb:hover{background:#ffffff4d}@media (max-width: 768px){.language-container{max-width:100%;max-height:60vh}.language-header{padding:20px}.language-title{font-size:18px}.language-item{padding:14px;font-size:15px}}@media (prefers-reduced-motion: reduce){.language-overlay,.language-container,.language-item,.close-btn{animation:none;transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
13874
13874
|
}
|
|
13875
13875
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: LanguageModalComponent, decorators: [{
|
|
13876
13876
|
type: Component,
|
|
13877
|
-
args: [{ selector: 'pw-language-modal', imports: [TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\
|
|
13877
|
+
args: [{ selector: 'pw-language-modal', imports: [TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\n <div \n class=\"language-overlay\" \n (click)=\"onBackdropClick($event)\"\n (keydown)=\"onBackdropKeydown($event)\"\n tabindex=\"-1\">\n <div class=\"language-container\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"language-title\">\n <!-- Header -->\n <div class=\"language-header\">\n <h2 id=\"language-title\" class=\"language-title\">\n {{ 'toolbar.select_language' | translate }}\n </h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'settings.close' | translate\"\n (click)=\"closeModal.emit()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Language list -->\n <div class=\"language-content\">\n <div class=\"language-list\">\n @for (locale of availableLocales; track locale) {\n <button\n class=\"language-item\"\n [class.active]=\"locale === currentLocale\"\n type=\"button\"\n (click)=\"onSelectLocale(locale)\">\n <span class=\"language-icon\"><pw-icon name=\"lucideGlobe\" /></span>\n <span class=\"language-name\">{{ locale }}</span>\n @if (locale === currentLocale) {\n <span class=\"language-check\"><pw-icon name=\"lucideCheck\" /></span>\n }\n </button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [".language-overlay{position:fixed;inset:0;background:#000c;display:flex;align-items:center;justify-content:center;z-index:1000;padding:20px;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.language-container{background:linear-gradient(135deg,#1a1a1a,#2d2d2d);border:1px solid rgba(255,255,255,.1);border-radius:12px;box-shadow:0 8px 32px #00000080;max-width:400px;width:100%;max-height:80vh;display:flex;flex-direction:column;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.language-header{display:flex;align-items:center;justify-content:space-between;padding:24px;border-bottom:1px solid rgba(255,255,255,.1)}.language-title{margin:0;font-size:20px;font-weight:600;color:#fff}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;color:#ffffffb3;font-size:20px;cursor:pointer;border-radius:6px;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{transform:scale(.95)}.language-content{padding:16px;overflow-y:auto;flex:1}.language-list{display:flex;flex-direction:column;gap:8px}.language-item{display:flex;align-items:center;gap:12px;padding:16px;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:8px;color:#ffffffe6;font-size:16px;cursor:pointer;transition:all .2s ease;text-align:left}.language-item:hover{background:#ffffff1a;border-color:#fff3;transform:translate(4px)}.language-item:active{transform:translate(4px) scale(.98)}.language-item.active{background:#0078d74d;border-color:#0078d780;color:#fff}.language-icon{font-size:24px;flex-shrink:0}.language-name{flex:1;font-weight:500}.language-check{font-size:18px;color:#00c864;flex-shrink:0}.language-content::-webkit-scrollbar{width:8px}.language-content::-webkit-scrollbar-track{background:#ffffff0d;border-radius:4px}.language-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.language-content::-webkit-scrollbar-thumb:hover{background:#ffffff4d}@media (max-width: 768px){.language-container{max-width:100%;max-height:60vh}.language-header{padding:20px}.language-title{font-size:18px}.language-item{padding:14px;font-size:15px}}@media (prefers-reduced-motion: reduce){.language-overlay,.language-container,.language-item,.close-btn{animation:none;transition:none}}\n"] }]
|
|
13878
13878
|
}], propDecorators: { visible: [{
|
|
13879
13879
|
type: Input
|
|
13880
13880
|
}], currentLocale: [{
|
|
@@ -14216,11 +14216,11 @@ class ThumbnailStripComponent {
|
|
|
14216
14216
|
}
|
|
14217
14217
|
}
|
|
14218
14218
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ThumbnailStripComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
14219
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: ThumbnailStripComponent, isStandalone: true, selector: "pw-thumbnail-strip", inputs: { chapters: "chapters", currentChapterId: "currentChapterId", currentPanelId: "currentPanelId", baseUrl: "baseUrl", visible: "visible", lockedPanels: "lockedPanels" }, outputs: { navigate: "navigate", close: "close" }, host: { listeners: { "window:keydown": "handleKeyboard($event)" } }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "@if (visible) {\
|
|
14219
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: ThumbnailStripComponent, isStandalone: true, selector: "pw-thumbnail-strip", inputs: { chapters: "chapters", currentChapterId: "currentChapterId", currentPanelId: "currentPanelId", baseUrl: "baseUrl", visible: "visible", lockedPanels: "lockedPanels" }, outputs: { navigate: "navigate", close: "close" }, host: { listeners: { "window:keydown": "handleKeyboard($event)" } }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "@if (visible) {\n <div class=\"thumbnail-strip\">\n <!-- Close button -->\n <button\n class=\"close-btn\"\n type=\"button\"\n aria-label=\"Close thumbnail strip\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n\n <!-- Left arrow navigation -->\n <button\n class=\"nav-arrow nav-arrow-left\"\n type=\"button\"\n aria-label=\"Scroll left\"\n [disabled]=\"!canScrollLeft()\"\n [class.disabled]=\"!canScrollLeft()\"\n (click)=\"scrollLeft()\">\n <pw-icon name=\"lucideChevronLeft\" />\n </button>\n\n <!-- Right arrow navigation -->\n <button\n class=\"nav-arrow nav-arrow-right\"\n type=\"button\"\n aria-label=\"Scroll right\"\n [disabled]=\"!canScrollRight()\"\n [class.disabled]=\"!canScrollRight()\"\n (click)=\"scrollRight()\">\n <pw-icon name=\"lucideChevronRight\" />\n </button>\n\n <!-- Scroll container -->\n <div\n class=\"strip-scroll-container\"\n #scrollContainer\n (scroll)=\"onScroll($event)\"\n (mousedown)=\"onMouseDown($event)\"\n (mousemove)=\"onMouseMove($event)\"\n (mouseup)=\"onMouseUp()\"\n (mouseleave)=\"onMouseLeave()\">\n \n <!-- Content wrapper for virtual scrolling -->\n <div\n class=\"strip-content\"\n [style.width.px]=\"getTotalWidth()\">\n \n @for (item of getVisibleItems(); track item.panelId) {\n <div\n class=\"thumbnail-wrapper\"\n [style.left.px]=\"item.index * (itemWidth + itemGap)\"\n [class.chapter-start]=\"item.chapterStart\">\n \n <!-- Chapter separator -->\n @if (item.chapterStart && item.chapterTitle) {\n <div class=\"chapter-separator\">\n <span class=\"separator-title\">{{ item.chapterTitle }}</span>\n </div>\n }\n\n <!-- Thumbnail -->\n <button\n class=\"thumbnail-item\"\n [class.current]=\"item.isCurrentPanel\"\n [class.current-chapter]=\"item.isCurrentChapter\"\n [class.locked]=\"item.isLocked\"\n type=\"button\"\n [attr.aria-label]=\"'Panel ' + item.panelId\"\n [attr.aria-current]=\"item.isCurrentPanel ? 'true' : null\"\n (click)=\"onNavigate(item)\">\n \n @if (getThumbnailUrl(item)) {\n <img\n [src]=\"getThumbnailUrl(item)\"\n [alt]=\"'Panel ' + item.panelId\"\n class=\"thumbnail-image\"\n loading=\"lazy\" />\n } @else {\n <div class=\"thumbnail-placeholder\">\n <span class=\"placeholder-text\">{{ item.panelId }}</span>\n </div>\n }\n\n <!-- Lock overlay -->\n @if (item.isLocked) {\n <div class=\"lock-overlay\">\n <span class=\"lock-icon\"><pw-icon name=\"lucideLock\" /></span>\n </div>\n }\n\n <!-- Current indicator -->\n @if (item.isCurrentPanel) {\n <div class=\"current-indicator\"></div>\n }\n </button>\n </div>\n }\n </div>\n </div>\n\n <!-- Navigation hints -->\n <div class=\"strip-hints\">\n <kbd>\u2190</kbd> <kbd>\u2192</kbd> Navigate\n <span class=\"separator\">\u2022</span>\n <kbd>Esc</kbd> Close\n </div>\n </div>\n}\n", styles: [".thumbnail-strip{position:fixed;bottom:0;left:0;right:0;z-index:120;background:linear-gradient(to top,#000000f2,#000c);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);padding:12px 16px 16px;box-shadow:0 -4px 16px #0006;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}.close-btn{position:absolute;top:8px;right:16px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:18px;color:#ffffffb3;background:#ffffff1a;border:none;border-radius:50%;cursor:pointer;transition:all .2s ease;z-index:1}.close-btn:hover{background:#fff3;color:#fff}.nav-arrow{position:absolute;top:50%;transform:translateY(-50%);width:40px;height:40px;display:flex;align-items:center;justify-content:center;font-size:32px;font-weight:700;color:#ffffffe6;background:#000000b3;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.2);border-radius:50%;cursor:pointer;transition:all .2s ease;z-index:2;-webkit-user-select:none;user-select:none}.nav-arrow:hover:not(.disabled){background:#000000d9;border-color:#fff6;transform:translateY(-50%) scale(1.1)}.nav-arrow:active:not(.disabled){transform:translateY(-50%) scale(.95)}.nav-arrow-left{left:12px}.nav-arrow-right{right:12px}.nav-arrow.disabled{opacity:.3;cursor:not-allowed}.nav-arrow:disabled{opacity:.3;cursor:not-allowed}.strip-scroll-container{position:relative;overflow-x:auto;overflow-y:hidden;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.3) transparent;padding:8px 0;cursor:grab;-webkit-user-select:none;user-select:none}.strip-scroll-container:active{cursor:grabbing}.strip-scroll-container::-webkit-scrollbar{height:6px}.strip-scroll-container::-webkit-scrollbar-track{background:#ffffff0d;border-radius:3px}.strip-scroll-container::-webkit-scrollbar-thumb{background:#ffffff4d;border-radius:3px}.strip-scroll-container::-webkit-scrollbar-thumb:hover{background:#fff6}.strip-content{position:relative;height:140px;min-width:100%}.thumbnail-wrapper{position:absolute;top:0;width:120px;display:flex;flex-direction:column;gap:8px}.thumbnail-wrapper.chapter-start{padding-top:32px}.chapter-separator{position:absolute;top:0;left:0;right:0;display:flex;align-items:center;gap:8px}.chapter-separator:before,.chapter-separator:after{content:\"\";flex:1;height:1px;background:#fff3}.separator-title{font-size:11px;font-weight:600;color:#fff9;text-transform:uppercase;letter-spacing:.5px;white-space:nowrap;padding:0 8px}.thumbnail-item{position:relative;width:120px;height:100px;background:#ffffff0d;border:2px solid rgba(255,255,255,.2);border-radius:8px;cursor:pointer;overflow:hidden;transition:all .2s ease;padding:0}.thumbnail-item:hover:not(.locked){border-color:#fff6;transform:translateY(-4px);box-shadow:0 4px 12px #0006}.thumbnail-item:active:not(.locked){transform:translateY(-2px)}.thumbnail-item.current-chapter{border-color:#0078d766}.thumbnail-item.current{border-color:#0078d7cc;border-width:3px;box-shadow:0 0 0 1px #0078d74d,0 4px 12px #0078d766}.thumbnail-item.current:hover{border-color:#0078d7}.thumbnail-item.locked{opacity:.6;cursor:not-allowed}.thumbnail-item.locked:hover{transform:none;border-color:#fff3;box-shadow:none}.thumbnail-image{width:100%;height:100%;object-fit:cover;display:block}.thumbnail-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#0078d71a,#00b4781a)}.placeholder-text{font-size:12px;font-weight:600;color:#ffffff80}.lock-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.lock-icon{font-size:24px;filter:drop-shadow(0 2px 4px rgba(0,0,0,.5))}.current-indicator{position:absolute;bottom:0;left:0;right:0;height:4px;background:linear-gradient(90deg,#0078d7cc,#00b4ffcc);animation:pulse 2s ease-in-out infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.6}}.strip-hints{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:12px;font-size:11px;color:#ffffff80}.strip-hints kbd{padding:3px 6px;font-size:10px;font-family:monospace;color:#fffc;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:3px}.separator{opacity:.5}@media (max-width: 768px){.thumbnail-strip{padding:10px 12px 12px}.thumbnail-wrapper{width:100px}.thumbnail-item{width:100px;height:80px}.close-btn{width:24px;height:24px;font-size:16px}.nav-arrow{width:32px;height:32px;font-size:24px}.nav-arrow-left{left:8px}.nav-arrow-right{right:8px}.strip-content{height:120px}.separator-title{font-size:10px}.lock-icon{font-size:20px}.strip-hints{margin-top:8px;font-size:10px}}@media (max-width: 480px){.thumbnail-wrapper{width:80px}.thumbnail-item{width:80px;height:64px}.strip-content{height:100px}}.close-btn:focus-visible,.thumbnail-item:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.thumbnail-strip,.thumbnail-item,.current-indicator{animation:none;transition:none}}\n"], dependencies: [{ kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
14220
14220
|
}
|
|
14221
14221
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ThumbnailStripComponent, decorators: [{
|
|
14222
14222
|
type: Component,
|
|
14223
|
-
args: [{ selector: 'pw-thumbnail-strip', imports: [PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\
|
|
14223
|
+
args: [{ selector: 'pw-thumbnail-strip', imports: [PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\n <div class=\"thumbnail-strip\">\n <!-- Close button -->\n <button\n class=\"close-btn\"\n type=\"button\"\n aria-label=\"Close thumbnail strip\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n\n <!-- Left arrow navigation -->\n <button\n class=\"nav-arrow nav-arrow-left\"\n type=\"button\"\n aria-label=\"Scroll left\"\n [disabled]=\"!canScrollLeft()\"\n [class.disabled]=\"!canScrollLeft()\"\n (click)=\"scrollLeft()\">\n <pw-icon name=\"lucideChevronLeft\" />\n </button>\n\n <!-- Right arrow navigation -->\n <button\n class=\"nav-arrow nav-arrow-right\"\n type=\"button\"\n aria-label=\"Scroll right\"\n [disabled]=\"!canScrollRight()\"\n [class.disabled]=\"!canScrollRight()\"\n (click)=\"scrollRight()\">\n <pw-icon name=\"lucideChevronRight\" />\n </button>\n\n <!-- Scroll container -->\n <div\n class=\"strip-scroll-container\"\n #scrollContainer\n (scroll)=\"onScroll($event)\"\n (mousedown)=\"onMouseDown($event)\"\n (mousemove)=\"onMouseMove($event)\"\n (mouseup)=\"onMouseUp()\"\n (mouseleave)=\"onMouseLeave()\">\n \n <!-- Content wrapper for virtual scrolling -->\n <div\n class=\"strip-content\"\n [style.width.px]=\"getTotalWidth()\">\n \n @for (item of getVisibleItems(); track item.panelId) {\n <div\n class=\"thumbnail-wrapper\"\n [style.left.px]=\"item.index * (itemWidth + itemGap)\"\n [class.chapter-start]=\"item.chapterStart\">\n \n <!-- Chapter separator -->\n @if (item.chapterStart && item.chapterTitle) {\n <div class=\"chapter-separator\">\n <span class=\"separator-title\">{{ item.chapterTitle }}</span>\n </div>\n }\n\n <!-- Thumbnail -->\n <button\n class=\"thumbnail-item\"\n [class.current]=\"item.isCurrentPanel\"\n [class.current-chapter]=\"item.isCurrentChapter\"\n [class.locked]=\"item.isLocked\"\n type=\"button\"\n [attr.aria-label]=\"'Panel ' + item.panelId\"\n [attr.aria-current]=\"item.isCurrentPanel ? 'true' : null\"\n (click)=\"onNavigate(item)\">\n \n @if (getThumbnailUrl(item)) {\n <img\n [src]=\"getThumbnailUrl(item)\"\n [alt]=\"'Panel ' + item.panelId\"\n class=\"thumbnail-image\"\n loading=\"lazy\" />\n } @else {\n <div class=\"thumbnail-placeholder\">\n <span class=\"placeholder-text\">{{ item.panelId }}</span>\n </div>\n }\n\n <!-- Lock overlay -->\n @if (item.isLocked) {\n <div class=\"lock-overlay\">\n <span class=\"lock-icon\"><pw-icon name=\"lucideLock\" /></span>\n </div>\n }\n\n <!-- Current indicator -->\n @if (item.isCurrentPanel) {\n <div class=\"current-indicator\"></div>\n }\n </button>\n </div>\n }\n </div>\n </div>\n\n <!-- Navigation hints -->\n <div class=\"strip-hints\">\n <kbd>\u2190</kbd> <kbd>\u2192</kbd> Navigate\n <span class=\"separator\">\u2022</span>\n <kbd>Esc</kbd> Close\n </div>\n </div>\n}\n", styles: [".thumbnail-strip{position:fixed;bottom:0;left:0;right:0;z-index:120;background:linear-gradient(to top,#000000f2,#000c);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);padding:12px 16px 16px;box-shadow:0 -4px 16px #0006;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}.close-btn{position:absolute;top:8px;right:16px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:18px;color:#ffffffb3;background:#ffffff1a;border:none;border-radius:50%;cursor:pointer;transition:all .2s ease;z-index:1}.close-btn:hover{background:#fff3;color:#fff}.nav-arrow{position:absolute;top:50%;transform:translateY(-50%);width:40px;height:40px;display:flex;align-items:center;justify-content:center;font-size:32px;font-weight:700;color:#ffffffe6;background:#000000b3;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.2);border-radius:50%;cursor:pointer;transition:all .2s ease;z-index:2;-webkit-user-select:none;user-select:none}.nav-arrow:hover:not(.disabled){background:#000000d9;border-color:#fff6;transform:translateY(-50%) scale(1.1)}.nav-arrow:active:not(.disabled){transform:translateY(-50%) scale(.95)}.nav-arrow-left{left:12px}.nav-arrow-right{right:12px}.nav-arrow.disabled{opacity:.3;cursor:not-allowed}.nav-arrow:disabled{opacity:.3;cursor:not-allowed}.strip-scroll-container{position:relative;overflow-x:auto;overflow-y:hidden;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.3) transparent;padding:8px 0;cursor:grab;-webkit-user-select:none;user-select:none}.strip-scroll-container:active{cursor:grabbing}.strip-scroll-container::-webkit-scrollbar{height:6px}.strip-scroll-container::-webkit-scrollbar-track{background:#ffffff0d;border-radius:3px}.strip-scroll-container::-webkit-scrollbar-thumb{background:#ffffff4d;border-radius:3px}.strip-scroll-container::-webkit-scrollbar-thumb:hover{background:#fff6}.strip-content{position:relative;height:140px;min-width:100%}.thumbnail-wrapper{position:absolute;top:0;width:120px;display:flex;flex-direction:column;gap:8px}.thumbnail-wrapper.chapter-start{padding-top:32px}.chapter-separator{position:absolute;top:0;left:0;right:0;display:flex;align-items:center;gap:8px}.chapter-separator:before,.chapter-separator:after{content:\"\";flex:1;height:1px;background:#fff3}.separator-title{font-size:11px;font-weight:600;color:#fff9;text-transform:uppercase;letter-spacing:.5px;white-space:nowrap;padding:0 8px}.thumbnail-item{position:relative;width:120px;height:100px;background:#ffffff0d;border:2px solid rgba(255,255,255,.2);border-radius:8px;cursor:pointer;overflow:hidden;transition:all .2s ease;padding:0}.thumbnail-item:hover:not(.locked){border-color:#fff6;transform:translateY(-4px);box-shadow:0 4px 12px #0006}.thumbnail-item:active:not(.locked){transform:translateY(-2px)}.thumbnail-item.current-chapter{border-color:#0078d766}.thumbnail-item.current{border-color:#0078d7cc;border-width:3px;box-shadow:0 0 0 1px #0078d74d,0 4px 12px #0078d766}.thumbnail-item.current:hover{border-color:#0078d7}.thumbnail-item.locked{opacity:.6;cursor:not-allowed}.thumbnail-item.locked:hover{transform:none;border-color:#fff3;box-shadow:none}.thumbnail-image{width:100%;height:100%;object-fit:cover;display:block}.thumbnail-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#0078d71a,#00b4781a)}.placeholder-text{font-size:12px;font-weight:600;color:#ffffff80}.lock-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.lock-icon{font-size:24px;filter:drop-shadow(0 2px 4px rgba(0,0,0,.5))}.current-indicator{position:absolute;bottom:0;left:0;right:0;height:4px;background:linear-gradient(90deg,#0078d7cc,#00b4ffcc);animation:pulse 2s ease-in-out infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.6}}.strip-hints{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:12px;font-size:11px;color:#ffffff80}.strip-hints kbd{padding:3px 6px;font-size:10px;font-family:monospace;color:#fffc;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:3px}.separator{opacity:.5}@media (max-width: 768px){.thumbnail-strip{padding:10px 12px 12px}.thumbnail-wrapper{width:100px}.thumbnail-item{width:100px;height:80px}.close-btn{width:24px;height:24px;font-size:16px}.nav-arrow{width:32px;height:32px;font-size:24px}.nav-arrow-left{left:8px}.nav-arrow-right{right:8px}.strip-content{height:120px}.separator-title{font-size:10px}.lock-icon{font-size:20px}.strip-hints{margin-top:8px;font-size:10px}}@media (max-width: 480px){.thumbnail-wrapper{width:80px}.thumbnail-item{width:80px;height:64px}.strip-content{height:100px}}.close-btn:focus-visible,.thumbnail-item:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.thumbnail-strip,.thumbnail-item,.current-indicator{animation:none;transition:none}}\n"] }]
|
|
14224
14224
|
}], propDecorators: { chapters: [{
|
|
14225
14225
|
type: Input
|
|
14226
14226
|
}], currentChapterId: [{
|
|
@@ -14540,11 +14540,11 @@ class TocOverlayComponent {
|
|
|
14540
14540
|
return this.filteredChapters[this.selectedChapterIndex];
|
|
14541
14541
|
}
|
|
14542
14542
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TocOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
14543
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: TocOverlayComponent, isStandalone: true, selector: "pw-toc-overlay", inputs: { manifest: "manifest", chapters: "chapters", currentChapterId: "currentChapterId", currentPanelId: "currentPanelId", locale: "locale", baseUrl: "baseUrl", visible: "visible" }, outputs: { navigate: "navigate", close: "close" }, host: { listeners: { "window:keydown": "handleKeyboard($event)" } }, usesOnChanges: true, ngImport: i0, template: "@if (visible) {\r\n <div class=\"toc-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); onClose()\">\r\n <div class=\"toc-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Table of Contents\">\r\n <!-- Header -->\r\n <div class=\"toc-header\">\r\n <h2 class=\"toc-title\">{{ 'toc.title' | translate }}</h2>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toc.close' | translate\"\r\n (click)=\"onClose()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Search -->\r\n <div class=\"toc-search\">\r\n <input\r\n type=\"text\"\r\n class=\"search-input\"\r\n [placeholder]=\"'toc.search_chapters' | translate\"\r\n [attr.aria-label]=\"'toc.search_label' | translate\"\r\n [(ngModel)]=\"searchQuery\"\r\n (ngModelChange)=\"onSearchChange()\" />\r\n <span class=\"search-icon\"><pw-icon name=\"lucideSearch\" /></span>\r\n </div>\r\n\r\n <!-- Content -->\r\n <div class=\"toc-content\">\r\n <!-- Chapter list -->\r\n <div class=\"chapter-list\" role=\"list\">\r\n @for (chapter of filteredChapters; track chapter.id; let i = $index) {\r\n <div\r\n class=\"chapter-item\"\r\n [class.selected]=\"i === selectedChapterIndex\"\r\n [class.current]=\"isCurrentChapter(chapter)\"\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"selectChapter(i)\"\r\n (keydown.enter)=\"selectChapter(i)\">\r\n \r\n <!-- Chapter header -->\r\n <div class=\"chapter-header\" tabindex=\"0\" (click)=\"navigateToChapter(chapter)\" (keydown.enter)=\"navigateToChapter(chapter)\">\r\n <span class=\"chapter-number\">{{ i + 1 }}</span>\r\n <h3 class=\"chapter-title\">{{ getChapterTitle(chapter) }}</h3>\r\n @if (isCurrentChapter(chapter)) {\r\n <span class=\"current-badge\">{{ 'toc.current' | translate }}</span>\r\n }\r\n </div>\r\n\r\n <!-- Panel list (if chapter selected) -->\r\n @if (i === selectedChapterIndex) {\r\n <div class=\"panel-list\">\r\n <!-- If chapter has pages, group by pages -->\r\n @if (hasPages(chapter)) {\r\n @for (page of getPages(chapter); track page.id) {\r\n <div class=\"page-group\">\r\n <div class=\"page-header\">\r\n <span class=\"page-title\">{{ getPageTitle(page) }}</span>\r\n </div>\r\n <div class=\"panels-grid\">\r\n @for (panelItem of getPanelsForPage(chapter, page); track panelItem.id) {\r\n <button\r\n class=\"panel-card\"\r\n [class.current]=\"isCurrentPanel(panelItem.id)\"\r\n type=\"button\"\r\n (click)=\"navigateToPanel(chapter, panelItem.id)\">\r\n \r\n @if (getThumbnailUrl(panelItem.panel)) {\r\n <div class=\"panel-thumbnail\">\r\n <img \r\n [src]=\"getThumbnailUrl(panelItem.panel)\" \r\n [alt]=\"getPanelTitle(panelItem.panel, panelItem.id)\"\r\n loading=\"lazy\" />\r\n @if (isCurrentPanel(panelItem.id)) {\r\n <div class=\"current-overlay\">\r\n <span class=\"current-icon\"><pw-icon name=\"lucidePlay\" /></span>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"panel-thumbnail placeholder\">\r\n <span class=\"placeholder-icon\"><pw-icon name=\"lucideImage\" /></span>\r\n </div>\r\n }\r\n \r\n <div class=\"panel-info\">\r\n <span class=\"panel-title\">{{ getPanelTitle(panelItem.panel, panelItem.id) }}</span>\r\n </div>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n } @else {\r\n <!-- No pages, show all panels directly -->\r\n <div class=\"panels-grid\">\r\n @for (panelItem of getPanels(chapter); track panelItem.id) {\r\n <button\r\n class=\"panel-card\"\r\n [class.current]=\"isCurrentPanel(panelItem.id)\"\r\n type=\"button\"\r\n (click)=\"navigateToPanel(chapter, panelItem.id)\">\r\n \r\n @if (getThumbnailUrl(panelItem.panel)) {\r\n <div class=\"panel-thumbnail\">\r\n <img \r\n [src]=\"getThumbnailUrl(panelItem.panel)\" \r\n [alt]=\"getPanelTitle(panelItem.panel, panelItem.id)\"\r\n loading=\"lazy\" />\r\n @if (isCurrentPanel(panelItem.id)) {\r\n <div class=\"current-overlay\">\r\n <span class=\"current-icon\"><pw-icon name=\"lucidePlay\" /></span>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"panel-thumbnail placeholder\">\r\n <span class=\"placeholder-icon\"><pw-icon name=\"lucideImage\" /></span>\r\n </div>\r\n }\r\n \r\n <div class=\"panel-info\">\r\n <span class=\"panel-title\">{{ getPanelTitle(panelItem.panel, panelItem.id) }}</span>\r\n </div>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (filteredChapters.length === 0) {\r\n <div class=\"empty-state\">\r\n <p>{{ 'toc.no_results' | translate: {query: searchQuery} }}</p>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"toc-footer\">\r\n <p class=\"footer-hint\">\r\n <kbd>\u2191</kbd> <kbd>\u2193</kbd> {{ 'toc.keyboard_hint_nav' | translate }}\r\n <kbd>Enter</kbd> {{ 'toc.keyboard_hint_select' | translate }}\r\n <kbd>{{ 'common.esc' | translate }}</kbd> {{ 'toc.keyboard_hint_close' | translate }}\r\n </p>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [".toc-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.toc-container{width:90%;max-width:800px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.toc-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.toc-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.toc-search{position:relative;padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.search-input{width:100%;padding:10px 40px 10px 16px;font-size:14px;color:#fff;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:8px;outline:none;transition:all .2s ease}.search-input::placeholder{color:#fff6}.search-input:focus{background:#ffffff14;border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.search-icon{position:absolute;right:36px;top:50%;transform:translateY(-50%);font-size:16px;pointer-events:none;opacity:.5}.toc-content{flex:1;overflow-y:auto;padding:16px 0}.toc-content::-webkit-scrollbar{width:8px}.toc-content::-webkit-scrollbar-track{background:#ffffff0d}.toc-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.toc-content::-webkit-scrollbar-thumb:hover{background:#ffffff4d}.chapter-list{display:flex;flex-direction:column;gap:8px;padding:0 24px}.chapter-item{border-radius:8px;transition:background .2s ease}.chapter-item.selected{background:#ffffff0d}.chapter-item.current{background:#0078d71a}.chapter-header{display:flex;align-items:center;gap:12px;padding:12px 16px;cursor:pointer;border-radius:8px;transition:all .2s ease}.chapter-header:hover{background:#ffffff1a}.chapter-number{display:flex;align-items:center;justify-content:center;width:32px;height:32px;font-size:14px;font-weight:600;color:#fff;background:#ffffff1a;border-radius:6px;flex-shrink:0}.chapter-item.current .chapter-number{background:#0078d7cc}.chapter-title{flex:1;font-size:16px;font-weight:500;color:#ffffffe6;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.current-badge{padding:4px 8px;font-size:11px;font-weight:600;color:#fff;background:#0078d7cc;border-radius:4px;text-transform:uppercase;letter-spacing:.5px;flex-shrink:0}.panel-list{padding:12px 16px 16px 60px;display:flex;flex-direction:column;gap:16px}.page-group{display:flex;flex-direction:column;gap:12px}.page-header{padding:8px 12px;background:#ffffff0d;border-left:3px solid rgba(0,120,215,.6);border-radius:4px}.page-title{font-size:13px;font-weight:600;color:#ffffffe6;text-transform:uppercase;letter-spacing:.5px}.panels-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}.panel-card{display:flex;flex-direction:column;background:#ffffff0d;border:2px solid rgba(255,255,255,.1);border-radius:8px;cursor:pointer;transition:all .2s ease;overflow:hidden;padding:0}.panel-card:hover{background:#ffffff14;border-color:#fff3;transform:translateY(-2px);box-shadow:0 4px 12px #0000004d}.panel-card:active{transform:translateY(0)}.panel-card.current{border-color:#0078d7cc;box-shadow:0 0 0 1px #0078d74d}.panel-thumbnail{position:relative;width:100%;aspect-ratio:4 / 3;background:#0000004d;overflow:hidden;display:flex;align-items:center;justify-content:center}.panel-thumbnail img{width:100%;height:100%;object-fit:cover;display:block}.panel-thumbnail.placeholder{background:linear-gradient(135deg,#0078d71a,#00b4781a)}.placeholder-icon{font-size:32px;opacity:.4}.current-overlay{position:absolute;inset:0;background:#0078d74d;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.current-icon{font-size:24px;color:#fff;filter:drop-shadow(0 2px 4px rgba(0,0,0,.5))}.panel-info{padding:8px 10px;display:flex;flex-direction:column;gap:4px}.panel-title{font-size:12px;font-weight:500;color:#ffffffe6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}.empty-state{padding:40px 24px;text-align:center}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.toc-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.footer-hint{display:flex;align-items:center;justify-content:center;gap:16px;font-size:12px;color:#ffffff80;margin:0}.footer-hint kbd{padding:4px 8px;font-size:11px;font-family:monospace;color:#fffc;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:4px}@media (max-width: 768px){.toc-container{width:95%;max-height:90vh}.toc-header{padding:16px 20px}.toc-title{font-size:18px}.toc-search{padding:12px 20px}.chapter-list{padding:0 20px}.chapter-header{padding:10px 12px;gap:10px}.chapter-number{width:28px;height:28px;font-size:13px}.chapter-title{font-size:15px}.panel-list{padding:10px 12px 12px 48px;gap:12px}.page-header{padding:6px 10px}.page-title{font-size:12px}.panels-grid{grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:10px}.panel-thumbnail{aspect-ratio:3 / 2}.placeholder-icon{font-size:24px}.current-icon{font-size:20px}.panel-info{padding:6px 8px}.panel-title{font-size:11px}.toc-footer{padding:12px 20px}.footer-hint{font-size:11px;gap:12px}}.close-btn:focus-visible,.search-input:focus-visible,.panel-card:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.toc-overlay,.toc-container,.close-btn,.chapter-header,.panel-card{animation:none;transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
14543
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: TocOverlayComponent, isStandalone: true, selector: "pw-toc-overlay", inputs: { manifest: "manifest", chapters: "chapters", currentChapterId: "currentChapterId", currentPanelId: "currentPanelId", locale: "locale", baseUrl: "baseUrl", visible: "visible" }, outputs: { navigate: "navigate", close: "close" }, host: { listeners: { "window:keydown": "handleKeyboard($event)" } }, usesOnChanges: true, ngImport: i0, template: "@if (visible) {\n <div class=\"toc-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); onClose()\">\n <div class=\"toc-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Table of Contents\">\n <!-- Header -->\n <div class=\"toc-header\">\n <h2 class=\"toc-title\">{{ 'toc.title' | translate }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toc.close' | translate\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Search -->\n <div class=\"toc-search\">\n <input\n type=\"text\"\n class=\"search-input\"\n [placeholder]=\"'toc.search_chapters' | translate\"\n [attr.aria-label]=\"'toc.search_label' | translate\"\n [(ngModel)]=\"searchQuery\"\n (ngModelChange)=\"onSearchChange()\" />\n <span class=\"search-icon\"><pw-icon name=\"lucideSearch\" /></span>\n </div>\n\n <!-- Content -->\n <div class=\"toc-content\">\n <!-- Chapter list -->\n <div class=\"chapter-list\" role=\"list\">\n @for (chapter of filteredChapters; track chapter.id; let i = $index) {\n <div\n class=\"chapter-item\"\n [class.selected]=\"i === selectedChapterIndex\"\n [class.current]=\"isCurrentChapter(chapter)\"\n role=\"listitem\"\n tabindex=\"0\"\n (click)=\"selectChapter(i)\"\n (keydown.enter)=\"selectChapter(i)\">\n \n <!-- Chapter header -->\n <div class=\"chapter-header\" tabindex=\"0\" (click)=\"navigateToChapter(chapter)\" (keydown.enter)=\"navigateToChapter(chapter)\">\n <span class=\"chapter-number\">{{ i + 1 }}</span>\n <h3 class=\"chapter-title\">{{ getChapterTitle(chapter) }}</h3>\n @if (isCurrentChapter(chapter)) {\n <span class=\"current-badge\">{{ 'toc.current' | translate }}</span>\n }\n </div>\n\n <!-- Panel list (if chapter selected) -->\n @if (i === selectedChapterIndex) {\n <div class=\"panel-list\">\n <!-- If chapter has pages, group by pages -->\n @if (hasPages(chapter)) {\n @for (page of getPages(chapter); track page.id) {\n <div class=\"page-group\">\n <div class=\"page-header\">\n <span class=\"page-title\">{{ getPageTitle(page) }}</span>\n </div>\n <div class=\"panels-grid\">\n @for (panelItem of getPanelsForPage(chapter, page); track panelItem.id) {\n <button\n class=\"panel-card\"\n [class.current]=\"isCurrentPanel(panelItem.id)\"\n type=\"button\"\n (click)=\"navigateToPanel(chapter, panelItem.id)\">\n \n @if (getThumbnailUrl(panelItem.panel)) {\n <div class=\"panel-thumbnail\">\n <img \n [src]=\"getThumbnailUrl(panelItem.panel)\" \n [alt]=\"getPanelTitle(panelItem.panel, panelItem.id)\"\n loading=\"lazy\" />\n @if (isCurrentPanel(panelItem.id)) {\n <div class=\"current-overlay\">\n <span class=\"current-icon\"><pw-icon name=\"lucidePlay\" /></span>\n </div>\n }\n </div>\n } @else {\n <div class=\"panel-thumbnail placeholder\">\n <span class=\"placeholder-icon\"><pw-icon name=\"lucideImage\" /></span>\n </div>\n }\n \n <div class=\"panel-info\">\n <span class=\"panel-title\">{{ getPanelTitle(panelItem.panel, panelItem.id) }}</span>\n </div>\n </button>\n }\n </div>\n </div>\n }\n } @else {\n <!-- No pages, show all panels directly -->\n <div class=\"panels-grid\">\n @for (panelItem of getPanels(chapter); track panelItem.id) {\n <button\n class=\"panel-card\"\n [class.current]=\"isCurrentPanel(panelItem.id)\"\n type=\"button\"\n (click)=\"navigateToPanel(chapter, panelItem.id)\">\n \n @if (getThumbnailUrl(panelItem.panel)) {\n <div class=\"panel-thumbnail\">\n <img \n [src]=\"getThumbnailUrl(panelItem.panel)\" \n [alt]=\"getPanelTitle(panelItem.panel, panelItem.id)\"\n loading=\"lazy\" />\n @if (isCurrentPanel(panelItem.id)) {\n <div class=\"current-overlay\">\n <span class=\"current-icon\"><pw-icon name=\"lucidePlay\" /></span>\n </div>\n }\n </div>\n } @else {\n <div class=\"panel-thumbnail placeholder\">\n <span class=\"placeholder-icon\"><pw-icon name=\"lucideImage\" /></span>\n </div>\n }\n \n <div class=\"panel-info\">\n <span class=\"panel-title\">{{ getPanelTitle(panelItem.panel, panelItem.id) }}</span>\n </div>\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (filteredChapters.length === 0) {\n <div class=\"empty-state\">\n <p>{{ 'toc.no_results' | translate: {query: searchQuery} }}</p>\n </div>\n }\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"toc-footer\">\n <p class=\"footer-hint\">\n <kbd>\u2191</kbd> <kbd>\u2193</kbd> {{ 'toc.keyboard_hint_nav' | translate }}\n <kbd>Enter</kbd> {{ 'toc.keyboard_hint_select' | translate }}\n <kbd>{{ 'common.esc' | translate }}</kbd> {{ 'toc.keyboard_hint_close' | translate }}\n </p>\n </div>\n </div>\n </div>\n}\n", styles: [".toc-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.toc-container{width:90%;max-width:800px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.toc-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.toc-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.toc-search{position:relative;padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.search-input{width:100%;padding:10px 40px 10px 16px;font-size:14px;color:#fff;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:8px;outline:none;transition:all .2s ease}.search-input::placeholder{color:#fff6}.search-input:focus{background:#ffffff14;border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.search-icon{position:absolute;right:36px;top:50%;transform:translateY(-50%);font-size:16px;pointer-events:none;opacity:.5}.toc-content{flex:1;overflow-y:auto;padding:16px 0}.toc-content::-webkit-scrollbar{width:8px}.toc-content::-webkit-scrollbar-track{background:#ffffff0d}.toc-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.toc-content::-webkit-scrollbar-thumb:hover{background:#ffffff4d}.chapter-list{display:flex;flex-direction:column;gap:8px;padding:0 24px}.chapter-item{border-radius:8px;transition:background .2s ease}.chapter-item.selected{background:#ffffff0d}.chapter-item.current{background:#0078d71a}.chapter-header{display:flex;align-items:center;gap:12px;padding:12px 16px;cursor:pointer;border-radius:8px;transition:all .2s ease}.chapter-header:hover{background:#ffffff1a}.chapter-number{display:flex;align-items:center;justify-content:center;width:32px;height:32px;font-size:14px;font-weight:600;color:#fff;background:#ffffff1a;border-radius:6px;flex-shrink:0}.chapter-item.current .chapter-number{background:#0078d7cc}.chapter-title{flex:1;font-size:16px;font-weight:500;color:#ffffffe6;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.current-badge{padding:4px 8px;font-size:11px;font-weight:600;color:#fff;background:#0078d7cc;border-radius:4px;text-transform:uppercase;letter-spacing:.5px;flex-shrink:0}.panel-list{padding:12px 16px 16px 60px;display:flex;flex-direction:column;gap:16px}.page-group{display:flex;flex-direction:column;gap:12px}.page-header{padding:8px 12px;background:#ffffff0d;border-left:3px solid rgba(0,120,215,.6);border-radius:4px}.page-title{font-size:13px;font-weight:600;color:#ffffffe6;text-transform:uppercase;letter-spacing:.5px}.panels-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}.panel-card{display:flex;flex-direction:column;background:#ffffff0d;border:2px solid rgba(255,255,255,.1);border-radius:8px;cursor:pointer;transition:all .2s ease;overflow:hidden;padding:0}.panel-card:hover{background:#ffffff14;border-color:#fff3;transform:translateY(-2px);box-shadow:0 4px 12px #0000004d}.panel-card:active{transform:translateY(0)}.panel-card.current{border-color:#0078d7cc;box-shadow:0 0 0 1px #0078d74d}.panel-thumbnail{position:relative;width:100%;aspect-ratio:4 / 3;background:#0000004d;overflow:hidden;display:flex;align-items:center;justify-content:center}.panel-thumbnail img{width:100%;height:100%;object-fit:cover;display:block}.panel-thumbnail.placeholder{background:linear-gradient(135deg,#0078d71a,#00b4781a)}.placeholder-icon{font-size:32px;opacity:.4}.current-overlay{position:absolute;inset:0;background:#0078d74d;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.current-icon{font-size:24px;color:#fff;filter:drop-shadow(0 2px 4px rgba(0,0,0,.5))}.panel-info{padding:8px 10px;display:flex;flex-direction:column;gap:4px}.panel-title{font-size:12px;font-weight:500;color:#ffffffe6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}.empty-state{padding:40px 24px;text-align:center}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.toc-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.footer-hint{display:flex;align-items:center;justify-content:center;gap:16px;font-size:12px;color:#ffffff80;margin:0}.footer-hint kbd{padding:4px 8px;font-size:11px;font-family:monospace;color:#fffc;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:4px}@media (max-width: 768px){.toc-container{width:95%;max-height:90vh}.toc-header{padding:16px 20px}.toc-title{font-size:18px}.toc-search{padding:12px 20px}.chapter-list{padding:0 20px}.chapter-header{padding:10px 12px;gap:10px}.chapter-number{width:28px;height:28px;font-size:13px}.chapter-title{font-size:15px}.panel-list{padding:10px 12px 12px 48px;gap:12px}.page-header{padding:6px 10px}.page-title{font-size:12px}.panels-grid{grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:10px}.panel-thumbnail{aspect-ratio:3 / 2}.placeholder-icon{font-size:24px}.current-icon{font-size:20px}.panel-info{padding:6px 8px}.panel-title{font-size:11px}.toc-footer{padding:12px 20px}.footer-hint{font-size:11px;gap:12px}}.close-btn:focus-visible,.search-input:focus-visible,.panel-card:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.toc-overlay,.toc-container,.close-btn,.chapter-header,.panel-card{animation:none;transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
14544
14544
|
}
|
|
14545
14545
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TocOverlayComponent, decorators: [{
|
|
14546
14546
|
type: Component,
|
|
14547
|
-
args: [{ selector: 'pw-toc-overlay', imports: [FormsModule, TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\r\n <div class=\"toc-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); onClose()\">\r\n <div class=\"toc-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Table of Contents\">\r\n <!-- Header -->\r\n <div class=\"toc-header\">\r\n <h2 class=\"toc-title\">{{ 'toc.title' | translate }}</h2>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'toc.close' | translate\"\r\n (click)=\"onClose()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Search -->\r\n <div class=\"toc-search\">\r\n <input\r\n type=\"text\"\r\n class=\"search-input\"\r\n [placeholder]=\"'toc.search_chapters' | translate\"\r\n [attr.aria-label]=\"'toc.search_label' | translate\"\r\n [(ngModel)]=\"searchQuery\"\r\n (ngModelChange)=\"onSearchChange()\" />\r\n <span class=\"search-icon\"><pw-icon name=\"lucideSearch\" /></span>\r\n </div>\r\n\r\n <!-- Content -->\r\n <div class=\"toc-content\">\r\n <!-- Chapter list -->\r\n <div class=\"chapter-list\" role=\"list\">\r\n @for (chapter of filteredChapters; track chapter.id; let i = $index) {\r\n <div\r\n class=\"chapter-item\"\r\n [class.selected]=\"i === selectedChapterIndex\"\r\n [class.current]=\"isCurrentChapter(chapter)\"\r\n role=\"listitem\"\r\n tabindex=\"0\"\r\n (click)=\"selectChapter(i)\"\r\n (keydown.enter)=\"selectChapter(i)\">\r\n \r\n <!-- Chapter header -->\r\n <div class=\"chapter-header\" tabindex=\"0\" (click)=\"navigateToChapter(chapter)\" (keydown.enter)=\"navigateToChapter(chapter)\">\r\n <span class=\"chapter-number\">{{ i + 1 }}</span>\r\n <h3 class=\"chapter-title\">{{ getChapterTitle(chapter) }}</h3>\r\n @if (isCurrentChapter(chapter)) {\r\n <span class=\"current-badge\">{{ 'toc.current' | translate }}</span>\r\n }\r\n </div>\r\n\r\n <!-- Panel list (if chapter selected) -->\r\n @if (i === selectedChapterIndex) {\r\n <div class=\"panel-list\">\r\n <!-- If chapter has pages, group by pages -->\r\n @if (hasPages(chapter)) {\r\n @for (page of getPages(chapter); track page.id) {\r\n <div class=\"page-group\">\r\n <div class=\"page-header\">\r\n <span class=\"page-title\">{{ getPageTitle(page) }}</span>\r\n </div>\r\n <div class=\"panels-grid\">\r\n @for (panelItem of getPanelsForPage(chapter, page); track panelItem.id) {\r\n <button\r\n class=\"panel-card\"\r\n [class.current]=\"isCurrentPanel(panelItem.id)\"\r\n type=\"button\"\r\n (click)=\"navigateToPanel(chapter, panelItem.id)\">\r\n \r\n @if (getThumbnailUrl(panelItem.panel)) {\r\n <div class=\"panel-thumbnail\">\r\n <img \r\n [src]=\"getThumbnailUrl(panelItem.panel)\" \r\n [alt]=\"getPanelTitle(panelItem.panel, panelItem.id)\"\r\n loading=\"lazy\" />\r\n @if (isCurrentPanel(panelItem.id)) {\r\n <div class=\"current-overlay\">\r\n <span class=\"current-icon\"><pw-icon name=\"lucidePlay\" /></span>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"panel-thumbnail placeholder\">\r\n <span class=\"placeholder-icon\"><pw-icon name=\"lucideImage\" /></span>\r\n </div>\r\n }\r\n \r\n <div class=\"panel-info\">\r\n <span class=\"panel-title\">{{ getPanelTitle(panelItem.panel, panelItem.id) }}</span>\r\n </div>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n } @else {\r\n <!-- No pages, show all panels directly -->\r\n <div class=\"panels-grid\">\r\n @for (panelItem of getPanels(chapter); track panelItem.id) {\r\n <button\r\n class=\"panel-card\"\r\n [class.current]=\"isCurrentPanel(panelItem.id)\"\r\n type=\"button\"\r\n (click)=\"navigateToPanel(chapter, panelItem.id)\">\r\n \r\n @if (getThumbnailUrl(panelItem.panel)) {\r\n <div class=\"panel-thumbnail\">\r\n <img \r\n [src]=\"getThumbnailUrl(panelItem.panel)\" \r\n [alt]=\"getPanelTitle(panelItem.panel, panelItem.id)\"\r\n loading=\"lazy\" />\r\n @if (isCurrentPanel(panelItem.id)) {\r\n <div class=\"current-overlay\">\r\n <span class=\"current-icon\"><pw-icon name=\"lucidePlay\" /></span>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"panel-thumbnail placeholder\">\r\n <span class=\"placeholder-icon\"><pw-icon name=\"lucideImage\" /></span>\r\n </div>\r\n }\r\n \r\n <div class=\"panel-info\">\r\n <span class=\"panel-title\">{{ getPanelTitle(panelItem.panel, panelItem.id) }}</span>\r\n </div>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (filteredChapters.length === 0) {\r\n <div class=\"empty-state\">\r\n <p>{{ 'toc.no_results' | translate: {query: searchQuery} }}</p>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"toc-footer\">\r\n <p class=\"footer-hint\">\r\n <kbd>\u2191</kbd> <kbd>\u2193</kbd> {{ 'toc.keyboard_hint_nav' | translate }}\r\n <kbd>Enter</kbd> {{ 'toc.keyboard_hint_select' | translate }}\r\n <kbd>{{ 'common.esc' | translate }}</kbd> {{ 'toc.keyboard_hint_close' | translate }}\r\n </p>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [".toc-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.toc-container{width:90%;max-width:800px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.toc-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.toc-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.toc-search{position:relative;padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.search-input{width:100%;padding:10px 40px 10px 16px;font-size:14px;color:#fff;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:8px;outline:none;transition:all .2s ease}.search-input::placeholder{color:#fff6}.search-input:focus{background:#ffffff14;border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.search-icon{position:absolute;right:36px;top:50%;transform:translateY(-50%);font-size:16px;pointer-events:none;opacity:.5}.toc-content{flex:1;overflow-y:auto;padding:16px 0}.toc-content::-webkit-scrollbar{width:8px}.toc-content::-webkit-scrollbar-track{background:#ffffff0d}.toc-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.toc-content::-webkit-scrollbar-thumb:hover{background:#ffffff4d}.chapter-list{display:flex;flex-direction:column;gap:8px;padding:0 24px}.chapter-item{border-radius:8px;transition:background .2s ease}.chapter-item.selected{background:#ffffff0d}.chapter-item.current{background:#0078d71a}.chapter-header{display:flex;align-items:center;gap:12px;padding:12px 16px;cursor:pointer;border-radius:8px;transition:all .2s ease}.chapter-header:hover{background:#ffffff1a}.chapter-number{display:flex;align-items:center;justify-content:center;width:32px;height:32px;font-size:14px;font-weight:600;color:#fff;background:#ffffff1a;border-radius:6px;flex-shrink:0}.chapter-item.current .chapter-number{background:#0078d7cc}.chapter-title{flex:1;font-size:16px;font-weight:500;color:#ffffffe6;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.current-badge{padding:4px 8px;font-size:11px;font-weight:600;color:#fff;background:#0078d7cc;border-radius:4px;text-transform:uppercase;letter-spacing:.5px;flex-shrink:0}.panel-list{padding:12px 16px 16px 60px;display:flex;flex-direction:column;gap:16px}.page-group{display:flex;flex-direction:column;gap:12px}.page-header{padding:8px 12px;background:#ffffff0d;border-left:3px solid rgba(0,120,215,.6);border-radius:4px}.page-title{font-size:13px;font-weight:600;color:#ffffffe6;text-transform:uppercase;letter-spacing:.5px}.panels-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}.panel-card{display:flex;flex-direction:column;background:#ffffff0d;border:2px solid rgba(255,255,255,.1);border-radius:8px;cursor:pointer;transition:all .2s ease;overflow:hidden;padding:0}.panel-card:hover{background:#ffffff14;border-color:#fff3;transform:translateY(-2px);box-shadow:0 4px 12px #0000004d}.panel-card:active{transform:translateY(0)}.panel-card.current{border-color:#0078d7cc;box-shadow:0 0 0 1px #0078d74d}.panel-thumbnail{position:relative;width:100%;aspect-ratio:4 / 3;background:#0000004d;overflow:hidden;display:flex;align-items:center;justify-content:center}.panel-thumbnail img{width:100%;height:100%;object-fit:cover;display:block}.panel-thumbnail.placeholder{background:linear-gradient(135deg,#0078d71a,#00b4781a)}.placeholder-icon{font-size:32px;opacity:.4}.current-overlay{position:absolute;inset:0;background:#0078d74d;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.current-icon{font-size:24px;color:#fff;filter:drop-shadow(0 2px 4px rgba(0,0,0,.5))}.panel-info{padding:8px 10px;display:flex;flex-direction:column;gap:4px}.panel-title{font-size:12px;font-weight:500;color:#ffffffe6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}.empty-state{padding:40px 24px;text-align:center}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.toc-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.footer-hint{display:flex;align-items:center;justify-content:center;gap:16px;font-size:12px;color:#ffffff80;margin:0}.footer-hint kbd{padding:4px 8px;font-size:11px;font-family:monospace;color:#fffc;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:4px}@media (max-width: 768px){.toc-container{width:95%;max-height:90vh}.toc-header{padding:16px 20px}.toc-title{font-size:18px}.toc-search{padding:12px 20px}.chapter-list{padding:0 20px}.chapter-header{padding:10px 12px;gap:10px}.chapter-number{width:28px;height:28px;font-size:13px}.chapter-title{font-size:15px}.panel-list{padding:10px 12px 12px 48px;gap:12px}.page-header{padding:6px 10px}.page-title{font-size:12px}.panels-grid{grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:10px}.panel-thumbnail{aspect-ratio:3 / 2}.placeholder-icon{font-size:24px}.current-icon{font-size:20px}.panel-info{padding:6px 8px}.panel-title{font-size:11px}.toc-footer{padding:12px 20px}.footer-hint{font-size:11px;gap:12px}}.close-btn:focus-visible,.search-input:focus-visible,.panel-card:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.toc-overlay,.toc-container,.close-btn,.chapter-header,.panel-card{animation:none;transition:none}}\n"] }]
|
|
14547
|
+
args: [{ selector: 'pw-toc-overlay', imports: [FormsModule, TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\n <div class=\"toc-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); onClose()\">\n <div class=\"toc-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Table of Contents\">\n <!-- Header -->\n <div class=\"toc-header\">\n <h2 class=\"toc-title\">{{ 'toc.title' | translate }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'toc.close' | translate\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Search -->\n <div class=\"toc-search\">\n <input\n type=\"text\"\n class=\"search-input\"\n [placeholder]=\"'toc.search_chapters' | translate\"\n [attr.aria-label]=\"'toc.search_label' | translate\"\n [(ngModel)]=\"searchQuery\"\n (ngModelChange)=\"onSearchChange()\" />\n <span class=\"search-icon\"><pw-icon name=\"lucideSearch\" /></span>\n </div>\n\n <!-- Content -->\n <div class=\"toc-content\">\n <!-- Chapter list -->\n <div class=\"chapter-list\" role=\"list\">\n @for (chapter of filteredChapters; track chapter.id; let i = $index) {\n <div\n class=\"chapter-item\"\n [class.selected]=\"i === selectedChapterIndex\"\n [class.current]=\"isCurrentChapter(chapter)\"\n role=\"listitem\"\n tabindex=\"0\"\n (click)=\"selectChapter(i)\"\n (keydown.enter)=\"selectChapter(i)\">\n \n <!-- Chapter header -->\n <div class=\"chapter-header\" tabindex=\"0\" (click)=\"navigateToChapter(chapter)\" (keydown.enter)=\"navigateToChapter(chapter)\">\n <span class=\"chapter-number\">{{ i + 1 }}</span>\n <h3 class=\"chapter-title\">{{ getChapterTitle(chapter) }}</h3>\n @if (isCurrentChapter(chapter)) {\n <span class=\"current-badge\">{{ 'toc.current' | translate }}</span>\n }\n </div>\n\n <!-- Panel list (if chapter selected) -->\n @if (i === selectedChapterIndex) {\n <div class=\"panel-list\">\n <!-- If chapter has pages, group by pages -->\n @if (hasPages(chapter)) {\n @for (page of getPages(chapter); track page.id) {\n <div class=\"page-group\">\n <div class=\"page-header\">\n <span class=\"page-title\">{{ getPageTitle(page) }}</span>\n </div>\n <div class=\"panels-grid\">\n @for (panelItem of getPanelsForPage(chapter, page); track panelItem.id) {\n <button\n class=\"panel-card\"\n [class.current]=\"isCurrentPanel(panelItem.id)\"\n type=\"button\"\n (click)=\"navigateToPanel(chapter, panelItem.id)\">\n \n @if (getThumbnailUrl(panelItem.panel)) {\n <div class=\"panel-thumbnail\">\n <img \n [src]=\"getThumbnailUrl(panelItem.panel)\" \n [alt]=\"getPanelTitle(panelItem.panel, panelItem.id)\"\n loading=\"lazy\" />\n @if (isCurrentPanel(panelItem.id)) {\n <div class=\"current-overlay\">\n <span class=\"current-icon\"><pw-icon name=\"lucidePlay\" /></span>\n </div>\n }\n </div>\n } @else {\n <div class=\"panel-thumbnail placeholder\">\n <span class=\"placeholder-icon\"><pw-icon name=\"lucideImage\" /></span>\n </div>\n }\n \n <div class=\"panel-info\">\n <span class=\"panel-title\">{{ getPanelTitle(panelItem.panel, panelItem.id) }}</span>\n </div>\n </button>\n }\n </div>\n </div>\n }\n } @else {\n <!-- No pages, show all panels directly -->\n <div class=\"panels-grid\">\n @for (panelItem of getPanels(chapter); track panelItem.id) {\n <button\n class=\"panel-card\"\n [class.current]=\"isCurrentPanel(panelItem.id)\"\n type=\"button\"\n (click)=\"navigateToPanel(chapter, panelItem.id)\">\n \n @if (getThumbnailUrl(panelItem.panel)) {\n <div class=\"panel-thumbnail\">\n <img \n [src]=\"getThumbnailUrl(panelItem.panel)\" \n [alt]=\"getPanelTitle(panelItem.panel, panelItem.id)\"\n loading=\"lazy\" />\n @if (isCurrentPanel(panelItem.id)) {\n <div class=\"current-overlay\">\n <span class=\"current-icon\"><pw-icon name=\"lucidePlay\" /></span>\n </div>\n }\n </div>\n } @else {\n <div class=\"panel-thumbnail placeholder\">\n <span class=\"placeholder-icon\"><pw-icon name=\"lucideImage\" /></span>\n </div>\n }\n \n <div class=\"panel-info\">\n <span class=\"panel-title\">{{ getPanelTitle(panelItem.panel, panelItem.id) }}</span>\n </div>\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (filteredChapters.length === 0) {\n <div class=\"empty-state\">\n <p>{{ 'toc.no_results' | translate: {query: searchQuery} }}</p>\n </div>\n }\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"toc-footer\">\n <p class=\"footer-hint\">\n <kbd>\u2191</kbd> <kbd>\u2193</kbd> {{ 'toc.keyboard_hint_nav' | translate }}\n <kbd>Enter</kbd> {{ 'toc.keyboard_hint_select' | translate }}\n <kbd>{{ 'common.esc' | translate }}</kbd> {{ 'toc.keyboard_hint_close' | translate }}\n </p>\n </div>\n </div>\n </div>\n}\n", styles: [".toc-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.toc-container{width:90%;max-width:800px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.toc-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.toc-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.toc-search{position:relative;padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.search-input{width:100%;padding:10px 40px 10px 16px;font-size:14px;color:#fff;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:8px;outline:none;transition:all .2s ease}.search-input::placeholder{color:#fff6}.search-input:focus{background:#ffffff14;border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.search-icon{position:absolute;right:36px;top:50%;transform:translateY(-50%);font-size:16px;pointer-events:none;opacity:.5}.toc-content{flex:1;overflow-y:auto;padding:16px 0}.toc-content::-webkit-scrollbar{width:8px}.toc-content::-webkit-scrollbar-track{background:#ffffff0d}.toc-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.toc-content::-webkit-scrollbar-thumb:hover{background:#ffffff4d}.chapter-list{display:flex;flex-direction:column;gap:8px;padding:0 24px}.chapter-item{border-radius:8px;transition:background .2s ease}.chapter-item.selected{background:#ffffff0d}.chapter-item.current{background:#0078d71a}.chapter-header{display:flex;align-items:center;gap:12px;padding:12px 16px;cursor:pointer;border-radius:8px;transition:all .2s ease}.chapter-header:hover{background:#ffffff1a}.chapter-number{display:flex;align-items:center;justify-content:center;width:32px;height:32px;font-size:14px;font-weight:600;color:#fff;background:#ffffff1a;border-radius:6px;flex-shrink:0}.chapter-item.current .chapter-number{background:#0078d7cc}.chapter-title{flex:1;font-size:16px;font-weight:500;color:#ffffffe6;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.current-badge{padding:4px 8px;font-size:11px;font-weight:600;color:#fff;background:#0078d7cc;border-radius:4px;text-transform:uppercase;letter-spacing:.5px;flex-shrink:0}.panel-list{padding:12px 16px 16px 60px;display:flex;flex-direction:column;gap:16px}.page-group{display:flex;flex-direction:column;gap:12px}.page-header{padding:8px 12px;background:#ffffff0d;border-left:3px solid rgba(0,120,215,.6);border-radius:4px}.page-title{font-size:13px;font-weight:600;color:#ffffffe6;text-transform:uppercase;letter-spacing:.5px}.panels-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}.panel-card{display:flex;flex-direction:column;background:#ffffff0d;border:2px solid rgba(255,255,255,.1);border-radius:8px;cursor:pointer;transition:all .2s ease;overflow:hidden;padding:0}.panel-card:hover{background:#ffffff14;border-color:#fff3;transform:translateY(-2px);box-shadow:0 4px 12px #0000004d}.panel-card:active{transform:translateY(0)}.panel-card.current{border-color:#0078d7cc;box-shadow:0 0 0 1px #0078d74d}.panel-thumbnail{position:relative;width:100%;aspect-ratio:4 / 3;background:#0000004d;overflow:hidden;display:flex;align-items:center;justify-content:center}.panel-thumbnail img{width:100%;height:100%;object-fit:cover;display:block}.panel-thumbnail.placeholder{background:linear-gradient(135deg,#0078d71a,#00b4781a)}.placeholder-icon{font-size:32px;opacity:.4}.current-overlay{position:absolute;inset:0;background:#0078d74d;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.current-icon{font-size:24px;color:#fff;filter:drop-shadow(0 2px 4px rgba(0,0,0,.5))}.panel-info{padding:8px 10px;display:flex;flex-direction:column;gap:4px}.panel-title{font-size:12px;font-weight:500;color:#ffffffe6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}.empty-state{padding:40px 24px;text-align:center}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.toc-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.footer-hint{display:flex;align-items:center;justify-content:center;gap:16px;font-size:12px;color:#ffffff80;margin:0}.footer-hint kbd{padding:4px 8px;font-size:11px;font-family:monospace;color:#fffc;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:4px}@media (max-width: 768px){.toc-container{width:95%;max-height:90vh}.toc-header{padding:16px 20px}.toc-title{font-size:18px}.toc-search{padding:12px 20px}.chapter-list{padding:0 20px}.chapter-header{padding:10px 12px;gap:10px}.chapter-number{width:28px;height:28px;font-size:13px}.chapter-title{font-size:15px}.panel-list{padding:10px 12px 12px 48px;gap:12px}.page-header{padding:6px 10px}.page-title{font-size:12px}.panels-grid{grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:10px}.panel-thumbnail{aspect-ratio:3 / 2}.placeholder-icon{font-size:24px}.current-icon{font-size:20px}.panel-info{padding:6px 8px}.panel-title{font-size:11px}.toc-footer{padding:12px 20px}.footer-hint{font-size:11px;gap:12px}}.close-btn:focus-visible,.search-input:focus-visible,.panel-card:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.toc-overlay,.toc-container,.close-btn,.chapter-header,.panel-card{animation:none;transition:none}}\n"] }]
|
|
14548
14548
|
}], propDecorators: { manifest: [{
|
|
14549
14549
|
type: Input
|
|
14550
14550
|
}], chapters: [{
|
|
@@ -14752,11 +14752,11 @@ class PaywallOverlayComponent {
|
|
|
14752
14752
|
return (PAYWALL_TEXT_EN[key] ?? key).replace(/\{\{\s*(\w+)\s*\}\}/g, (_m, name) => String(params?.[name] ?? ''));
|
|
14753
14753
|
}
|
|
14754
14754
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: PaywallOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
14755
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: PaywallOverlayComponent, isStandalone: true, selector: "pw-paywall-overlay", inputs: { visible: "visible", gate: "gate", purchaseOptions: "purchaseOptions", locale: "locale", title: "title", message: "message", allowPreview: "allowPreview", showLogin: "showLogin" }, outputs: { action: "action", purchase: "purchase", close: "close" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "<!-- Paywall Overlay -->\
|
|
14755
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: PaywallOverlayComponent, isStandalone: true, selector: "pw-paywall-overlay", inputs: { visible: "visible", gate: "gate", purchaseOptions: "purchaseOptions", locale: "locale", title: "title", message: "message", allowPreview: "allowPreview", showLogin: "showLogin" }, outputs: { action: "action", purchase: "purchase", close: "close" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "<!-- Paywall Overlay -->\n<div class=\"paywall-overlay\" [class.visible]=\"visible\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); onDismiss()\">\n <div class=\"paywall-modal\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"getTitle()\">\n <!-- Header -->\n <div class=\"paywall-header\">\n <h2 class=\"paywall-title\">{{ getTitle() }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"t('paywall.close')\"\n (click)=\"onDismiss()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"paywall-content\">\n <!-- Icon -->\n <div class=\"paywall-icon\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z\"/>\n </svg>\n </div>\n\n <!-- Message -->\n <p class=\"paywall-message\">{{ getMessage() }}</p>\n\n <!-- Preview Info -->\n @if (gate?.preview && allowPreview) {\n <div class=\"preview-info\">\n @if (gate?.preview?.previewPanels) {\n <p class=\"preview-text\">\n <pw-icon name=\"lucideCheck\" /> {{ t('paywall.preview_panels', { count: gate?.preview?.previewPanels }) }}\n </p>\n }\n @if (gate?.preview?.mode) {\n <p class=\"preview-mode\">\n {{ t('paywall.preview_mode', { mode: gate?.preview?.mode }) }}\n </p>\n }\n </div>\n }\n\n <!-- Purchase Options -->\n @if (purchaseOptions.length > 0) {\n <div class=\"purchase-options\">\n <h3 class=\"options-title\">{{ t('paywall.choose_option') }}</h3>\n @for (option of purchaseOptions; track option.productId) {\n <button\n class=\"purchase-option\"\n type=\"button\"\n (click)=\"onPurchaseOption(option.productId)\">\n <div class=\"option-header\">\n <span class=\"option-name\">{{ option.name }}</span>\n <span class=\"option-price\">{{ formatPrice(option.price.amount, option.price.currency) }}</span>\n </div>\n @if (option.description) {\n <p class=\"option-description\">{{ option.description }}</p>\n }\n <span class=\"option-type\">{{ getPurchaseTypeLabel(option.type) }}</span>\n </button>\n }\n </div>\n }\n\n <!-- Login Option -->\n @if (showLogin && !purchaseOptions.length) {\n <button\n class=\"login-btn\"\n type=\"button\"\n (click)=\"onLogin()\">\n {{ t('paywall.sign_in') }}\n </button>\n }\n\n <!-- Actions -->\n <div class=\"paywall-actions\">\n @if (purchaseOptions.length === 0 && !showLogin) {\n <button\n class=\"action-btn primary\"\n type=\"button\"\n (click)=\"onDismiss()\">\n {{ t('paywall.go_back') }}\n </button>\n }\n @if (purchaseOptions.length > 0 || showLogin) {\n <button\n class=\"action-btn secondary\"\n type=\"button\"\n (click)=\"onDismiss()\">\n {{ t('paywall.maybe_later') }}\n </button>\n }\n </div>\n\n <!-- Footer Info -->\n <div class=\"paywall-footer\">\n <p class=\"footer-text\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z\"/>\n </svg>\n {{ t('paywall.secure_payment') }}\n </p>\n </div>\n </div>\n </div>\n</div>\n", styles: [".paywall-overlay{position:fixed;inset:0;background:#000000bf;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:9999;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;padding:20px}.paywall-overlay.visible{opacity:1;visibility:visible}.paywall-modal{background:linear-gradient(135deg,#667eea,#764ba2);border-radius:16px;max-width:500px;width:100%;max-height:90vh;overflow-y:auto;box-shadow:0 20px 60px #0000004d;transform:scale(.9);transition:transform .3s ease}.paywall-overlay.visible .paywall-modal{transform:scale(1)}.paywall-header{display:flex;justify-content:space-between;align-items:center;padding:24px;border-bottom:1px solid rgba(255,255,255,.1)}.paywall-title{font-size:24px;font-weight:700;color:#fff;margin:0}.close-btn{background:#fff3;border:none;color:#fff;font-size:24px;width:36px;height:36px;border-radius:50%;cursor:pointer;transition:background .2s ease;display:flex;align-items:center;justify-content:center;padding:0}.close-btn:hover{background:#ffffff4d}.paywall-content{padding:32px 24px;text-align:center}.paywall-icon{width:80px;height:80px;margin:0 auto 24px;background:#ffffff26;border-radius:50%;display:flex;align-items:center;justify-content:center}.paywall-icon svg{width:48px;height:48px;color:#fff}.paywall-message{font-size:16px;line-height:1.5;color:#ffffffe6;margin:0 0 24px}.preview-info{background:#ffffff1a;border-radius:8px;padding:16px;margin-bottom:24px}.preview-text{font-size:14px;font-weight:600;color:#fff;margin:0 0 4px}.preview-mode{font-size:12px;color:#ffffffb3;margin:0}.purchase-options{margin-bottom:24px}.options-title{font-size:14px;font-weight:600;color:#fff;text-transform:uppercase;letter-spacing:.5px;margin:0 0 16px}.purchase-option{background:#fff;border:none;border-radius:12px;padding:20px;margin-bottom:12px;width:100%;cursor:pointer;transition:transform .2s ease,box-shadow .2s ease;text-align:left}.purchase-option:hover{transform:translateY(-2px);box-shadow:0 8px 20px #0003}.purchase-option:active{transform:translateY(0)}.option-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.option-name{font-size:18px;font-weight:700;color:#333}.option-price{font-size:24px;font-weight:700;color:#667eea}.option-description{font-size:14px;color:#666;margin:0 0 8px}.option-type{display:inline-block;font-size:12px;font-weight:600;color:#4350c4;background:#667eea1a;padding:4px 12px;border-radius:12px}.login-btn{background:#fff;border:none;color:#4350c4;font-size:16px;font-weight:700;padding:16px 32px;border-radius:12px;cursor:pointer;transition:transform .2s ease,box-shadow .2s ease;width:100%;margin-bottom:16px}.login-btn:hover{transform:translateY(-2px);box-shadow:0 4px 12px #0003}.paywall-actions{display:flex;gap:12px;margin-bottom:24px}.action-btn{flex:1;border:none;font-size:16px;font-weight:600;padding:14px 24px;border-radius:12px;cursor:pointer;transition:transform .2s ease,opacity .2s ease}.action-btn.primary{background:#fff;color:#667eea}.action-btn.secondary{background:#fff3;color:#fff}.action-btn:hover{transform:translateY(-2px);opacity:.9}.paywall-footer{border-top:1px solid rgba(255,255,255,.1);padding-top:16px}.footer-text{display:flex;align-items:center;justify-content:center;gap:8px;font-size:12px;color:#ffffffb3;margin:0}.footer-text svg{width:16px;height:16px}@media (max-width: 640px){.paywall-modal{max-width:100%;border-radius:12px}.paywall-header{padding:20px}.paywall-title{font-size:20px}.paywall-content{padding:24px 20px}.option-name{font-size:16px}.option-price{font-size:20px}}\n"], dependencies: [{ kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
14756
14756
|
}
|
|
14757
14757
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: PaywallOverlayComponent, decorators: [{
|
|
14758
14758
|
type: Component,
|
|
14759
|
-
args: [{ selector: 'pw-paywall-overlay', imports: [PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Paywall Overlay -->\
|
|
14759
|
+
args: [{ selector: 'pw-paywall-overlay', imports: [PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Paywall Overlay -->\n<div class=\"paywall-overlay\" [class.visible]=\"visible\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); onDismiss()\">\n <div class=\"paywall-modal\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"getTitle()\">\n <!-- Header -->\n <div class=\"paywall-header\">\n <h2 class=\"paywall-title\">{{ getTitle() }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"t('paywall.close')\"\n (click)=\"onDismiss()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"paywall-content\">\n <!-- Icon -->\n <div class=\"paywall-icon\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z\"/>\n </svg>\n </div>\n\n <!-- Message -->\n <p class=\"paywall-message\">{{ getMessage() }}</p>\n\n <!-- Preview Info -->\n @if (gate?.preview && allowPreview) {\n <div class=\"preview-info\">\n @if (gate?.preview?.previewPanels) {\n <p class=\"preview-text\">\n <pw-icon name=\"lucideCheck\" /> {{ t('paywall.preview_panels', { count: gate?.preview?.previewPanels }) }}\n </p>\n }\n @if (gate?.preview?.mode) {\n <p class=\"preview-mode\">\n {{ t('paywall.preview_mode', { mode: gate?.preview?.mode }) }}\n </p>\n }\n </div>\n }\n\n <!-- Purchase Options -->\n @if (purchaseOptions.length > 0) {\n <div class=\"purchase-options\">\n <h3 class=\"options-title\">{{ t('paywall.choose_option') }}</h3>\n @for (option of purchaseOptions; track option.productId) {\n <button\n class=\"purchase-option\"\n type=\"button\"\n (click)=\"onPurchaseOption(option.productId)\">\n <div class=\"option-header\">\n <span class=\"option-name\">{{ option.name }}</span>\n <span class=\"option-price\">{{ formatPrice(option.price.amount, option.price.currency) }}</span>\n </div>\n @if (option.description) {\n <p class=\"option-description\">{{ option.description }}</p>\n }\n <span class=\"option-type\">{{ getPurchaseTypeLabel(option.type) }}</span>\n </button>\n }\n </div>\n }\n\n <!-- Login Option -->\n @if (showLogin && !purchaseOptions.length) {\n <button\n class=\"login-btn\"\n type=\"button\"\n (click)=\"onLogin()\">\n {{ t('paywall.sign_in') }}\n </button>\n }\n\n <!-- Actions -->\n <div class=\"paywall-actions\">\n @if (purchaseOptions.length === 0 && !showLogin) {\n <button\n class=\"action-btn primary\"\n type=\"button\"\n (click)=\"onDismiss()\">\n {{ t('paywall.go_back') }}\n </button>\n }\n @if (purchaseOptions.length > 0 || showLogin) {\n <button\n class=\"action-btn secondary\"\n type=\"button\"\n (click)=\"onDismiss()\">\n {{ t('paywall.maybe_later') }}\n </button>\n }\n </div>\n\n <!-- Footer Info -->\n <div class=\"paywall-footer\">\n <p class=\"footer-text\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z\"/>\n </svg>\n {{ t('paywall.secure_payment') }}\n </p>\n </div>\n </div>\n </div>\n</div>\n", styles: [".paywall-overlay{position:fixed;inset:0;background:#000000bf;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:9999;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;padding:20px}.paywall-overlay.visible{opacity:1;visibility:visible}.paywall-modal{background:linear-gradient(135deg,#667eea,#764ba2);border-radius:16px;max-width:500px;width:100%;max-height:90vh;overflow-y:auto;box-shadow:0 20px 60px #0000004d;transform:scale(.9);transition:transform .3s ease}.paywall-overlay.visible .paywall-modal{transform:scale(1)}.paywall-header{display:flex;justify-content:space-between;align-items:center;padding:24px;border-bottom:1px solid rgba(255,255,255,.1)}.paywall-title{font-size:24px;font-weight:700;color:#fff;margin:0}.close-btn{background:#fff3;border:none;color:#fff;font-size:24px;width:36px;height:36px;border-radius:50%;cursor:pointer;transition:background .2s ease;display:flex;align-items:center;justify-content:center;padding:0}.close-btn:hover{background:#ffffff4d}.paywall-content{padding:32px 24px;text-align:center}.paywall-icon{width:80px;height:80px;margin:0 auto 24px;background:#ffffff26;border-radius:50%;display:flex;align-items:center;justify-content:center}.paywall-icon svg{width:48px;height:48px;color:#fff}.paywall-message{font-size:16px;line-height:1.5;color:#ffffffe6;margin:0 0 24px}.preview-info{background:#ffffff1a;border-radius:8px;padding:16px;margin-bottom:24px}.preview-text{font-size:14px;font-weight:600;color:#fff;margin:0 0 4px}.preview-mode{font-size:12px;color:#ffffffb3;margin:0}.purchase-options{margin-bottom:24px}.options-title{font-size:14px;font-weight:600;color:#fff;text-transform:uppercase;letter-spacing:.5px;margin:0 0 16px}.purchase-option{background:#fff;border:none;border-radius:12px;padding:20px;margin-bottom:12px;width:100%;cursor:pointer;transition:transform .2s ease,box-shadow .2s ease;text-align:left}.purchase-option:hover{transform:translateY(-2px);box-shadow:0 8px 20px #0003}.purchase-option:active{transform:translateY(0)}.option-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.option-name{font-size:18px;font-weight:700;color:#333}.option-price{font-size:24px;font-weight:700;color:#667eea}.option-description{font-size:14px;color:#666;margin:0 0 8px}.option-type{display:inline-block;font-size:12px;font-weight:600;color:#4350c4;background:#667eea1a;padding:4px 12px;border-radius:12px}.login-btn{background:#fff;border:none;color:#4350c4;font-size:16px;font-weight:700;padding:16px 32px;border-radius:12px;cursor:pointer;transition:transform .2s ease,box-shadow .2s ease;width:100%;margin-bottom:16px}.login-btn:hover{transform:translateY(-2px);box-shadow:0 4px 12px #0003}.paywall-actions{display:flex;gap:12px;margin-bottom:24px}.action-btn{flex:1;border:none;font-size:16px;font-weight:600;padding:14px 24px;border-radius:12px;cursor:pointer;transition:transform .2s ease,opacity .2s ease}.action-btn.primary{background:#fff;color:#667eea}.action-btn.secondary{background:#fff3;color:#fff}.action-btn:hover{transform:translateY(-2px);opacity:.9}.paywall-footer{border-top:1px solid rgba(255,255,255,.1);padding-top:16px}.footer-text{display:flex;align-items:center;justify-content:center;gap:8px;font-size:12px;color:#ffffffb3;margin:0}.footer-text svg{width:16px;height:16px}@media (max-width: 640px){.paywall-modal{max-width:100%;border-radius:12px}.paywall-header{padding:20px}.paywall-title{font-size:20px}.paywall-content{padding:24px 20px}.option-name{font-size:16px}.option-price{font-size:20px}}\n"] }]
|
|
14760
14760
|
}], propDecorators: { visible: [{
|
|
14761
14761
|
type: Input
|
|
14762
14762
|
}], gate: [{
|
|
@@ -15404,11 +15404,11 @@ class CharacterRosterComponent {
|
|
|
15404
15404
|
}
|
|
15405
15405
|
}
|
|
15406
15406
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CharacterRosterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
15407
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: CharacterRosterComponent, isStandalone: true, selector: "pw-character-roster", inputs: { characters: "characters", locale: "locale", baseUrl: "baseUrl", visible: "visible" }, outputs: { characterSelect: "characterSelect", close: "close" }, host: { listeners: { "window:keydown": "handleKeyboard($event)" } }, usesOnChanges: true, ngImport: i0, template: "@if (visible) {\r\n <div class=\"roster-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\r\n <div class=\"roster-container\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"currentView === 'list' ? 'Character Roster' : 'Character Details'\">\r\n \r\n <!-- LIST VIEW -->\r\n @if (currentView === 'list') {\r\n <!-- Header -->\r\n <div class=\"roster-header\">\r\n <h2 class=\"roster-title\">{{ 'character_roster.title' | translate }}</h2>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'character_roster.close' | translate\"\r\n (click)=\"onClose()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Search -->\r\n <div class=\"roster-search\">\r\n <input\r\n type=\"text\"\r\n class=\"search-input\"\r\n [placeholder]=\"'character_roster.search' | translate\"\r\n [attr.aria-label]=\"'character_roster.search_label' | translate\"\r\n [(ngModel)]=\"searchQuery\"\r\n (ngModelChange)=\"onSearchChange()\" />\r\n <span class=\"search-icon\"><pw-icon name=\"lucideSearch\" /></span>\r\n </div>\r\n\r\n <!-- Character grid -->\r\n <div class=\"roster-content\">\r\n @if (filteredCharacters.length > 0) {\r\n <div class=\"character-grid\" role=\"list\">\r\n @for (character of filteredCharacters; track character.id) {\r\n <button\r\n class=\"character-card\"\r\n type=\"button\"\r\n role=\"listitem\"\r\n (click)=\"selectCharacter(character)\">\r\n \r\n <!-- Avatar -->\r\n <div class=\"character-avatar\">\r\n @if (getAvatarUrl(character)) {\r\n <img\r\n [src]=\"getAvatarUrl(character)\"\r\n [alt]=\"getCharacterName(character)\"\r\n class=\"avatar-image\" />\r\n } @else {\r\n <div class=\"avatar-placeholder\">\r\n {{ getInitials(character) }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Info -->\r\n <div class=\"character-info\">\r\n <h3 class=\"character-name\">{{ getCharacterName(character) }}</h3>\r\n @if (character['role']) {\r\n <p class=\"character-role\">{{ character['role'] }}</p>\r\n }\r\n </div>\r\n </button>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"empty-state\">\r\n <p>{{ 'character_roster.no_results' | translate: {query: searchQuery} }}</p>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"roster-footer\">\r\n <p class=\"footer-hint\">\r\n <kbd>{{ 'common.esc' | translate }}</kbd> {{ 'character_roster.keyboard_hint' | translate }}\r\n </p>\r\n </div>\r\n }\r\n\r\n <!-- DETAIL VIEW -->\r\n @if (currentView === 'detail' && selectedCharacter) {\r\n <!-- Header with back button -->\r\n <div class=\"roster-header\">\r\n <button\r\n class=\"back-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'character_roster.back_to_list' | translate\"\r\n (click)=\"backToList()\">\r\n {{ 'character_roster.back_to_list' | translate }}\r\n </button>\r\n <h2 class=\"roster-title\">{{ getCharacterName(selectedCharacter) }}</h2>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'character_sheet.close' | translate\"\r\n (click)=\"onClose()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Content -->\r\n <div class=\"sheet-content\">\r\n <!-- Portrait -->\r\n <div class=\"portrait-section\">\r\n <div class=\"portrait-container\">\r\n @if (getAvatarUrl(selectedCharacter)) {\r\n <img\r\n [src]=\"getAvatarUrl(selectedCharacter)\"\r\n [alt]=\"getCharacterName(selectedCharacter)\"\r\n class=\"portrait-image\" />\r\n } @else {\r\n <div class=\"portrait-placeholder\">\r\n {{ getInitials(selectedCharacter) }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Role badge -->\r\n @if (selectedCharacter['role']) {\r\n <div class=\"role-badge\">\r\n {{ selectedCharacter['role'] }}\r\n </div>\r\n }\r\n\r\n <!-- Voice sample button -->\r\n @if (hasVoiceSample(selectedCharacter)) {\r\n <button\r\n class=\"voice-btn\"\r\n [class.playing]=\"isPlaying\"\r\n type=\"button\"\r\n [attr.aria-label]=\"(isPlaying ? 'character_sheet.stop_voice' : 'character_sheet.play_voice') | translate\"\r\n (click)=\"playVoiceSample(selectedCharacter)\">\r\n <span class=\"voice-icon\"><pw-icon [name]=\"isPlaying ? 'lucidePause' : 'lucideVolume2'\" /></span>\r\n <span class=\"voice-label\">{{ (isPlaying ? 'character_sheet.stop_voice' : 'character_sheet.play_voice') | translate }}</span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Bio -->\r\n <div class=\"bio-section\">\r\n <h3 class=\"bio-title\">Biography</h3>\r\n @if (getCharacterBio(selectedCharacter)) {\r\n <div class=\"bio-content\">\r\n {{ getCharacterBio(selectedCharacter) }}\r\n </div>\r\n } @else {\r\n <p class=\"bio-empty\">No biography available.</p>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sheet-footer\">\r\n <p class=\"footer-hint\">\r\n <kbd>{{ 'common.esc' | translate }}</kbd> {{ 'character_sheet.keyboard_hint' | translate }}\r\n </p>\r\n </div>\r\n }\r\n\r\n </div>\r\n </div>\r\n}\r\n", styles: [".roster-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.roster-container{width:90%;max-width:900px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.roster-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.roster-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.roster-search{position:relative;padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.search-input{width:100%;padding:10px 40px 10px 16px;font-size:14px;color:#fff;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:8px;outline:none;transition:all .2s ease}.search-input::placeholder{color:#fff6}.search-input:focus{background:#ffffff14;border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.search-icon{position:absolute;right:36px;top:50%;transform:translateY(-50%);font-size:16px;pointer-events:none;opacity:.5}.roster-content{flex:1;overflow-y:auto;padding:24px}.roster-content::-webkit-scrollbar{width:8px}.roster-content::-webkit-scrollbar-track{background:#ffffff0d}.roster-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.roster-content::-webkit-scrollbar-thumb:hover{background:#ffffff4d}.character-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px}.character-card{display:flex;flex-direction:column;align-items:center;padding:20px;background:#ffffff08;border:1px solid rgba(255,255,255,.1);border-radius:12px;cursor:pointer;transition:all .2s ease;text-align:center}.character-card:hover{background:#ffffff14;border-color:#fff3;transform:translateY(-4px);box-shadow:0 4px 12px #0000004d}.character-card:active{transform:translateY(-2px)}.character-avatar{width:120px;height:120px;margin-bottom:16px;border-radius:50%;overflow:hidden;flex-shrink:0}.avatar-image{width:100%;height:100%;object-fit:cover;display:block}.avatar-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:36px;font-weight:700;color:#ffffffe6;background:linear-gradient(135deg,#0078d74d,#00b4784d);border:2px solid rgba(255,255,255,.2)}.character-info{width:100%}.character-name{font-size:16px;font-weight:600;color:#ffffffe6;margin:0 0 4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.character-role{font-size:13px;color:#fff9;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.empty-state{padding:60px 24px;text-align:center}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.roster-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.footer-hint{display:flex;align-items:center;justify-content:center;gap:8px;font-size:12px;color:#ffffff80;margin:0}.footer-hint kbd{padding:4px 8px;font-size:11px;font-family:monospace;color:#fffc;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:4px}@media (max-width: 1024px){.character-grid{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px}.character-card{padding:16px}.character-avatar{width:100px;height:100px;margin-bottom:12px}.avatar-placeholder{font-size:30px}.character-name{font-size:15px}.character-role{font-size:12px}}@media (max-width: 768px){.roster-container{width:95%;max-height:90vh}.roster-header{padding:16px 20px}.roster-title{font-size:18px}.roster-search{padding:12px 20px}.roster-content{padding:20px}.character-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}.character-card{padding:14px}.character-avatar{width:90px;height:90px;margin-bottom:10px}.avatar-placeholder{font-size:28px}.character-name{font-size:14px}.character-role{font-size:11px}.empty-state{padding:40px 20px}.roster-footer{padding:12px 20px}}.close-btn:focus-visible,.search-input:focus-visible,.character-card:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}.back-btn{padding:8px 16px;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;white-space:nowrap}.back-btn:hover{background:#ffffff26;border-color:#ffffff4d}.back-btn:active{transform:scale(.95)}.sheet-content{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:24px}.portrait-section{display:flex;flex-direction:column;align-items:center;gap:16px}.portrait-container{width:200px;height:200px;border-radius:50%;overflow:hidden;border:4px solid rgba(255,255,255,.2);background:#ffffff0d}.portrait-image{width:100%;height:100%;object-fit:cover}.portrait-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:64px;font-weight:700;color:#ffffff80;background:linear-gradient(135deg,#0078d74d,#00b4784d)}.role-badge{padding:6px 16px;background:#0078d74d;border:1px solid rgba(0,120,215,.5);border-radius:20px;color:#fff;font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.voice-btn{display:flex;align-items:center;gap:8px;padding:12px 24px;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:8px;color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease}.voice-btn:hover{background:#ffffff26;border-color:#ffffff4d}.voice-btn.playing{background:#0078d74d;border-color:#0078d780}.voice-icon{font-size:18px}.bio-section{display:flex;flex-direction:column;gap:12px}.bio-title{margin:0;font-size:18px;font-weight:600;color:#ffffffe6;border-bottom:1px solid rgba(255,255,255,.1);padding-bottom:8px}.bio-content{color:#fffc;font-size:15px;line-height:1.6;white-space:pre-wrap}.bio-empty{color:#ffffff80;font-style:italic;margin:0}.sheet-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.1);background:#0003}@media (prefers-reduced-motion: reduce){.roster-overlay,.roster-container,.close-btn,.back-btn,.character-card,.voice-btn{animation:none;transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
15407
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: CharacterRosterComponent, isStandalone: true, selector: "pw-character-roster", inputs: { characters: "characters", locale: "locale", baseUrl: "baseUrl", visible: "visible" }, outputs: { characterSelect: "characterSelect", close: "close" }, host: { listeners: { "window:keydown": "handleKeyboard($event)" } }, usesOnChanges: true, ngImport: i0, template: "@if (visible) {\n <div class=\"roster-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\n <div class=\"roster-container\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"currentView === 'list' ? 'Character Roster' : 'Character Details'\">\n \n <!-- LIST VIEW -->\n @if (currentView === 'list') {\n <!-- Header -->\n <div class=\"roster-header\">\n <h2 class=\"roster-title\">{{ 'character_roster.title' | translate }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'character_roster.close' | translate\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Search -->\n <div class=\"roster-search\">\n <input\n type=\"text\"\n class=\"search-input\"\n [placeholder]=\"'character_roster.search' | translate\"\n [attr.aria-label]=\"'character_roster.search_label' | translate\"\n [(ngModel)]=\"searchQuery\"\n (ngModelChange)=\"onSearchChange()\" />\n <span class=\"search-icon\"><pw-icon name=\"lucideSearch\" /></span>\n </div>\n\n <!-- Character grid -->\n <div class=\"roster-content\">\n @if (filteredCharacters.length > 0) {\n <div class=\"character-grid\" role=\"list\">\n @for (character of filteredCharacters; track character.id) {\n <button\n class=\"character-card\"\n type=\"button\"\n role=\"listitem\"\n (click)=\"selectCharacter(character)\">\n \n <!-- Avatar -->\n <div class=\"character-avatar\">\n @if (getAvatarUrl(character)) {\n <img\n [src]=\"getAvatarUrl(character)\"\n [alt]=\"getCharacterName(character)\"\n class=\"avatar-image\" />\n } @else {\n <div class=\"avatar-placeholder\">\n {{ getInitials(character) }}\n </div>\n }\n </div>\n\n <!-- Info -->\n <div class=\"character-info\">\n <h3 class=\"character-name\">{{ getCharacterName(character) }}</h3>\n @if (character['role']) {\n <p class=\"character-role\">{{ character['role'] }}</p>\n }\n </div>\n </button>\n }\n </div>\n } @else {\n <div class=\"empty-state\">\n <p>{{ 'character_roster.no_results' | translate: {query: searchQuery} }}</p>\n </div>\n }\n </div>\n\n <!-- Footer -->\n <div class=\"roster-footer\">\n <p class=\"footer-hint\">\n <kbd>{{ 'common.esc' | translate }}</kbd> {{ 'character_roster.keyboard_hint' | translate }}\n </p>\n </div>\n }\n\n <!-- DETAIL VIEW -->\n @if (currentView === 'detail' && selectedCharacter) {\n <!-- Header with back button -->\n <div class=\"roster-header\">\n <button\n class=\"back-btn\"\n type=\"button\"\n [attr.aria-label]=\"'character_roster.back_to_list' | translate\"\n (click)=\"backToList()\">\n {{ 'character_roster.back_to_list' | translate }}\n </button>\n <h2 class=\"roster-title\">{{ getCharacterName(selectedCharacter) }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'character_sheet.close' | translate\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"sheet-content\">\n <!-- Portrait -->\n <div class=\"portrait-section\">\n <div class=\"portrait-container\">\n @if (getAvatarUrl(selectedCharacter)) {\n <img\n [src]=\"getAvatarUrl(selectedCharacter)\"\n [alt]=\"getCharacterName(selectedCharacter)\"\n class=\"portrait-image\" />\n } @else {\n <div class=\"portrait-placeholder\">\n {{ getInitials(selectedCharacter) }}\n </div>\n }\n </div>\n\n <!-- Role badge -->\n @if (selectedCharacter['role']) {\n <div class=\"role-badge\">\n {{ selectedCharacter['role'] }}\n </div>\n }\n\n <!-- Voice sample button -->\n @if (hasVoiceSample(selectedCharacter)) {\n <button\n class=\"voice-btn\"\n [class.playing]=\"isPlaying\"\n type=\"button\"\n [attr.aria-label]=\"(isPlaying ? 'character_sheet.stop_voice' : 'character_sheet.play_voice') | translate\"\n (click)=\"playVoiceSample(selectedCharacter)\">\n <span class=\"voice-icon\"><pw-icon [name]=\"isPlaying ? 'lucidePause' : 'lucideVolume2'\" /></span>\n <span class=\"voice-label\">{{ (isPlaying ? 'character_sheet.stop_voice' : 'character_sheet.play_voice') | translate }}</span>\n </button>\n }\n </div>\n\n <!-- Bio -->\n <div class=\"bio-section\">\n <h3 class=\"bio-title\">Biography</h3>\n @if (getCharacterBio(selectedCharacter)) {\n <div class=\"bio-content\">\n {{ getCharacterBio(selectedCharacter) }}\n </div>\n } @else {\n <p class=\"bio-empty\">No biography available.</p>\n }\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"sheet-footer\">\n <p class=\"footer-hint\">\n <kbd>{{ 'common.esc' | translate }}</kbd> {{ 'character_sheet.keyboard_hint' | translate }}\n </p>\n </div>\n }\n\n </div>\n </div>\n}\n", styles: [".roster-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.roster-container{width:90%;max-width:900px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.roster-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.roster-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.roster-search{position:relative;padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.search-input{width:100%;padding:10px 40px 10px 16px;font-size:14px;color:#fff;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:8px;outline:none;transition:all .2s ease}.search-input::placeholder{color:#fff6}.search-input:focus{background:#ffffff14;border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.search-icon{position:absolute;right:36px;top:50%;transform:translateY(-50%);font-size:16px;pointer-events:none;opacity:.5}.roster-content{flex:1;overflow-y:auto;padding:24px}.roster-content::-webkit-scrollbar{width:8px}.roster-content::-webkit-scrollbar-track{background:#ffffff0d}.roster-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.roster-content::-webkit-scrollbar-thumb:hover{background:#ffffff4d}.character-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px}.character-card{display:flex;flex-direction:column;align-items:center;padding:20px;background:#ffffff08;border:1px solid rgba(255,255,255,.1);border-radius:12px;cursor:pointer;transition:all .2s ease;text-align:center}.character-card:hover{background:#ffffff14;border-color:#fff3;transform:translateY(-4px);box-shadow:0 4px 12px #0000004d}.character-card:active{transform:translateY(-2px)}.character-avatar{width:120px;height:120px;margin-bottom:16px;border-radius:50%;overflow:hidden;flex-shrink:0}.avatar-image{width:100%;height:100%;object-fit:cover;display:block}.avatar-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:36px;font-weight:700;color:#ffffffe6;background:linear-gradient(135deg,#0078d74d,#00b4784d);border:2px solid rgba(255,255,255,.2)}.character-info{width:100%}.character-name{font-size:16px;font-weight:600;color:#ffffffe6;margin:0 0 4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.character-role{font-size:13px;color:#fff9;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.empty-state{padding:60px 24px;text-align:center}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.roster-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.footer-hint{display:flex;align-items:center;justify-content:center;gap:8px;font-size:12px;color:#ffffff80;margin:0}.footer-hint kbd{padding:4px 8px;font-size:11px;font-family:monospace;color:#fffc;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:4px}@media (max-width: 1024px){.character-grid{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px}.character-card{padding:16px}.character-avatar{width:100px;height:100px;margin-bottom:12px}.avatar-placeholder{font-size:30px}.character-name{font-size:15px}.character-role{font-size:12px}}@media (max-width: 768px){.roster-container{width:95%;max-height:90vh}.roster-header{padding:16px 20px}.roster-title{font-size:18px}.roster-search{padding:12px 20px}.roster-content{padding:20px}.character-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}.character-card{padding:14px}.character-avatar{width:90px;height:90px;margin-bottom:10px}.avatar-placeholder{font-size:28px}.character-name{font-size:14px}.character-role{font-size:11px}.empty-state{padding:40px 20px}.roster-footer{padding:12px 20px}}.close-btn:focus-visible,.search-input:focus-visible,.character-card:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}.back-btn{padding:8px 16px;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;white-space:nowrap}.back-btn:hover{background:#ffffff26;border-color:#ffffff4d}.back-btn:active{transform:scale(.95)}.sheet-content{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:24px}.portrait-section{display:flex;flex-direction:column;align-items:center;gap:16px}.portrait-container{width:200px;height:200px;border-radius:50%;overflow:hidden;border:4px solid rgba(255,255,255,.2);background:#ffffff0d}.portrait-image{width:100%;height:100%;object-fit:cover}.portrait-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:64px;font-weight:700;color:#ffffff80;background:linear-gradient(135deg,#0078d74d,#00b4784d)}.role-badge{padding:6px 16px;background:#0078d74d;border:1px solid rgba(0,120,215,.5);border-radius:20px;color:#fff;font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.voice-btn{display:flex;align-items:center;gap:8px;padding:12px 24px;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:8px;color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease}.voice-btn:hover{background:#ffffff26;border-color:#ffffff4d}.voice-btn.playing{background:#0078d74d;border-color:#0078d780}.voice-icon{font-size:18px}.bio-section{display:flex;flex-direction:column;gap:12px}.bio-title{margin:0;font-size:18px;font-weight:600;color:#ffffffe6;border-bottom:1px solid rgba(255,255,255,.1);padding-bottom:8px}.bio-content{color:#fffc;font-size:15px;line-height:1.6;white-space:pre-wrap}.bio-empty{color:#ffffff80;font-style:italic;margin:0}.sheet-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.1);background:#0003}@media (prefers-reduced-motion: reduce){.roster-overlay,.roster-container,.close-btn,.back-btn,.character-card,.voice-btn{animation:none;transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
15408
15408
|
}
|
|
15409
15409
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CharacterRosterComponent, decorators: [{
|
|
15410
15410
|
type: Component,
|
|
15411
|
-
args: [{ selector: 'pw-character-roster', imports: [FormsModule, TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\r\n <div class=\"roster-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\r\n <div class=\"roster-container\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"currentView === 'list' ? 'Character Roster' : 'Character Details'\">\r\n \r\n <!-- LIST VIEW -->\r\n @if (currentView === 'list') {\r\n <!-- Header -->\r\n <div class=\"roster-header\">\r\n <h2 class=\"roster-title\">{{ 'character_roster.title' | translate }}</h2>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'character_roster.close' | translate\"\r\n (click)=\"onClose()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Search -->\r\n <div class=\"roster-search\">\r\n <input\r\n type=\"text\"\r\n class=\"search-input\"\r\n [placeholder]=\"'character_roster.search' | translate\"\r\n [attr.aria-label]=\"'character_roster.search_label' | translate\"\r\n [(ngModel)]=\"searchQuery\"\r\n (ngModelChange)=\"onSearchChange()\" />\r\n <span class=\"search-icon\"><pw-icon name=\"lucideSearch\" /></span>\r\n </div>\r\n\r\n <!-- Character grid -->\r\n <div class=\"roster-content\">\r\n @if (filteredCharacters.length > 0) {\r\n <div class=\"character-grid\" role=\"list\">\r\n @for (character of filteredCharacters; track character.id) {\r\n <button\r\n class=\"character-card\"\r\n type=\"button\"\r\n role=\"listitem\"\r\n (click)=\"selectCharacter(character)\">\r\n \r\n <!-- Avatar -->\r\n <div class=\"character-avatar\">\r\n @if (getAvatarUrl(character)) {\r\n <img\r\n [src]=\"getAvatarUrl(character)\"\r\n [alt]=\"getCharacterName(character)\"\r\n class=\"avatar-image\" />\r\n } @else {\r\n <div class=\"avatar-placeholder\">\r\n {{ getInitials(character) }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Info -->\r\n <div class=\"character-info\">\r\n <h3 class=\"character-name\">{{ getCharacterName(character) }}</h3>\r\n @if (character['role']) {\r\n <p class=\"character-role\">{{ character['role'] }}</p>\r\n }\r\n </div>\r\n </button>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"empty-state\">\r\n <p>{{ 'character_roster.no_results' | translate: {query: searchQuery} }}</p>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"roster-footer\">\r\n <p class=\"footer-hint\">\r\n <kbd>{{ 'common.esc' | translate }}</kbd> {{ 'character_roster.keyboard_hint' | translate }}\r\n </p>\r\n </div>\r\n }\r\n\r\n <!-- DETAIL VIEW -->\r\n @if (currentView === 'detail' && selectedCharacter) {\r\n <!-- Header with back button -->\r\n <div class=\"roster-header\">\r\n <button\r\n class=\"back-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'character_roster.back_to_list' | translate\"\r\n (click)=\"backToList()\">\r\n {{ 'character_roster.back_to_list' | translate }}\r\n </button>\r\n <h2 class=\"roster-title\">{{ getCharacterName(selectedCharacter) }}</h2>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'character_sheet.close' | translate\"\r\n (click)=\"onClose()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Content -->\r\n <div class=\"sheet-content\">\r\n <!-- Portrait -->\r\n <div class=\"portrait-section\">\r\n <div class=\"portrait-container\">\r\n @if (getAvatarUrl(selectedCharacter)) {\r\n <img\r\n [src]=\"getAvatarUrl(selectedCharacter)\"\r\n [alt]=\"getCharacterName(selectedCharacter)\"\r\n class=\"portrait-image\" />\r\n } @else {\r\n <div class=\"portrait-placeholder\">\r\n {{ getInitials(selectedCharacter) }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Role badge -->\r\n @if (selectedCharacter['role']) {\r\n <div class=\"role-badge\">\r\n {{ selectedCharacter['role'] }}\r\n </div>\r\n }\r\n\r\n <!-- Voice sample button -->\r\n @if (hasVoiceSample(selectedCharacter)) {\r\n <button\r\n class=\"voice-btn\"\r\n [class.playing]=\"isPlaying\"\r\n type=\"button\"\r\n [attr.aria-label]=\"(isPlaying ? 'character_sheet.stop_voice' : 'character_sheet.play_voice') | translate\"\r\n (click)=\"playVoiceSample(selectedCharacter)\">\r\n <span class=\"voice-icon\"><pw-icon [name]=\"isPlaying ? 'lucidePause' : 'lucideVolume2'\" /></span>\r\n <span class=\"voice-label\">{{ (isPlaying ? 'character_sheet.stop_voice' : 'character_sheet.play_voice') | translate }}</span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Bio -->\r\n <div class=\"bio-section\">\r\n <h3 class=\"bio-title\">Biography</h3>\r\n @if (getCharacterBio(selectedCharacter)) {\r\n <div class=\"bio-content\">\r\n {{ getCharacterBio(selectedCharacter) }}\r\n </div>\r\n } @else {\r\n <p class=\"bio-empty\">No biography available.</p>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sheet-footer\">\r\n <p class=\"footer-hint\">\r\n <kbd>{{ 'common.esc' | translate }}</kbd> {{ 'character_sheet.keyboard_hint' | translate }}\r\n </p>\r\n </div>\r\n }\r\n\r\n </div>\r\n </div>\r\n}\r\n", styles: [".roster-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.roster-container{width:90%;max-width:900px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.roster-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.roster-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.roster-search{position:relative;padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.search-input{width:100%;padding:10px 40px 10px 16px;font-size:14px;color:#fff;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:8px;outline:none;transition:all .2s ease}.search-input::placeholder{color:#fff6}.search-input:focus{background:#ffffff14;border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.search-icon{position:absolute;right:36px;top:50%;transform:translateY(-50%);font-size:16px;pointer-events:none;opacity:.5}.roster-content{flex:1;overflow-y:auto;padding:24px}.roster-content::-webkit-scrollbar{width:8px}.roster-content::-webkit-scrollbar-track{background:#ffffff0d}.roster-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.roster-content::-webkit-scrollbar-thumb:hover{background:#ffffff4d}.character-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px}.character-card{display:flex;flex-direction:column;align-items:center;padding:20px;background:#ffffff08;border:1px solid rgba(255,255,255,.1);border-radius:12px;cursor:pointer;transition:all .2s ease;text-align:center}.character-card:hover{background:#ffffff14;border-color:#fff3;transform:translateY(-4px);box-shadow:0 4px 12px #0000004d}.character-card:active{transform:translateY(-2px)}.character-avatar{width:120px;height:120px;margin-bottom:16px;border-radius:50%;overflow:hidden;flex-shrink:0}.avatar-image{width:100%;height:100%;object-fit:cover;display:block}.avatar-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:36px;font-weight:700;color:#ffffffe6;background:linear-gradient(135deg,#0078d74d,#00b4784d);border:2px solid rgba(255,255,255,.2)}.character-info{width:100%}.character-name{font-size:16px;font-weight:600;color:#ffffffe6;margin:0 0 4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.character-role{font-size:13px;color:#fff9;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.empty-state{padding:60px 24px;text-align:center}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.roster-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.footer-hint{display:flex;align-items:center;justify-content:center;gap:8px;font-size:12px;color:#ffffff80;margin:0}.footer-hint kbd{padding:4px 8px;font-size:11px;font-family:monospace;color:#fffc;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:4px}@media (max-width: 1024px){.character-grid{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px}.character-card{padding:16px}.character-avatar{width:100px;height:100px;margin-bottom:12px}.avatar-placeholder{font-size:30px}.character-name{font-size:15px}.character-role{font-size:12px}}@media (max-width: 768px){.roster-container{width:95%;max-height:90vh}.roster-header{padding:16px 20px}.roster-title{font-size:18px}.roster-search{padding:12px 20px}.roster-content{padding:20px}.character-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}.character-card{padding:14px}.character-avatar{width:90px;height:90px;margin-bottom:10px}.avatar-placeholder{font-size:28px}.character-name{font-size:14px}.character-role{font-size:11px}.empty-state{padding:40px 20px}.roster-footer{padding:12px 20px}}.close-btn:focus-visible,.search-input:focus-visible,.character-card:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}.back-btn{padding:8px 16px;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;white-space:nowrap}.back-btn:hover{background:#ffffff26;border-color:#ffffff4d}.back-btn:active{transform:scale(.95)}.sheet-content{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:24px}.portrait-section{display:flex;flex-direction:column;align-items:center;gap:16px}.portrait-container{width:200px;height:200px;border-radius:50%;overflow:hidden;border:4px solid rgba(255,255,255,.2);background:#ffffff0d}.portrait-image{width:100%;height:100%;object-fit:cover}.portrait-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:64px;font-weight:700;color:#ffffff80;background:linear-gradient(135deg,#0078d74d,#00b4784d)}.role-badge{padding:6px 16px;background:#0078d74d;border:1px solid rgba(0,120,215,.5);border-radius:20px;color:#fff;font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.voice-btn{display:flex;align-items:center;gap:8px;padding:12px 24px;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:8px;color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease}.voice-btn:hover{background:#ffffff26;border-color:#ffffff4d}.voice-btn.playing{background:#0078d74d;border-color:#0078d780}.voice-icon{font-size:18px}.bio-section{display:flex;flex-direction:column;gap:12px}.bio-title{margin:0;font-size:18px;font-weight:600;color:#ffffffe6;border-bottom:1px solid rgba(255,255,255,.1);padding-bottom:8px}.bio-content{color:#fffc;font-size:15px;line-height:1.6;white-space:pre-wrap}.bio-empty{color:#ffffff80;font-style:italic;margin:0}.sheet-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.1);background:#0003}@media (prefers-reduced-motion: reduce){.roster-overlay,.roster-container,.close-btn,.back-btn,.character-card,.voice-btn{animation:none;transition:none}}\n"] }]
|
|
15411
|
+
args: [{ selector: 'pw-character-roster', imports: [FormsModule, TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\n <div class=\"roster-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\n <div class=\"roster-container\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"currentView === 'list' ? 'Character Roster' : 'Character Details'\">\n \n <!-- LIST VIEW -->\n @if (currentView === 'list') {\n <!-- Header -->\n <div class=\"roster-header\">\n <h2 class=\"roster-title\">{{ 'character_roster.title' | translate }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'character_roster.close' | translate\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Search -->\n <div class=\"roster-search\">\n <input\n type=\"text\"\n class=\"search-input\"\n [placeholder]=\"'character_roster.search' | translate\"\n [attr.aria-label]=\"'character_roster.search_label' | translate\"\n [(ngModel)]=\"searchQuery\"\n (ngModelChange)=\"onSearchChange()\" />\n <span class=\"search-icon\"><pw-icon name=\"lucideSearch\" /></span>\n </div>\n\n <!-- Character grid -->\n <div class=\"roster-content\">\n @if (filteredCharacters.length > 0) {\n <div class=\"character-grid\" role=\"list\">\n @for (character of filteredCharacters; track character.id) {\n <button\n class=\"character-card\"\n type=\"button\"\n role=\"listitem\"\n (click)=\"selectCharacter(character)\">\n \n <!-- Avatar -->\n <div class=\"character-avatar\">\n @if (getAvatarUrl(character)) {\n <img\n [src]=\"getAvatarUrl(character)\"\n [alt]=\"getCharacterName(character)\"\n class=\"avatar-image\" />\n } @else {\n <div class=\"avatar-placeholder\">\n {{ getInitials(character) }}\n </div>\n }\n </div>\n\n <!-- Info -->\n <div class=\"character-info\">\n <h3 class=\"character-name\">{{ getCharacterName(character) }}</h3>\n @if (character['role']) {\n <p class=\"character-role\">{{ character['role'] }}</p>\n }\n </div>\n </button>\n }\n </div>\n } @else {\n <div class=\"empty-state\">\n <p>{{ 'character_roster.no_results' | translate: {query: searchQuery} }}</p>\n </div>\n }\n </div>\n\n <!-- Footer -->\n <div class=\"roster-footer\">\n <p class=\"footer-hint\">\n <kbd>{{ 'common.esc' | translate }}</kbd> {{ 'character_roster.keyboard_hint' | translate }}\n </p>\n </div>\n }\n\n <!-- DETAIL VIEW -->\n @if (currentView === 'detail' && selectedCharacter) {\n <!-- Header with back button -->\n <div class=\"roster-header\">\n <button\n class=\"back-btn\"\n type=\"button\"\n [attr.aria-label]=\"'character_roster.back_to_list' | translate\"\n (click)=\"backToList()\">\n {{ 'character_roster.back_to_list' | translate }}\n </button>\n <h2 class=\"roster-title\">{{ getCharacterName(selectedCharacter) }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'character_sheet.close' | translate\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"sheet-content\">\n <!-- Portrait -->\n <div class=\"portrait-section\">\n <div class=\"portrait-container\">\n @if (getAvatarUrl(selectedCharacter)) {\n <img\n [src]=\"getAvatarUrl(selectedCharacter)\"\n [alt]=\"getCharacterName(selectedCharacter)\"\n class=\"portrait-image\" />\n } @else {\n <div class=\"portrait-placeholder\">\n {{ getInitials(selectedCharacter) }}\n </div>\n }\n </div>\n\n <!-- Role badge -->\n @if (selectedCharacter['role']) {\n <div class=\"role-badge\">\n {{ selectedCharacter['role'] }}\n </div>\n }\n\n <!-- Voice sample button -->\n @if (hasVoiceSample(selectedCharacter)) {\n <button\n class=\"voice-btn\"\n [class.playing]=\"isPlaying\"\n type=\"button\"\n [attr.aria-label]=\"(isPlaying ? 'character_sheet.stop_voice' : 'character_sheet.play_voice') | translate\"\n (click)=\"playVoiceSample(selectedCharacter)\">\n <span class=\"voice-icon\"><pw-icon [name]=\"isPlaying ? 'lucidePause' : 'lucideVolume2'\" /></span>\n <span class=\"voice-label\">{{ (isPlaying ? 'character_sheet.stop_voice' : 'character_sheet.play_voice') | translate }}</span>\n </button>\n }\n </div>\n\n <!-- Bio -->\n <div class=\"bio-section\">\n <h3 class=\"bio-title\">Biography</h3>\n @if (getCharacterBio(selectedCharacter)) {\n <div class=\"bio-content\">\n {{ getCharacterBio(selectedCharacter) }}\n </div>\n } @else {\n <p class=\"bio-empty\">No biography available.</p>\n }\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"sheet-footer\">\n <p class=\"footer-hint\">\n <kbd>{{ 'common.esc' | translate }}</kbd> {{ 'character_sheet.keyboard_hint' | translate }}\n </p>\n </div>\n }\n\n </div>\n </div>\n}\n", styles: [".roster-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.roster-container{width:90%;max-width:900px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.roster-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.roster-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.roster-search{position:relative;padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.search-input{width:100%;padding:10px 40px 10px 16px;font-size:14px;color:#fff;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:8px;outline:none;transition:all .2s ease}.search-input::placeholder{color:#fff6}.search-input:focus{background:#ffffff14;border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.search-icon{position:absolute;right:36px;top:50%;transform:translateY(-50%);font-size:16px;pointer-events:none;opacity:.5}.roster-content{flex:1;overflow-y:auto;padding:24px}.roster-content::-webkit-scrollbar{width:8px}.roster-content::-webkit-scrollbar-track{background:#ffffff0d}.roster-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.roster-content::-webkit-scrollbar-thumb:hover{background:#ffffff4d}.character-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px}.character-card{display:flex;flex-direction:column;align-items:center;padding:20px;background:#ffffff08;border:1px solid rgba(255,255,255,.1);border-radius:12px;cursor:pointer;transition:all .2s ease;text-align:center}.character-card:hover{background:#ffffff14;border-color:#fff3;transform:translateY(-4px);box-shadow:0 4px 12px #0000004d}.character-card:active{transform:translateY(-2px)}.character-avatar{width:120px;height:120px;margin-bottom:16px;border-radius:50%;overflow:hidden;flex-shrink:0}.avatar-image{width:100%;height:100%;object-fit:cover;display:block}.avatar-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:36px;font-weight:700;color:#ffffffe6;background:linear-gradient(135deg,#0078d74d,#00b4784d);border:2px solid rgba(255,255,255,.2)}.character-info{width:100%}.character-name{font-size:16px;font-weight:600;color:#ffffffe6;margin:0 0 4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.character-role{font-size:13px;color:#fff9;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.empty-state{padding:60px 24px;text-align:center}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.roster-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.footer-hint{display:flex;align-items:center;justify-content:center;gap:8px;font-size:12px;color:#ffffff80;margin:0}.footer-hint kbd{padding:4px 8px;font-size:11px;font-family:monospace;color:#fffc;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:4px}@media (max-width: 1024px){.character-grid{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px}.character-card{padding:16px}.character-avatar{width:100px;height:100px;margin-bottom:12px}.avatar-placeholder{font-size:30px}.character-name{font-size:15px}.character-role{font-size:12px}}@media (max-width: 768px){.roster-container{width:95%;max-height:90vh}.roster-header{padding:16px 20px}.roster-title{font-size:18px}.roster-search{padding:12px 20px}.roster-content{padding:20px}.character-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}.character-card{padding:14px}.character-avatar{width:90px;height:90px;margin-bottom:10px}.avatar-placeholder{font-size:28px}.character-name{font-size:14px}.character-role{font-size:11px}.empty-state{padding:40px 20px}.roster-footer{padding:12px 20px}}.close-btn:focus-visible,.search-input:focus-visible,.character-card:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}.back-btn{padding:8px 16px;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;white-space:nowrap}.back-btn:hover{background:#ffffff26;border-color:#ffffff4d}.back-btn:active{transform:scale(.95)}.sheet-content{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:24px}.portrait-section{display:flex;flex-direction:column;align-items:center;gap:16px}.portrait-container{width:200px;height:200px;border-radius:50%;overflow:hidden;border:4px solid rgba(255,255,255,.2);background:#ffffff0d}.portrait-image{width:100%;height:100%;object-fit:cover}.portrait-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:64px;font-weight:700;color:#ffffff80;background:linear-gradient(135deg,#0078d74d,#00b4784d)}.role-badge{padding:6px 16px;background:#0078d74d;border:1px solid rgba(0,120,215,.5);border-radius:20px;color:#fff;font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.voice-btn{display:flex;align-items:center;gap:8px;padding:12px 24px;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:8px;color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease}.voice-btn:hover{background:#ffffff26;border-color:#ffffff4d}.voice-btn.playing{background:#0078d74d;border-color:#0078d780}.voice-icon{font-size:18px}.bio-section{display:flex;flex-direction:column;gap:12px}.bio-title{margin:0;font-size:18px;font-weight:600;color:#ffffffe6;border-bottom:1px solid rgba(255,255,255,.1);padding-bottom:8px}.bio-content{color:#fffc;font-size:15px;line-height:1.6;white-space:pre-wrap}.bio-empty{color:#ffffff80;font-style:italic;margin:0}.sheet-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.1);background:#0003}@media (prefers-reduced-motion: reduce){.roster-overlay,.roster-container,.close-btn,.back-btn,.character-card,.voice-btn{animation:none;transition:none}}\n"] }]
|
|
15412
15412
|
}], propDecorators: { characters: [{
|
|
15413
15413
|
type: Input
|
|
15414
15414
|
}], locale: [{
|
|
@@ -15673,11 +15673,11 @@ class ExtrasViewerComponent {
|
|
|
15673
15673
|
}
|
|
15674
15674
|
}
|
|
15675
15675
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ExtrasViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
15676
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: ExtrasViewerComponent, isStandalone: true, selector: "pw-extras-viewer", inputs: { extras: "extras", initialExtraId: "initialExtraId", locale: "locale", baseUrl: "baseUrl", visible: "visible", hasPremiumAccess: "hasPremiumAccess" }, outputs: { purchase: "purchase", close: "close" }, host: { listeners: { "window:keydown": "handleKeyboard($event)" } }, usesOnChanges: true, ngImport: i0, template: "@if (visible) {\r\n <div class=\"extras-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\r\n <div class=\"extras-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Extras Viewer\">\r\n <!-- Header -->\r\n <div class=\"extras-header\">\r\n <h2 class=\"extras-title\">{{ 'extras.title' | translate }}</h2>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'extras.close' | translate\"\r\n (click)=\"onClose()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Filters -->\r\n <div class=\"extras-filters\">\r\n <button\r\n class=\"filter-btn\"\r\n [class.active]=\"filterType === 'all'\"\r\n type=\"button\"\r\n (click)=\"setFilter('all')\">\r\n {{ 'extras.all' | translate }} ({{ extras.length }})\r\n </button>\r\n @if (getExtrasByType('cover') > 0) {\r\n <button\r\n class=\"filter-btn\"\r\n [class.active]=\"filterType === 'cover'\"\r\n type=\"button\"\r\n (click)=\"setFilter('cover')\">\r\n {{ getTypeName('cover') }} ({{ getExtrasByType('cover') }})\r\n </button>\r\n }\r\n @if (getExtrasByType('art') > 0) {\r\n <button\r\n class=\"filter-btn\"\r\n [class.active]=\"filterType === 'art'\"\r\n type=\"button\"\r\n (click)=\"setFilter('art')\">\r\n {{ getTypeName('art') }} ({{ getExtrasByType('art') }})\r\n </button>\r\n }\r\n @if (getExtrasByType('bts') > 0) {\r\n <button\r\n class=\"filter-btn\"\r\n [class.active]=\"filterType === 'bts'\"\r\n type=\"button\"\r\n (click)=\"setFilter('bts')\">\r\n {{ getTypeName('bts') }} ({{ getExtrasByType('bts') }})\r\n </button>\r\n }\r\n @if (getExtrasByType('interview') > 0) {\r\n <button\r\n class=\"filter-btn\"\r\n [class.active]=\"filterType === 'interview'\"\r\n type=\"button\"\r\n (click)=\"setFilter('interview')\">\r\n {{ getTypeName('interview') }} ({{ getExtrasByType('interview') }})\r\n </button>\r\n }\r\n @if (getExtrasByType('other') > 0) {\r\n <button\r\n class=\"filter-btn\"\r\n [class.active]=\"filterType === 'other'\"\r\n type=\"button\"\r\n (click)=\"setFilter('other')\">\r\n {{ getTypeName('other') }} ({{ getExtrasByType('other') }})\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Gallery Grid -->\r\n <div class=\"extras-content\">\r\n @if (getFilteredExtras().length > 0) {\r\n <div class=\"extras-grid\" role=\"list\">\r\n @for (extra of getFilteredExtras(); track extra.id) {\r\n <div class=\"extra-card\" role=\"listitem\">\r\n <button\r\n class=\"extra-thumbnail\"\r\n [class.locked]=\"!isAccessible(extra)\"\r\n type=\"button\"\r\n (click)=\"openExtra(extra)\">\r\n \r\n @if (getThumbnailUrl(extra)) {\r\n <img\r\n [src]=\"getThumbnailUrl(extra)\"\r\n [alt]=\"getExtraTitle(extra)\"\r\n class=\"thumbnail-image\" />\r\n } @else {\r\n <div class=\"thumbnail-placeholder\">\r\n <pw-icon [name]=\"getMediaIcon(extra)\" />\r\n </div>\r\n }\r\n\r\n <!-- Lock overlay for gated content -->\r\n @if (!isAccessible(extra)) {\r\n <div class=\"lock-overlay\">\r\n <span class=\"lock-icon\"><pw-icon name=\"lucideLock\" /></span>\r\n <span class=\"lock-text\">{{ 'extras.locked_premium' | translate }}</span>\r\n </div>\r\n }\r\n\r\n <!-- Media type badge -->\r\n <div class=\"media-badge\">\r\n <pw-icon [name]=\"getMediaIcon(extra)\" />\r\n </div>\r\n </button>\r\n\r\n <div class=\"extra-info\">\r\n <h3 class=\"extra-title\">{{ getExtraTitle(extra) }}</h3>\r\n @if (getExtraDescription(extra)) {\r\n <p class=\"extra-description\">{{ getExtraDescription(extra) }}</p>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"empty-state\">\r\n <p>{{ 'extras.no_extras' | translate }}</p>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Detail View -->\r\n @if (selectedExtra) {\r\n <div class=\"detail-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); closeDetail()\">\r\n <div class=\"detail-container\">\r\n <!-- Detail Header -->\r\n <div class=\"detail-header\">\r\n <h3 class=\"detail-title\">{{ getExtraTitle(selectedExtra) }}</h3>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'extras.close_detail' | translate\"\r\n (click)=\"closeDetail()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Detail Content -->\r\n <div class=\"detail-content\">\r\n @if ((selectedExtra.mediaType === 'image' || !selectedExtra.mediaType) && getAssetUrl(selectedExtra)) {\r\n <img\r\n [src]=\"getAssetUrl(selectedExtra)\"\r\n [alt]=\"getExtraTitle(selectedExtra)\"\r\n class=\"detail-image\" />\r\n }\r\n\r\n @if (selectedExtra.mediaType === 'video') {\r\n <video\r\n [src]=\"getAssetUrl(selectedExtra)\"\r\n controls\r\n class=\"detail-video\">\r\n {{ 'extras.video_not_supported' | translate }}\r\n </video>\r\n }\r\n\r\n @if (selectedExtra.mediaType === 'audio') {\r\n <audio\r\n [src]=\"getAssetUrl(selectedExtra)\"\r\n controls\r\n class=\"detail-audio\">\r\n {{ 'extras.audio_not_supported' | translate }}\r\n </audio>\r\n }\r\n\r\n @if (selectedExtra.mediaType === 'document' && getAssetUrl(selectedExtra)) {\r\n <a\r\n class=\"action-btn detail-document-link\"\r\n [href]=\"getAssetUrl(selectedExtra)\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\">\r\n {{ 'extras.open_document' | translate }}\r\n </a>\r\n }\r\n\r\n @if (getExtraDescription(selectedExtra)) {\r\n <div class=\"detail-description\">\r\n {{ getExtraDescription(selectedExtra) }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Detail Footer -->\r\n <div class=\"detail-footer\">\r\n <button\r\n class=\"action-btn\"\r\n type=\"button\"\r\n (click)=\"closeDetail()\">\r\n {{ 'extras.close_button' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Paywall Notice (for gated content without access) -->\r\n @if (!hasPremiumAccess && hasGatedExtras()) {\r\n <div class=\"paywall-notice\">\r\n <p class=\"notice-text\">\r\n <span class=\"notice-icon\"><pw-icon name=\"lucideLock\" /></span>\r\n {{ 'extras.premium_notice' | translate }}\r\n </p>\r\n <button\r\n class=\"upgrade-btn\"\r\n type=\"button\"\r\n (click)=\"onPurchase()\">\r\n {{ 'extras.upgrade_now' | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [".extras-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.extras-container{width:90%;max-width:1000px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.extras-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.extras-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.extras-filters{display:flex;gap:8px;padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);overflow-x:auto;flex-shrink:0;scrollbar-width:thin}.extras-filters::-webkit-scrollbar{height:4px}.extras-filters::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.filter-btn{padding:8px 16px;font-size:13px;font-weight:500;color:#ffffffb3;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:20px;cursor:pointer;transition:all .2s ease;white-space:nowrap;flex-shrink:0}.filter-btn:hover{background:#ffffff1a;border-color:#fff3}.filter-btn.active{color:#fff;background:#0078d780;border-color:#0078d7b3}.extras-content{flex:1;overflow-y:auto;padding:24px}.extras-content::-webkit-scrollbar{width:8px}.extras-content::-webkit-scrollbar-track{background:#ffffff0d}.extras-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.extras-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:20px}.extra-card{display:flex;flex-direction:column;gap:12px}.extra-thumbnail{position:relative;aspect-ratio:3 / 4;background:#ffffff08;border:1px solid rgba(255,255,255,.1);border-radius:8px;overflow:hidden;cursor:pointer;transition:all .2s ease;padding:0}.extra-thumbnail:hover{border-color:#fff3;transform:translateY(-4px);box-shadow:0 4px 12px #0000004d}.extra-thumbnail.locked{cursor:not-allowed}.extra-thumbnail.locked:hover{transform:none;box-shadow:none}.thumbnail-image{width:100%;height:100%;object-fit:cover;display:block}.thumbnail-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:48px;background:linear-gradient(135deg,#0078d71a,#00b4781a)}.lock-overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;background:#000c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.lock-icon{font-size:32px}.lock-text{font-size:12px;font-weight:600;color:#ffffffe6;text-transform:uppercase;letter-spacing:.5px}.media-badge{position:absolute;top:8px;right:8px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:14px;background:#0009;border-radius:6px;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.extra-info{display:flex;flex-direction:column;gap:4px}.extra-title{font-size:14px;font-weight:600;color:#ffffffe6;margin:0;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.extra-description{font-size:12px;color:#fff9;margin:0;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.empty-state{padding:60px 24px;text-align:center}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.detail-overlay{position:fixed;inset:0;z-index:10;display:flex;align-items:center;justify-content:center;background:#000000e6;animation:fadeIn .2s ease}.detail-container{width:90%;max-width:800px;max-height:85vh;background:#1a1a1a;border-radius:12px;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}.detail-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.detail-title{font-size:18px;font-weight:600;color:#fff;margin:0}.detail-content{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:16px;align-items:center}.detail-image,.detail-video{max-width:100%;max-height:60vh;border-radius:8px}.detail-audio{width:100%;max-width:500px}.detail-description{font-size:14px;line-height:1.6;color:#fffc;white-space:pre-wrap;text-align:center;max-width:600px}.detail-footer{display:flex;justify-content:flex-end;padding:16px 20px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.action-btn{padding:10px 24px;font-size:14px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease}.action-btn:hover{background:#ffffff26;border-color:#ffffff4d}.paywall-notice{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 24px;background:#ffa5001a;border-top:1px solid rgba(255,165,0,.3);flex-shrink:0}.notice-text{display:flex;align-items:center;gap:8px;font-size:13px;color:#ffffffe6;margin:0}.notice-icon{font-size:16px}.upgrade-btn{padding:8px 20px;font-size:13px;font-weight:600;color:#fff;background:#ffa500cc;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease;white-space:nowrap}.upgrade-btn:hover{background:#ffa500e6;transform:translateY(-1px);box-shadow:0 2px 8px #ffa5004d}@media (max-width: 768px){.extras-container{width:95%}.extras-header{padding:16px 20px}.extras-title{font-size:18px}.extras-filters{padding:12px 20px}.extras-content{padding:20px}.extras-grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:16px}.extra-title{font-size:13px}.extra-description{font-size:11px}.detail-container{width:95%}.detail-header{padding:14px 18px}.detail-title{font-size:16px}.detail-content{padding:16px}.paywall-notice{flex-direction:column;align-items:flex-start;gap:12px}.upgrade-btn{width:100%}}.close-btn:focus-visible,.filter-btn:focus-visible,.extra-thumbnail:focus-visible,.action-btn:focus-visible,.upgrade-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.extras-overlay,.extras-container,.detail-overlay,.detail-container,.close-btn,.filter-btn,.extra-thumbnail,.action-btn,.upgrade-btn{animation:none;transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
15676
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: ExtrasViewerComponent, isStandalone: true, selector: "pw-extras-viewer", inputs: { extras: "extras", initialExtraId: "initialExtraId", locale: "locale", baseUrl: "baseUrl", visible: "visible", hasPremiumAccess: "hasPremiumAccess" }, outputs: { purchase: "purchase", close: "close" }, host: { listeners: { "window:keydown": "handleKeyboard($event)" } }, usesOnChanges: true, ngImport: i0, template: "@if (visible) {\n <div class=\"extras-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\n <div class=\"extras-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Extras Viewer\">\n <!-- Header -->\n <div class=\"extras-header\">\n <h2 class=\"extras-title\">{{ 'extras.title' | translate }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'extras.close' | translate\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Filters -->\n <div class=\"extras-filters\">\n <button\n class=\"filter-btn\"\n [class.active]=\"filterType === 'all'\"\n type=\"button\"\n (click)=\"setFilter('all')\">\n {{ 'extras.all' | translate }} ({{ extras.length }})\n </button>\n @if (getExtrasByType('cover') > 0) {\n <button\n class=\"filter-btn\"\n [class.active]=\"filterType === 'cover'\"\n type=\"button\"\n (click)=\"setFilter('cover')\">\n {{ getTypeName('cover') }} ({{ getExtrasByType('cover') }})\n </button>\n }\n @if (getExtrasByType('art') > 0) {\n <button\n class=\"filter-btn\"\n [class.active]=\"filterType === 'art'\"\n type=\"button\"\n (click)=\"setFilter('art')\">\n {{ getTypeName('art') }} ({{ getExtrasByType('art') }})\n </button>\n }\n @if (getExtrasByType('bts') > 0) {\n <button\n class=\"filter-btn\"\n [class.active]=\"filterType === 'bts'\"\n type=\"button\"\n (click)=\"setFilter('bts')\">\n {{ getTypeName('bts') }} ({{ getExtrasByType('bts') }})\n </button>\n }\n @if (getExtrasByType('interview') > 0) {\n <button\n class=\"filter-btn\"\n [class.active]=\"filterType === 'interview'\"\n type=\"button\"\n (click)=\"setFilter('interview')\">\n {{ getTypeName('interview') }} ({{ getExtrasByType('interview') }})\n </button>\n }\n @if (getExtrasByType('other') > 0) {\n <button\n class=\"filter-btn\"\n [class.active]=\"filterType === 'other'\"\n type=\"button\"\n (click)=\"setFilter('other')\">\n {{ getTypeName('other') }} ({{ getExtrasByType('other') }})\n </button>\n }\n </div>\n\n <!-- Gallery Grid -->\n <div class=\"extras-content\">\n @if (getFilteredExtras().length > 0) {\n <div class=\"extras-grid\" role=\"list\">\n @for (extra of getFilteredExtras(); track extra.id) {\n <div class=\"extra-card\" role=\"listitem\">\n <button\n class=\"extra-thumbnail\"\n [class.locked]=\"!isAccessible(extra)\"\n type=\"button\"\n (click)=\"openExtra(extra)\">\n \n @if (getThumbnailUrl(extra)) {\n <img\n [src]=\"getThumbnailUrl(extra)\"\n [alt]=\"getExtraTitle(extra)\"\n class=\"thumbnail-image\" />\n } @else {\n <div class=\"thumbnail-placeholder\">\n <pw-icon [name]=\"getMediaIcon(extra)\" />\n </div>\n }\n\n <!-- Lock overlay for gated content -->\n @if (!isAccessible(extra)) {\n <div class=\"lock-overlay\">\n <span class=\"lock-icon\"><pw-icon name=\"lucideLock\" /></span>\n <span class=\"lock-text\">{{ 'extras.locked_premium' | translate }}</span>\n </div>\n }\n\n <!-- Media type badge -->\n <div class=\"media-badge\">\n <pw-icon [name]=\"getMediaIcon(extra)\" />\n </div>\n </button>\n\n <div class=\"extra-info\">\n <h3 class=\"extra-title\">{{ getExtraTitle(extra) }}</h3>\n @if (getExtraDescription(extra)) {\n <p class=\"extra-description\">{{ getExtraDescription(extra) }}</p>\n }\n </div>\n </div>\n }\n </div>\n } @else {\n <div class=\"empty-state\">\n <p>{{ 'extras.no_extras' | translate }}</p>\n </div>\n }\n </div>\n\n <!-- Detail View -->\n @if (selectedExtra) {\n <div class=\"detail-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); closeDetail()\">\n <div class=\"detail-container\">\n <!-- Detail Header -->\n <div class=\"detail-header\">\n <h3 class=\"detail-title\">{{ getExtraTitle(selectedExtra) }}</h3>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'extras.close_detail' | translate\"\n (click)=\"closeDetail()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Detail Content -->\n <div class=\"detail-content\">\n @if ((selectedExtra.mediaType === 'image' || !selectedExtra.mediaType) && getAssetUrl(selectedExtra)) {\n <img\n [src]=\"getAssetUrl(selectedExtra)\"\n [alt]=\"getExtraTitle(selectedExtra)\"\n class=\"detail-image\" />\n }\n\n @if (selectedExtra.mediaType === 'video') {\n <video\n [src]=\"getAssetUrl(selectedExtra)\"\n controls\n class=\"detail-video\">\n {{ 'extras.video_not_supported' | translate }}\n </video>\n }\n\n @if (selectedExtra.mediaType === 'audio') {\n <audio\n [src]=\"getAssetUrl(selectedExtra)\"\n controls\n class=\"detail-audio\">\n {{ 'extras.audio_not_supported' | translate }}\n </audio>\n }\n\n @if (selectedExtra.mediaType === 'document' && getAssetUrl(selectedExtra)) {\n <a\n class=\"action-btn detail-document-link\"\n [href]=\"getAssetUrl(selectedExtra)\"\n target=\"_blank\"\n rel=\"noopener noreferrer\">\n {{ 'extras.open_document' | translate }}\n </a>\n }\n\n @if (getExtraDescription(selectedExtra)) {\n <div class=\"detail-description\">\n {{ getExtraDescription(selectedExtra) }}\n </div>\n }\n </div>\n\n <!-- Detail Footer -->\n <div class=\"detail-footer\">\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"closeDetail()\">\n {{ 'extras.close_button' | translate }}\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Paywall Notice (for gated content without access) -->\n @if (!hasPremiumAccess && hasGatedExtras()) {\n <div class=\"paywall-notice\">\n <p class=\"notice-text\">\n <span class=\"notice-icon\"><pw-icon name=\"lucideLock\" /></span>\n {{ 'extras.premium_notice' | translate }}\n </p>\n <button\n class=\"upgrade-btn\"\n type=\"button\"\n (click)=\"onPurchase()\">\n {{ 'extras.upgrade_now' | translate }}\n </button>\n </div>\n }\n </div>\n </div>\n}\n", styles: [".extras-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.extras-container{width:90%;max-width:1000px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.extras-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.extras-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.extras-filters{display:flex;gap:8px;padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);overflow-x:auto;flex-shrink:0;scrollbar-width:thin}.extras-filters::-webkit-scrollbar{height:4px}.extras-filters::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.filter-btn{padding:8px 16px;font-size:13px;font-weight:500;color:#ffffffb3;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:20px;cursor:pointer;transition:all .2s ease;white-space:nowrap;flex-shrink:0}.filter-btn:hover{background:#ffffff1a;border-color:#fff3}.filter-btn.active{color:#fff;background:#0078d780;border-color:#0078d7b3}.extras-content{flex:1;overflow-y:auto;padding:24px}.extras-content::-webkit-scrollbar{width:8px}.extras-content::-webkit-scrollbar-track{background:#ffffff0d}.extras-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.extras-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:20px}.extra-card{display:flex;flex-direction:column;gap:12px}.extra-thumbnail{position:relative;aspect-ratio:3 / 4;background:#ffffff08;border:1px solid rgba(255,255,255,.1);border-radius:8px;overflow:hidden;cursor:pointer;transition:all .2s ease;padding:0}.extra-thumbnail:hover{border-color:#fff3;transform:translateY(-4px);box-shadow:0 4px 12px #0000004d}.extra-thumbnail.locked{cursor:not-allowed}.extra-thumbnail.locked:hover{transform:none;box-shadow:none}.thumbnail-image{width:100%;height:100%;object-fit:cover;display:block}.thumbnail-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:48px;background:linear-gradient(135deg,#0078d71a,#00b4781a)}.lock-overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;background:#000c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.lock-icon{font-size:32px}.lock-text{font-size:12px;font-weight:600;color:#ffffffe6;text-transform:uppercase;letter-spacing:.5px}.media-badge{position:absolute;top:8px;right:8px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:14px;background:#0009;border-radius:6px;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.extra-info{display:flex;flex-direction:column;gap:4px}.extra-title{font-size:14px;font-weight:600;color:#ffffffe6;margin:0;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.extra-description{font-size:12px;color:#fff9;margin:0;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.empty-state{padding:60px 24px;text-align:center}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.detail-overlay{position:fixed;inset:0;z-index:10;display:flex;align-items:center;justify-content:center;background:#000000e6;animation:fadeIn .2s ease}.detail-container{width:90%;max-width:800px;max-height:85vh;background:#1a1a1a;border-radius:12px;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}.detail-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.detail-title{font-size:18px;font-weight:600;color:#fff;margin:0}.detail-content{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:16px;align-items:center}.detail-image,.detail-video{max-width:100%;max-height:60vh;border-radius:8px}.detail-audio{width:100%;max-width:500px}.detail-description{font-size:14px;line-height:1.6;color:#fffc;white-space:pre-wrap;text-align:center;max-width:600px}.detail-footer{display:flex;justify-content:flex-end;padding:16px 20px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.action-btn{padding:10px 24px;font-size:14px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease}.action-btn:hover{background:#ffffff26;border-color:#ffffff4d}.paywall-notice{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 24px;background:#ffa5001a;border-top:1px solid rgba(255,165,0,.3);flex-shrink:0}.notice-text{display:flex;align-items:center;gap:8px;font-size:13px;color:#ffffffe6;margin:0}.notice-icon{font-size:16px}.upgrade-btn{padding:8px 20px;font-size:13px;font-weight:600;color:#fff;background:#ffa500cc;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease;white-space:nowrap}.upgrade-btn:hover{background:#ffa500e6;transform:translateY(-1px);box-shadow:0 2px 8px #ffa5004d}@media (max-width: 768px){.extras-container{width:95%}.extras-header{padding:16px 20px}.extras-title{font-size:18px}.extras-filters{padding:12px 20px}.extras-content{padding:20px}.extras-grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:16px}.extra-title{font-size:13px}.extra-description{font-size:11px}.detail-container{width:95%}.detail-header{padding:14px 18px}.detail-title{font-size:16px}.detail-content{padding:16px}.paywall-notice{flex-direction:column;align-items:flex-start;gap:12px}.upgrade-btn{width:100%}}.close-btn:focus-visible,.filter-btn:focus-visible,.extra-thumbnail:focus-visible,.action-btn:focus-visible,.upgrade-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.extras-overlay,.extras-container,.detail-overlay,.detail-container,.close-btn,.filter-btn,.extra-thumbnail,.action-btn,.upgrade-btn{animation:none;transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
15677
15677
|
}
|
|
15678
15678
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ExtrasViewerComponent, decorators: [{
|
|
15679
15679
|
type: Component,
|
|
15680
|
-
args: [{ selector: 'pw-extras-viewer', imports: [TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\r\n <div class=\"extras-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\r\n <div class=\"extras-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Extras Viewer\">\r\n <!-- Header -->\r\n <div class=\"extras-header\">\r\n <h2 class=\"extras-title\">{{ 'extras.title' | translate }}</h2>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'extras.close' | translate\"\r\n (click)=\"onClose()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Filters -->\r\n <div class=\"extras-filters\">\r\n <button\r\n class=\"filter-btn\"\r\n [class.active]=\"filterType === 'all'\"\r\n type=\"button\"\r\n (click)=\"setFilter('all')\">\r\n {{ 'extras.all' | translate }} ({{ extras.length }})\r\n </button>\r\n @if (getExtrasByType('cover') > 0) {\r\n <button\r\n class=\"filter-btn\"\r\n [class.active]=\"filterType === 'cover'\"\r\n type=\"button\"\r\n (click)=\"setFilter('cover')\">\r\n {{ getTypeName('cover') }} ({{ getExtrasByType('cover') }})\r\n </button>\r\n }\r\n @if (getExtrasByType('art') > 0) {\r\n <button\r\n class=\"filter-btn\"\r\n [class.active]=\"filterType === 'art'\"\r\n type=\"button\"\r\n (click)=\"setFilter('art')\">\r\n {{ getTypeName('art') }} ({{ getExtrasByType('art') }})\r\n </button>\r\n }\r\n @if (getExtrasByType('bts') > 0) {\r\n <button\r\n class=\"filter-btn\"\r\n [class.active]=\"filterType === 'bts'\"\r\n type=\"button\"\r\n (click)=\"setFilter('bts')\">\r\n {{ getTypeName('bts') }} ({{ getExtrasByType('bts') }})\r\n </button>\r\n }\r\n @if (getExtrasByType('interview') > 0) {\r\n <button\r\n class=\"filter-btn\"\r\n [class.active]=\"filterType === 'interview'\"\r\n type=\"button\"\r\n (click)=\"setFilter('interview')\">\r\n {{ getTypeName('interview') }} ({{ getExtrasByType('interview') }})\r\n </button>\r\n }\r\n @if (getExtrasByType('other') > 0) {\r\n <button\r\n class=\"filter-btn\"\r\n [class.active]=\"filterType === 'other'\"\r\n type=\"button\"\r\n (click)=\"setFilter('other')\">\r\n {{ getTypeName('other') }} ({{ getExtrasByType('other') }})\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Gallery Grid -->\r\n <div class=\"extras-content\">\r\n @if (getFilteredExtras().length > 0) {\r\n <div class=\"extras-grid\" role=\"list\">\r\n @for (extra of getFilteredExtras(); track extra.id) {\r\n <div class=\"extra-card\" role=\"listitem\">\r\n <button\r\n class=\"extra-thumbnail\"\r\n [class.locked]=\"!isAccessible(extra)\"\r\n type=\"button\"\r\n (click)=\"openExtra(extra)\">\r\n \r\n @if (getThumbnailUrl(extra)) {\r\n <img\r\n [src]=\"getThumbnailUrl(extra)\"\r\n [alt]=\"getExtraTitle(extra)\"\r\n class=\"thumbnail-image\" />\r\n } @else {\r\n <div class=\"thumbnail-placeholder\">\r\n <pw-icon [name]=\"getMediaIcon(extra)\" />\r\n </div>\r\n }\r\n\r\n <!-- Lock overlay for gated content -->\r\n @if (!isAccessible(extra)) {\r\n <div class=\"lock-overlay\">\r\n <span class=\"lock-icon\"><pw-icon name=\"lucideLock\" /></span>\r\n <span class=\"lock-text\">{{ 'extras.locked_premium' | translate }}</span>\r\n </div>\r\n }\r\n\r\n <!-- Media type badge -->\r\n <div class=\"media-badge\">\r\n <pw-icon [name]=\"getMediaIcon(extra)\" />\r\n </div>\r\n </button>\r\n\r\n <div class=\"extra-info\">\r\n <h3 class=\"extra-title\">{{ getExtraTitle(extra) }}</h3>\r\n @if (getExtraDescription(extra)) {\r\n <p class=\"extra-description\">{{ getExtraDescription(extra) }}</p>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"empty-state\">\r\n <p>{{ 'extras.no_extras' | translate }}</p>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Detail View -->\r\n @if (selectedExtra) {\r\n <div class=\"detail-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); closeDetail()\">\r\n <div class=\"detail-container\">\r\n <!-- Detail Header -->\r\n <div class=\"detail-header\">\r\n <h3 class=\"detail-title\">{{ getExtraTitle(selectedExtra) }}</h3>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'extras.close_detail' | translate\"\r\n (click)=\"closeDetail()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Detail Content -->\r\n <div class=\"detail-content\">\r\n @if ((selectedExtra.mediaType === 'image' || !selectedExtra.mediaType) && getAssetUrl(selectedExtra)) {\r\n <img\r\n [src]=\"getAssetUrl(selectedExtra)\"\r\n [alt]=\"getExtraTitle(selectedExtra)\"\r\n class=\"detail-image\" />\r\n }\r\n\r\n @if (selectedExtra.mediaType === 'video') {\r\n <video\r\n [src]=\"getAssetUrl(selectedExtra)\"\r\n controls\r\n class=\"detail-video\">\r\n {{ 'extras.video_not_supported' | translate }}\r\n </video>\r\n }\r\n\r\n @if (selectedExtra.mediaType === 'audio') {\r\n <audio\r\n [src]=\"getAssetUrl(selectedExtra)\"\r\n controls\r\n class=\"detail-audio\">\r\n {{ 'extras.audio_not_supported' | translate }}\r\n </audio>\r\n }\r\n\r\n @if (selectedExtra.mediaType === 'document' && getAssetUrl(selectedExtra)) {\r\n <a\r\n class=\"action-btn detail-document-link\"\r\n [href]=\"getAssetUrl(selectedExtra)\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\">\r\n {{ 'extras.open_document' | translate }}\r\n </a>\r\n }\r\n\r\n @if (getExtraDescription(selectedExtra)) {\r\n <div class=\"detail-description\">\r\n {{ getExtraDescription(selectedExtra) }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Detail Footer -->\r\n <div class=\"detail-footer\">\r\n <button\r\n class=\"action-btn\"\r\n type=\"button\"\r\n (click)=\"closeDetail()\">\r\n {{ 'extras.close_button' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Paywall Notice (for gated content without access) -->\r\n @if (!hasPremiumAccess && hasGatedExtras()) {\r\n <div class=\"paywall-notice\">\r\n <p class=\"notice-text\">\r\n <span class=\"notice-icon\"><pw-icon name=\"lucideLock\" /></span>\r\n {{ 'extras.premium_notice' | translate }}\r\n </p>\r\n <button\r\n class=\"upgrade-btn\"\r\n type=\"button\"\r\n (click)=\"onPurchase()\">\r\n {{ 'extras.upgrade_now' | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [".extras-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.extras-container{width:90%;max-width:1000px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.extras-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.extras-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.extras-filters{display:flex;gap:8px;padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);overflow-x:auto;flex-shrink:0;scrollbar-width:thin}.extras-filters::-webkit-scrollbar{height:4px}.extras-filters::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.filter-btn{padding:8px 16px;font-size:13px;font-weight:500;color:#ffffffb3;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:20px;cursor:pointer;transition:all .2s ease;white-space:nowrap;flex-shrink:0}.filter-btn:hover{background:#ffffff1a;border-color:#fff3}.filter-btn.active{color:#fff;background:#0078d780;border-color:#0078d7b3}.extras-content{flex:1;overflow-y:auto;padding:24px}.extras-content::-webkit-scrollbar{width:8px}.extras-content::-webkit-scrollbar-track{background:#ffffff0d}.extras-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.extras-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:20px}.extra-card{display:flex;flex-direction:column;gap:12px}.extra-thumbnail{position:relative;aspect-ratio:3 / 4;background:#ffffff08;border:1px solid rgba(255,255,255,.1);border-radius:8px;overflow:hidden;cursor:pointer;transition:all .2s ease;padding:0}.extra-thumbnail:hover{border-color:#fff3;transform:translateY(-4px);box-shadow:0 4px 12px #0000004d}.extra-thumbnail.locked{cursor:not-allowed}.extra-thumbnail.locked:hover{transform:none;box-shadow:none}.thumbnail-image{width:100%;height:100%;object-fit:cover;display:block}.thumbnail-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:48px;background:linear-gradient(135deg,#0078d71a,#00b4781a)}.lock-overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;background:#000c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.lock-icon{font-size:32px}.lock-text{font-size:12px;font-weight:600;color:#ffffffe6;text-transform:uppercase;letter-spacing:.5px}.media-badge{position:absolute;top:8px;right:8px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:14px;background:#0009;border-radius:6px;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.extra-info{display:flex;flex-direction:column;gap:4px}.extra-title{font-size:14px;font-weight:600;color:#ffffffe6;margin:0;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.extra-description{font-size:12px;color:#fff9;margin:0;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.empty-state{padding:60px 24px;text-align:center}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.detail-overlay{position:fixed;inset:0;z-index:10;display:flex;align-items:center;justify-content:center;background:#000000e6;animation:fadeIn .2s ease}.detail-container{width:90%;max-width:800px;max-height:85vh;background:#1a1a1a;border-radius:12px;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}.detail-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.detail-title{font-size:18px;font-weight:600;color:#fff;margin:0}.detail-content{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:16px;align-items:center}.detail-image,.detail-video{max-width:100%;max-height:60vh;border-radius:8px}.detail-audio{width:100%;max-width:500px}.detail-description{font-size:14px;line-height:1.6;color:#fffc;white-space:pre-wrap;text-align:center;max-width:600px}.detail-footer{display:flex;justify-content:flex-end;padding:16px 20px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.action-btn{padding:10px 24px;font-size:14px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease}.action-btn:hover{background:#ffffff26;border-color:#ffffff4d}.paywall-notice{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 24px;background:#ffa5001a;border-top:1px solid rgba(255,165,0,.3);flex-shrink:0}.notice-text{display:flex;align-items:center;gap:8px;font-size:13px;color:#ffffffe6;margin:0}.notice-icon{font-size:16px}.upgrade-btn{padding:8px 20px;font-size:13px;font-weight:600;color:#fff;background:#ffa500cc;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease;white-space:nowrap}.upgrade-btn:hover{background:#ffa500e6;transform:translateY(-1px);box-shadow:0 2px 8px #ffa5004d}@media (max-width: 768px){.extras-container{width:95%}.extras-header{padding:16px 20px}.extras-title{font-size:18px}.extras-filters{padding:12px 20px}.extras-content{padding:20px}.extras-grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:16px}.extra-title{font-size:13px}.extra-description{font-size:11px}.detail-container{width:95%}.detail-header{padding:14px 18px}.detail-title{font-size:16px}.detail-content{padding:16px}.paywall-notice{flex-direction:column;align-items:flex-start;gap:12px}.upgrade-btn{width:100%}}.close-btn:focus-visible,.filter-btn:focus-visible,.extra-thumbnail:focus-visible,.action-btn:focus-visible,.upgrade-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.extras-overlay,.extras-container,.detail-overlay,.detail-container,.close-btn,.filter-btn,.extra-thumbnail,.action-btn,.upgrade-btn{animation:none;transition:none}}\n"] }]
|
|
15680
|
+
args: [{ selector: 'pw-extras-viewer', imports: [TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\n <div class=\"extras-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\n <div class=\"extras-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Extras Viewer\">\n <!-- Header -->\n <div class=\"extras-header\">\n <h2 class=\"extras-title\">{{ 'extras.title' | translate }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'extras.close' | translate\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Filters -->\n <div class=\"extras-filters\">\n <button\n class=\"filter-btn\"\n [class.active]=\"filterType === 'all'\"\n type=\"button\"\n (click)=\"setFilter('all')\">\n {{ 'extras.all' | translate }} ({{ extras.length }})\n </button>\n @if (getExtrasByType('cover') > 0) {\n <button\n class=\"filter-btn\"\n [class.active]=\"filterType === 'cover'\"\n type=\"button\"\n (click)=\"setFilter('cover')\">\n {{ getTypeName('cover') }} ({{ getExtrasByType('cover') }})\n </button>\n }\n @if (getExtrasByType('art') > 0) {\n <button\n class=\"filter-btn\"\n [class.active]=\"filterType === 'art'\"\n type=\"button\"\n (click)=\"setFilter('art')\">\n {{ getTypeName('art') }} ({{ getExtrasByType('art') }})\n </button>\n }\n @if (getExtrasByType('bts') > 0) {\n <button\n class=\"filter-btn\"\n [class.active]=\"filterType === 'bts'\"\n type=\"button\"\n (click)=\"setFilter('bts')\">\n {{ getTypeName('bts') }} ({{ getExtrasByType('bts') }})\n </button>\n }\n @if (getExtrasByType('interview') > 0) {\n <button\n class=\"filter-btn\"\n [class.active]=\"filterType === 'interview'\"\n type=\"button\"\n (click)=\"setFilter('interview')\">\n {{ getTypeName('interview') }} ({{ getExtrasByType('interview') }})\n </button>\n }\n @if (getExtrasByType('other') > 0) {\n <button\n class=\"filter-btn\"\n [class.active]=\"filterType === 'other'\"\n type=\"button\"\n (click)=\"setFilter('other')\">\n {{ getTypeName('other') }} ({{ getExtrasByType('other') }})\n </button>\n }\n </div>\n\n <!-- Gallery Grid -->\n <div class=\"extras-content\">\n @if (getFilteredExtras().length > 0) {\n <div class=\"extras-grid\" role=\"list\">\n @for (extra of getFilteredExtras(); track extra.id) {\n <div class=\"extra-card\" role=\"listitem\">\n <button\n class=\"extra-thumbnail\"\n [class.locked]=\"!isAccessible(extra)\"\n type=\"button\"\n (click)=\"openExtra(extra)\">\n \n @if (getThumbnailUrl(extra)) {\n <img\n [src]=\"getThumbnailUrl(extra)\"\n [alt]=\"getExtraTitle(extra)\"\n class=\"thumbnail-image\" />\n } @else {\n <div class=\"thumbnail-placeholder\">\n <pw-icon [name]=\"getMediaIcon(extra)\" />\n </div>\n }\n\n <!-- Lock overlay for gated content -->\n @if (!isAccessible(extra)) {\n <div class=\"lock-overlay\">\n <span class=\"lock-icon\"><pw-icon name=\"lucideLock\" /></span>\n <span class=\"lock-text\">{{ 'extras.locked_premium' | translate }}</span>\n </div>\n }\n\n <!-- Media type badge -->\n <div class=\"media-badge\">\n <pw-icon [name]=\"getMediaIcon(extra)\" />\n </div>\n </button>\n\n <div class=\"extra-info\">\n <h3 class=\"extra-title\">{{ getExtraTitle(extra) }}</h3>\n @if (getExtraDescription(extra)) {\n <p class=\"extra-description\">{{ getExtraDescription(extra) }}</p>\n }\n </div>\n </div>\n }\n </div>\n } @else {\n <div class=\"empty-state\">\n <p>{{ 'extras.no_extras' | translate }}</p>\n </div>\n }\n </div>\n\n <!-- Detail View -->\n @if (selectedExtra) {\n <div class=\"detail-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); closeDetail()\">\n <div class=\"detail-container\">\n <!-- Detail Header -->\n <div class=\"detail-header\">\n <h3 class=\"detail-title\">{{ getExtraTitle(selectedExtra) }}</h3>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'extras.close_detail' | translate\"\n (click)=\"closeDetail()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Detail Content -->\n <div class=\"detail-content\">\n @if ((selectedExtra.mediaType === 'image' || !selectedExtra.mediaType) && getAssetUrl(selectedExtra)) {\n <img\n [src]=\"getAssetUrl(selectedExtra)\"\n [alt]=\"getExtraTitle(selectedExtra)\"\n class=\"detail-image\" />\n }\n\n @if (selectedExtra.mediaType === 'video') {\n <video\n [src]=\"getAssetUrl(selectedExtra)\"\n controls\n class=\"detail-video\">\n {{ 'extras.video_not_supported' | translate }}\n </video>\n }\n\n @if (selectedExtra.mediaType === 'audio') {\n <audio\n [src]=\"getAssetUrl(selectedExtra)\"\n controls\n class=\"detail-audio\">\n {{ 'extras.audio_not_supported' | translate }}\n </audio>\n }\n\n @if (selectedExtra.mediaType === 'document' && getAssetUrl(selectedExtra)) {\n <a\n class=\"action-btn detail-document-link\"\n [href]=\"getAssetUrl(selectedExtra)\"\n target=\"_blank\"\n rel=\"noopener noreferrer\">\n {{ 'extras.open_document' | translate }}\n </a>\n }\n\n @if (getExtraDescription(selectedExtra)) {\n <div class=\"detail-description\">\n {{ getExtraDescription(selectedExtra) }}\n </div>\n }\n </div>\n\n <!-- Detail Footer -->\n <div class=\"detail-footer\">\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"closeDetail()\">\n {{ 'extras.close_button' | translate }}\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Paywall Notice (for gated content without access) -->\n @if (!hasPremiumAccess && hasGatedExtras()) {\n <div class=\"paywall-notice\">\n <p class=\"notice-text\">\n <span class=\"notice-icon\"><pw-icon name=\"lucideLock\" /></span>\n {{ 'extras.premium_notice' | translate }}\n </p>\n <button\n class=\"upgrade-btn\"\n type=\"button\"\n (click)=\"onPurchase()\">\n {{ 'extras.upgrade_now' | translate }}\n </button>\n </div>\n }\n </div>\n </div>\n}\n", styles: [".extras-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.extras-container{width:90%;max-width:1000px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.extras-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.extras-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.extras-filters{display:flex;gap:8px;padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);overflow-x:auto;flex-shrink:0;scrollbar-width:thin}.extras-filters::-webkit-scrollbar{height:4px}.extras-filters::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.filter-btn{padding:8px 16px;font-size:13px;font-weight:500;color:#ffffffb3;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:20px;cursor:pointer;transition:all .2s ease;white-space:nowrap;flex-shrink:0}.filter-btn:hover{background:#ffffff1a;border-color:#fff3}.filter-btn.active{color:#fff;background:#0078d780;border-color:#0078d7b3}.extras-content{flex:1;overflow-y:auto;padding:24px}.extras-content::-webkit-scrollbar{width:8px}.extras-content::-webkit-scrollbar-track{background:#ffffff0d}.extras-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.extras-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:20px}.extra-card{display:flex;flex-direction:column;gap:12px}.extra-thumbnail{position:relative;aspect-ratio:3 / 4;background:#ffffff08;border:1px solid rgba(255,255,255,.1);border-radius:8px;overflow:hidden;cursor:pointer;transition:all .2s ease;padding:0}.extra-thumbnail:hover{border-color:#fff3;transform:translateY(-4px);box-shadow:0 4px 12px #0000004d}.extra-thumbnail.locked{cursor:not-allowed}.extra-thumbnail.locked:hover{transform:none;box-shadow:none}.thumbnail-image{width:100%;height:100%;object-fit:cover;display:block}.thumbnail-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:48px;background:linear-gradient(135deg,#0078d71a,#00b4781a)}.lock-overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;background:#000c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.lock-icon{font-size:32px}.lock-text{font-size:12px;font-weight:600;color:#ffffffe6;text-transform:uppercase;letter-spacing:.5px}.media-badge{position:absolute;top:8px;right:8px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:14px;background:#0009;border-radius:6px;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.extra-info{display:flex;flex-direction:column;gap:4px}.extra-title{font-size:14px;font-weight:600;color:#ffffffe6;margin:0;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.extra-description{font-size:12px;color:#fff9;margin:0;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.empty-state{padding:60px 24px;text-align:center}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.detail-overlay{position:fixed;inset:0;z-index:10;display:flex;align-items:center;justify-content:center;background:#000000e6;animation:fadeIn .2s ease}.detail-container{width:90%;max-width:800px;max-height:85vh;background:#1a1a1a;border-radius:12px;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}.detail-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.detail-title{font-size:18px;font-weight:600;color:#fff;margin:0}.detail-content{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:16px;align-items:center}.detail-image,.detail-video{max-width:100%;max-height:60vh;border-radius:8px}.detail-audio{width:100%;max-width:500px}.detail-description{font-size:14px;line-height:1.6;color:#fffc;white-space:pre-wrap;text-align:center;max-width:600px}.detail-footer{display:flex;justify-content:flex-end;padding:16px 20px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.action-btn{padding:10px 24px;font-size:14px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease}.action-btn:hover{background:#ffffff26;border-color:#ffffff4d}.paywall-notice{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 24px;background:#ffa5001a;border-top:1px solid rgba(255,165,0,.3);flex-shrink:0}.notice-text{display:flex;align-items:center;gap:8px;font-size:13px;color:#ffffffe6;margin:0}.notice-icon{font-size:16px}.upgrade-btn{padding:8px 20px;font-size:13px;font-weight:600;color:#fff;background:#ffa500cc;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease;white-space:nowrap}.upgrade-btn:hover{background:#ffa500e6;transform:translateY(-1px);box-shadow:0 2px 8px #ffa5004d}@media (max-width: 768px){.extras-container{width:95%}.extras-header{padding:16px 20px}.extras-title{font-size:18px}.extras-filters{padding:12px 20px}.extras-content{padding:20px}.extras-grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:16px}.extra-title{font-size:13px}.extra-description{font-size:11px}.detail-container{width:95%}.detail-header{padding:14px 18px}.detail-title{font-size:16px}.detail-content{padding:16px}.paywall-notice{flex-direction:column;align-items:flex-start;gap:12px}.upgrade-btn{width:100%}}.close-btn:focus-visible,.filter-btn:focus-visible,.extra-thumbnail:focus-visible,.action-btn:focus-visible,.upgrade-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.extras-overlay,.extras-container,.detail-overlay,.detail-container,.close-btn,.filter-btn,.extra-thumbnail,.action-btn,.upgrade-btn{animation:none;transition:none}}\n"] }]
|
|
15681
15681
|
}], propDecorators: { extras: [{
|
|
15682
15682
|
type: Input
|
|
15683
15683
|
}], initialExtraId: [{
|
|
@@ -15741,11 +15741,11 @@ class ActionModalComponent {
|
|
|
15741
15741
|
}
|
|
15742
15742
|
}
|
|
15743
15743
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ActionModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
15744
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: ActionModalComponent, isStandalone: true, selector: "pw-action-modal", inputs: { visible: "visible", locale: "locale", title: "title", content: "content" }, outputs: { close: "close" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (visible) {\
|
|
15744
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: ActionModalComponent, isStandalone: true, selector: "pw-action-modal", inputs: { visible: "visible", locale: "locale", title: "title", content: "content" }, outputs: { close: "close" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (visible) {\n <div class=\"action-modal-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); close.emit()\">\n <div class=\"action-modal-container\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"resolved(title)\">\n <div class=\"action-modal-header\">\n <h2 class=\"action-modal-title\">{{ resolved(title) }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n aria-label=\"Close\"\n (click)=\"close.emit()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n <!-- Plain text only: manifest content is untrusted, never innerHTML -->\n <div class=\"action-modal-content\">{{ resolved(content) }}</div>\n </div>\n </div>\n}\n", styles: [".action-modal-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.action-modal-container{width:90%;max-width:480px;max-height:70vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.action-modal-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.action-modal-title{font-size:18px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:background .15s ease,color .15s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.action-modal-content{padding:20px 24px;color:#ffffffd9;font-size:15px;line-height:1.6;overflow-y:auto;white-space:pre-line}@media (prefers-reduced-motion: reduce){.action-modal-overlay,.action-modal-container{animation:none}}\n"], dependencies: [{ kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
15745
15745
|
}
|
|
15746
15746
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ActionModalComponent, decorators: [{
|
|
15747
15747
|
type: Component,
|
|
15748
|
-
args: [{ selector: 'pw-action-modal', imports: [PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\
|
|
15748
|
+
args: [{ selector: 'pw-action-modal', imports: [PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\n <div class=\"action-modal-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); close.emit()\">\n <div class=\"action-modal-container\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"resolved(title)\">\n <div class=\"action-modal-header\">\n <h2 class=\"action-modal-title\">{{ resolved(title) }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n aria-label=\"Close\"\n (click)=\"close.emit()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n <!-- Plain text only: manifest content is untrusted, never innerHTML -->\n <div class=\"action-modal-content\">{{ resolved(content) }}</div>\n </div>\n </div>\n}\n", styles: [".action-modal-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.action-modal-container{width:90%;max-width:480px;max-height:70vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.action-modal-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.action-modal-title{font-size:18px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:background .15s ease,color .15s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.action-modal-content{padding:20px 24px;color:#ffffffd9;font-size:15px;line-height:1.6;overflow-y:auto;white-space:pre-line}@media (prefers-reduced-motion: reduce){.action-modal-overlay,.action-modal-container{animation:none}}\n"] }]
|
|
15749
15749
|
}], propDecorators: { visible: [{
|
|
15750
15750
|
type: Input
|
|
15751
15751
|
}], locale: [{
|
|
@@ -15936,11 +15936,11 @@ class AgeGateComponent {
|
|
|
15936
15936
|
return years;
|
|
15937
15937
|
}
|
|
15938
15938
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AgeGateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
15939
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: AgeGateComponent, isStandalone: true, selector: "pw-age-gate", inputs: { visible: "visible", minimumAge: "minimumAge", locale: "locale", warningMessage: "warningMessage", allowDismiss: "allowDismiss" }, outputs: { verify: "verify", close: "close" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "<!-- Age Gate Overlay -->\
|
|
15939
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: AgeGateComponent, isStandalone: true, selector: "pw-age-gate", inputs: { visible: "visible", minimumAge: "minimumAge", locale: "locale", warningMessage: "warningMessage", allowDismiss: "allowDismiss" }, outputs: { verify: "verify", close: "close" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "<!-- Age Gate Overlay -->\n<div class=\"age-gate-overlay\" [class.visible]=\"visible\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); onDismiss()\">\n <div class=\"age-gate-modal\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Age Verification\">\n <!-- Header -->\n <div class=\"age-gate-header\">\n <div class=\"warning-icon\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/>\n </svg>\n </div>\n <h2 class=\"age-gate-title\">Age Verification Required</h2>\n @if (allowDismiss) {\n <button\n class=\"close-btn\"\n type=\"button\"\n aria-label=\"Close\"\n (click)=\"onDismiss()\">\n <pw-icon name=\"lucideX\" />\n </button>\n }\n </div>\n\n <!-- Content -->\n <div class=\"age-gate-content\">\n <!-- Warning Message -->\n <p class=\"warning-message\">{{ getWarningMessage() }}</p>\n\n <!-- Birth Date Form -->\n <form class=\"birth-date-form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"form-label\">Please enter your birth date:</div>\n\n <div class=\"date-inputs\">\n <!-- Month -->\n <div class=\"input-group\">\n <label for=\"birth-month\" class=\"input-label\">Month</label>\n <select\n id=\"birth-month\"\n class=\"date-input\"\n [(ngModel)]=\"birthMonth\"\n name=\"birthMonth\"\n required>\n <option value=\"\">--</option>\n @for (month of getMonthOptions(); track month.value) {\n <option [value]=\"month.value\">{{ month.label }}</option>\n }\n </select>\n </div>\n\n <!-- Day -->\n <div class=\"input-group\">\n <label for=\"birth-day\" class=\"input-label\">Day</label>\n <select\n id=\"birth-day\"\n class=\"date-input\"\n [(ngModel)]=\"birthDay\"\n name=\"birthDay\"\n required>\n <option value=\"\">--</option>\n @for (day of getDayOptions(); track day) {\n <option [value]=\"day\">{{ day }}</option>\n }\n </select>\n </div>\n\n <!-- Year -->\n <div class=\"input-group\">\n <label for=\"birth-year\" class=\"input-label\">Year</label>\n <select\n id=\"birth-year\"\n class=\"date-input\"\n [(ngModel)]=\"birthYear\"\n name=\"birthYear\"\n required>\n <option value=\"\">----</option>\n @for (year of getYearOptions(); track year) {\n <option [value]=\"year\">{{ year }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Error Message -->\n @if (errorMessage) {\n <div class=\"error-message\" role=\"alert\">\n {{ errorMessage }}\n </div>\n }\n\n <!-- Submit Button -->\n <button\n type=\"submit\"\n class=\"verify-btn\">\n Verify Age\n </button>\n </form>\n\n <!-- Privacy Notice -->\n <div class=\"privacy-notice\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z\"/>\n </svg>\n <span>Your information is private and will not be stored.</span>\n </div>\n </div>\n </div>\n</div>\n", styles: [".age-gate-overlay{position:fixed;inset:0;background:#000000d9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:10000;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;padding:20px}.age-gate-overlay.visible{opacity:1;visibility:visible}.age-gate-modal{background:#1a1a1a;border:2px solid #ffa500;border-radius:12px;max-width:500px;width:100%;max-height:90vh;overflow-y:auto;box-shadow:0 20px 60px #ffa5004d;transform:scale(.9);transition:transform .3s ease}.age-gate-overlay.visible .age-gate-modal{transform:scale(1)}.age-gate-header{position:relative;padding:32px 24px 24px;text-align:center;border-bottom:1px solid rgba(255,165,0,.2)}.warning-icon{width:64px;height:64px;margin:0 auto 16px;background:#ffa50026;border-radius:50%;display:flex;align-items:center;justify-content:center}.warning-icon svg{width:36px;height:36px;color:orange}.age-gate-title{font-size:24px;font-weight:700;color:orange;margin:0}.close-btn{position:absolute;top:16px;right:16px;background:#ffffff1a;border:none;color:orange;font-size:24px;width:36px;height:36px;border-radius:50%;cursor:pointer;transition:background .2s ease;display:flex;align-items:center;justify-content:center;padding:0}.close-btn:hover{background:#fff3}.age-gate-content{padding:32px 24px}.warning-message{font-size:16px;line-height:1.6;color:#ffffffe6;text-align:center;margin:0 0 32px}.birth-date-form{margin-bottom:24px}.form-label{font-size:14px;font-weight:600;color:#fffc;text-align:center;margin-bottom:16px}.date-inputs{display:grid;grid-template-columns:2fr 1fr 1.5fr;gap:12px;margin-bottom:16px}.input-group{display:flex;flex-direction:column}.input-label{font-size:12px;font-weight:600;color:#fff9;margin-bottom:8px;text-transform:uppercase;letter-spacing:.5px}.date-input{background:#ffffff1a;border:1px solid rgba(255,165,0,.3);color:#fff;font-size:16px;padding:12px;border-radius:8px;transition:border-color .2s ease,background .2s ease}.date-input:focus{outline:none;border-color:orange;background:#ffffff26}.date-input option{background:#1a1a1a;color:#fff}.error-message{background:#dc354526;border:1px solid #dc3545;border-radius:8px;color:#ff6b7a;font-size:14px;padding:12px;margin-bottom:16px;text-align:center}.verify-btn{background:linear-gradient(135deg,orange,#ff8c00);border:none;color:#fff;font-size:16px;font-weight:700;padding:16px 32px;border-radius:8px;cursor:pointer;transition:transform .2s ease,box-shadow .2s ease;width:100%}.verify-btn:hover{transform:translateY(-2px);box-shadow:0 8px 20px #ffa5004d}.verify-btn:active{transform:translateY(0)}.verify-btn:disabled{opacity:.5;cursor:not-allowed}.privacy-notice{display:flex;align-items:center;justify-content:center;gap:8px;padding-top:24px;border-top:1px solid rgba(255,255,255,.1)}.privacy-notice svg{width:16px;height:16px;color:#ffffff80;flex-shrink:0}.privacy-notice span{font-size:12px;color:#ffffff80}@media (max-width: 640px){.age-gate-modal{max-width:100%;border-radius:8px}.age-gate-header{padding:24px 20px 20px}.age-gate-title{font-size:20px}.age-gate-content{padding:24px 20px}.date-inputs{grid-template-columns:1fr;gap:16px}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
15940
15940
|
}
|
|
15941
15941
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AgeGateComponent, decorators: [{
|
|
15942
15942
|
type: Component,
|
|
15943
|
-
args: [{ selector: 'pw-age-gate', imports: [FormsModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Age Gate Overlay -->\
|
|
15943
|
+
args: [{ selector: 'pw-age-gate', imports: [FormsModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Age Gate Overlay -->\n<div class=\"age-gate-overlay\" [class.visible]=\"visible\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown.escape)=\"$event.stopPropagation(); onDismiss()\">\n <div class=\"age-gate-modal\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Age Verification\">\n <!-- Header -->\n <div class=\"age-gate-header\">\n <div class=\"warning-icon\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/>\n </svg>\n </div>\n <h2 class=\"age-gate-title\">Age Verification Required</h2>\n @if (allowDismiss) {\n <button\n class=\"close-btn\"\n type=\"button\"\n aria-label=\"Close\"\n (click)=\"onDismiss()\">\n <pw-icon name=\"lucideX\" />\n </button>\n }\n </div>\n\n <!-- Content -->\n <div class=\"age-gate-content\">\n <!-- Warning Message -->\n <p class=\"warning-message\">{{ getWarningMessage() }}</p>\n\n <!-- Birth Date Form -->\n <form class=\"birth-date-form\" (ngSubmit)=\"onSubmit()\">\n <div class=\"form-label\">Please enter your birth date:</div>\n\n <div class=\"date-inputs\">\n <!-- Month -->\n <div class=\"input-group\">\n <label for=\"birth-month\" class=\"input-label\">Month</label>\n <select\n id=\"birth-month\"\n class=\"date-input\"\n [(ngModel)]=\"birthMonth\"\n name=\"birthMonth\"\n required>\n <option value=\"\">--</option>\n @for (month of getMonthOptions(); track month.value) {\n <option [value]=\"month.value\">{{ month.label }}</option>\n }\n </select>\n </div>\n\n <!-- Day -->\n <div class=\"input-group\">\n <label for=\"birth-day\" class=\"input-label\">Day</label>\n <select\n id=\"birth-day\"\n class=\"date-input\"\n [(ngModel)]=\"birthDay\"\n name=\"birthDay\"\n required>\n <option value=\"\">--</option>\n @for (day of getDayOptions(); track day) {\n <option [value]=\"day\">{{ day }}</option>\n }\n </select>\n </div>\n\n <!-- Year -->\n <div class=\"input-group\">\n <label for=\"birth-year\" class=\"input-label\">Year</label>\n <select\n id=\"birth-year\"\n class=\"date-input\"\n [(ngModel)]=\"birthYear\"\n name=\"birthYear\"\n required>\n <option value=\"\">----</option>\n @for (year of getYearOptions(); track year) {\n <option [value]=\"year\">{{ year }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Error Message -->\n @if (errorMessage) {\n <div class=\"error-message\" role=\"alert\">\n {{ errorMessage }}\n </div>\n }\n\n <!-- Submit Button -->\n <button\n type=\"submit\"\n class=\"verify-btn\">\n Verify Age\n </button>\n </form>\n\n <!-- Privacy Notice -->\n <div class=\"privacy-notice\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z\"/>\n </svg>\n <span>Your information is private and will not be stored.</span>\n </div>\n </div>\n </div>\n</div>\n", styles: [".age-gate-overlay{position:fixed;inset:0;background:#000000d9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:10000;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;padding:20px}.age-gate-overlay.visible{opacity:1;visibility:visible}.age-gate-modal{background:#1a1a1a;border:2px solid #ffa500;border-radius:12px;max-width:500px;width:100%;max-height:90vh;overflow-y:auto;box-shadow:0 20px 60px #ffa5004d;transform:scale(.9);transition:transform .3s ease}.age-gate-overlay.visible .age-gate-modal{transform:scale(1)}.age-gate-header{position:relative;padding:32px 24px 24px;text-align:center;border-bottom:1px solid rgba(255,165,0,.2)}.warning-icon{width:64px;height:64px;margin:0 auto 16px;background:#ffa50026;border-radius:50%;display:flex;align-items:center;justify-content:center}.warning-icon svg{width:36px;height:36px;color:orange}.age-gate-title{font-size:24px;font-weight:700;color:orange;margin:0}.close-btn{position:absolute;top:16px;right:16px;background:#ffffff1a;border:none;color:orange;font-size:24px;width:36px;height:36px;border-radius:50%;cursor:pointer;transition:background .2s ease;display:flex;align-items:center;justify-content:center;padding:0}.close-btn:hover{background:#fff3}.age-gate-content{padding:32px 24px}.warning-message{font-size:16px;line-height:1.6;color:#ffffffe6;text-align:center;margin:0 0 32px}.birth-date-form{margin-bottom:24px}.form-label{font-size:14px;font-weight:600;color:#fffc;text-align:center;margin-bottom:16px}.date-inputs{display:grid;grid-template-columns:2fr 1fr 1.5fr;gap:12px;margin-bottom:16px}.input-group{display:flex;flex-direction:column}.input-label{font-size:12px;font-weight:600;color:#fff9;margin-bottom:8px;text-transform:uppercase;letter-spacing:.5px}.date-input{background:#ffffff1a;border:1px solid rgba(255,165,0,.3);color:#fff;font-size:16px;padding:12px;border-radius:8px;transition:border-color .2s ease,background .2s ease}.date-input:focus{outline:none;border-color:orange;background:#ffffff26}.date-input option{background:#1a1a1a;color:#fff}.error-message{background:#dc354526;border:1px solid #dc3545;border-radius:8px;color:#ff6b7a;font-size:14px;padding:12px;margin-bottom:16px;text-align:center}.verify-btn{background:linear-gradient(135deg,orange,#ff8c00);border:none;color:#fff;font-size:16px;font-weight:700;padding:16px 32px;border-radius:8px;cursor:pointer;transition:transform .2s ease,box-shadow .2s ease;width:100%}.verify-btn:hover{transform:translateY(-2px);box-shadow:0 8px 20px #ffa5004d}.verify-btn:active{transform:translateY(0)}.verify-btn:disabled{opacity:.5;cursor:not-allowed}.privacy-notice{display:flex;align-items:center;justify-content:center;gap:8px;padding-top:24px;border-top:1px solid rgba(255,255,255,.1)}.privacy-notice svg{width:16px;height:16px;color:#ffffff80;flex-shrink:0}.privacy-notice span{font-size:12px;color:#ffffff80}@media (max-width: 640px){.age-gate-modal{max-width:100%;border-radius:8px}.age-gate-header{padding:24px 20px 20px}.age-gate-title{font-size:20px}.age-gate-content{padding:24px 20px}.date-inputs{grid-template-columns:1fr;gap:16px}}\n"] }]
|
|
15944
15944
|
}], propDecorators: { visible: [{
|
|
15945
15945
|
type: Input
|
|
15946
15946
|
}], minimumAge: [{
|
|
@@ -16314,11 +16314,11 @@ class ShareModalComponent {
|
|
|
16314
16314
|
}
|
|
16315
16315
|
}
|
|
16316
16316
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ShareModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
16317
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: ShareModalComponent, isStandalone: true, selector: "pw-share-modal", inputs: { shareUrl: "shareUrl", shareTitle: "shareTitle", shareDescription: "shareDescription", visible: "visible", shareable: "shareable" }, outputs: { close: "close", share: "share" }, host: { listeners: { "window:keydown": "handleKeyboard($event)" } }, ngImport: i0, template: "@if (visible) {\r\n <div class=\"share-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\r\n <div class=\"share-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Share\">\r\n <!-- Header -->\r\n <div class=\"share-header\">\r\n <h2 class=\"share-title\">{{ 'share.title' | translate }}</h2>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'share.close' | translate\"\r\n (click)=\"onClose()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Content -->\r\n <div class=\"share-content\">\r\n @if (!shareable) {\r\n <!-- Not shareable message -->\r\n <div class=\"not-shareable\">\r\n <span class=\"warning-icon\"><pw-icon name=\"lucideTriangleAlert\" /></span>\r\n <p>{{ 'share.not_shareable' | translate }}</p>\r\n </div>\r\n } @else {\r\n <!-- Copy Link -->\r\n <div class=\"share-section\">\r\n <h3 class=\"section-title\">{{ 'share.copy_link' | translate }}</h3>\r\n <div class=\"link-container\">\r\n <input\r\n type=\"text\"\r\n class=\"link-input\"\r\n [value]=\"shareUrl\"\r\n readonly\r\n [attr.aria-label]=\"'share.share_url' | translate\" />\r\n <button\r\n class=\"copy-btn\"\r\n [class.success]=\"copySuccess\"\r\n type=\"button\"\r\n (click)=\"copyLink()\">\r\n <pw-icon [name]=\"copySuccess ? 'lucideCheck' : 'lucideClipboard'\" />\r\n {{ copySuccess ? ('share.copied' | translate) : ('share.copy' | translate) }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Native Share (if supported) -->\r\n @if (isNativeShareSupported) {\r\n <div class=\"share-section\">\r\n <button\r\n class=\"native-share-btn\"\r\n type=\"button\"\r\n (click)=\"useNativeShare()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideShare2\" /></span>\r\n <span class=\"btn-label\">{{ 'share.share_via' | translate }}</span>\r\n </button>\r\n </div>\r\n }\r\n\r\n <!-- Social Share Buttons -->\r\n <div class=\"share-section\">\r\n <h3 class=\"section-title\">{{ 'share.social_media' | translate }}</h3>\r\n <div class=\"social-buttons\">\r\n <button\r\n class=\"social-btn twitter\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'Share on ' + ('share.twitter' | translate)\"\r\n (click)=\"shareOnTwitter()\">\r\n <span class=\"social-icon\">\uD835\uDD4F</span>\r\n <span class=\"social-label\">{{ 'share.twitter' | translate }}</span>\r\n </button>\r\n\r\n <button\r\n class=\"social-btn facebook\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'Share on ' + ('share.facebook' | translate)\"\r\n (click)=\"shareOnFacebook()\">\r\n <span class=\"social-icon\">f</span>\r\n <span class=\"social-label\">{{ 'share.facebook' | translate }}</span>\r\n </button>\r\n\r\n <button\r\n class=\"social-btn reddit\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'Share on ' + ('share.reddit' | translate)\"\r\n (click)=\"shareOnReddit()\">\r\n <span class=\"social-icon\">\u25B2</span>\r\n <span class=\"social-label\">{{ 'share.reddit' | translate }}</span>\r\n </button>\r\n\r\n <button\r\n class=\"social-btn email\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'Share via ' + ('share.email' | translate)\"\r\n (click)=\"shareViaEmail()\">\r\n <span class=\"social-icon\"><pw-icon name=\"lucideMail\" /></span>\r\n <span class=\"social-label\">{{ 'share.email' | translate }}</span>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- QR Code -->\r\n <div class=\"share-section\">\r\n <button\r\n class=\"qr-toggle-btn\"\r\n type=\"button\"\r\n (click)=\"toggleQrCode()\">\r\n <span class=\"btn-icon\"><pw-icon [name]=\"showQrCode ? 'lucideChevronDown' : 'lucideChevronRight'\" /></span>\r\n <span class=\"btn-label\">{{ showQrCode ? ('share.hide_qr' | translate) : ('share.show_qr' | translate) }}</span>\r\n </button>\r\n\r\n @if (showQrCode) {\r\n <div class=\"qr-code-container\">\r\n @if (qrCodeDataUrl) {\r\n <img\r\n [src]=\"qrCodeDataUrl\"\r\n [alt]=\"'share.qr_code' | translate\"\r\n class=\"qr-code-image\" />\r\n <p class=\"qr-hint\">{{ 'share.scan_mobile' | translate }}</p>\r\n } @else {\r\n <div class=\"qr-loading\">{{ 'share.generating_qr' | translate }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Share Info -->\r\n <div class=\"share-info\">\r\n <p class=\"info-text\">\r\n <pw-icon name=\"lucideInfo\" /> {{ 'share.share_info' | translate }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [".share-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.share-container{width:90%;max-width:500px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.share-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.share-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.share-content{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:24px}.share-content::-webkit-scrollbar{width:8px}.share-content::-webkit-scrollbar-track{background:#ffffff0d}.share-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.not-shareable{display:flex;flex-direction:column;align-items:center;gap:16px;padding:40px 20px;text-align:center}.warning-icon{font-size:48px}.not-shareable p{font-size:14px;color:#ffffffb3;margin:0}.share-section{display:flex;flex-direction:column;gap:12px}.section-title{font-size:14px;font-weight:600;color:#ffffffe6;margin:0;text-transform:uppercase;letter-spacing:.5px}.link-container{display:flex;gap:8px}.link-input{flex:1;padding:10px 14px;font-size:13px;font-family:monospace;color:#fffc;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:6px;outline:none}.link-input:focus{border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.copy-btn{padding:10px 16px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease;white-space:nowrap}.copy-btn:hover{background:#ffffff26;border-color:#ffffff4d}.copy-btn.success{background:#00b4784d;border-color:#00b47880;color:#fff}.native-share-btn{display:flex;align-items:center;justify-content:center;gap:8px;padding:12px 20px;font-size:14px;font-weight:500;color:#ffffffe6;background:#0078d74d;border:1px solid rgba(0,120,215,.5);border-radius:8px;cursor:pointer;transition:all .2s ease}.native-share-btn:hover{background:#0078d766;border-color:#0078d799;transform:translateY(-1px)}.social-buttons{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}.social-btn{display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px 12px;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:8px;cursor:pointer;transition:all .2s ease}.social-btn:hover{background:#ffffff1a;border-color:#fff3;transform:translateY(-2px)}.social-btn:active{transform:translateY(0)}.social-icon{font-size:24px;font-weight:700;line-height:1}.social-label{font-size:12px;font-weight:500;color:#fffc}.social-btn.twitter{background:#1d9bf01a;border-color:#1d9bf04d}.social-btn.twitter:hover{background:#1d9bf033;border-color:#1d9bf080}.social-btn.facebook{background:#1877f21a;border-color:#1877f24d}.social-btn.facebook:hover{background:#1877f233;border-color:#1877f280}.social-btn.reddit{background:#ff45001a;border-color:#ff45004d}.social-btn.reddit:hover{background:#ff450033;border-color:#ff450080}.social-btn.email{background:#8080801a;border-color:#8080804d}.social-btn.email:hover{background:#80808033;border-color:#80808080}.qr-toggle-btn{display:flex;align-items:center;gap:8px;padding:10px 14px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:6px;cursor:pointer;transition:all .2s ease}.qr-toggle-btn:hover{background:#ffffff1a;border-color:#fff3}.btn-icon{font-size:14px;line-height:1}.btn-label{line-height:1}.qr-code-container{display:flex;flex-direction:column;align-items:center;gap:12px;padding:20px;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:8px;animation:fadeIn .2s ease}.qr-code-image{width:200px;height:200px;border-radius:8px;background:#fff;padding:8px}.qr-hint{font-size:12px;color:#fff9;margin:0}.qr-loading{padding:40px;font-size:13px;color:#ffffff80}.share-info{padding:12px 16px;background:#0078d71a;border:1px solid rgba(0,120,215,.2);border-radius:6px}.info-text{font-size:12px;line-height:1.5;color:#ffffffb3;margin:0}@media (max-width: 480px){.share-container{width:95%}.share-header{padding:16px 20px}.share-title{font-size:18px}.share-content{padding:20px;gap:20px}.social-buttons{grid-template-columns:1fr}.qr-code-image{width:180px;height:180px}}.close-btn:focus-visible,.copy-btn:focus-visible,.native-share-btn:focus-visible,.social-btn:focus-visible,.qr-toggle-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.share-overlay,.share-container,.qr-code-container,.close-btn,.copy-btn,.social-btn,.qr-toggle-btn{animation:none;transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
16317
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: ShareModalComponent, isStandalone: true, selector: "pw-share-modal", inputs: { shareUrl: "shareUrl", shareTitle: "shareTitle", shareDescription: "shareDescription", visible: "visible", shareable: "shareable" }, outputs: { close: "close", share: "share" }, host: { listeners: { "window:keydown": "handleKeyboard($event)" } }, ngImport: i0, template: "@if (visible) {\n <div class=\"share-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\n <div class=\"share-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Share\">\n <!-- Header -->\n <div class=\"share-header\">\n <h2 class=\"share-title\">{{ 'share.title' | translate }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'share.close' | translate\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"share-content\">\n @if (!shareable) {\n <!-- Not shareable message -->\n <div class=\"not-shareable\">\n <span class=\"warning-icon\"><pw-icon name=\"lucideTriangleAlert\" /></span>\n <p>{{ 'share.not_shareable' | translate }}</p>\n </div>\n } @else {\n <!-- Copy Link -->\n <div class=\"share-section\">\n <h3 class=\"section-title\">{{ 'share.copy_link' | translate }}</h3>\n <div class=\"link-container\">\n <input\n type=\"text\"\n class=\"link-input\"\n [value]=\"shareUrl\"\n readonly\n [attr.aria-label]=\"'share.share_url' | translate\" />\n <button\n class=\"copy-btn\"\n [class.success]=\"copySuccess\"\n type=\"button\"\n (click)=\"copyLink()\">\n <pw-icon [name]=\"copySuccess ? 'lucideCheck' : 'lucideClipboard'\" />\n {{ copySuccess ? ('share.copied' | translate) : ('share.copy' | translate) }}\n </button>\n </div>\n </div>\n\n <!-- Native Share (if supported) -->\n @if (isNativeShareSupported) {\n <div class=\"share-section\">\n <button\n class=\"native-share-btn\"\n type=\"button\"\n (click)=\"useNativeShare()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideShare2\" /></span>\n <span class=\"btn-label\">{{ 'share.share_via' | translate }}</span>\n </button>\n </div>\n }\n\n <!-- Social Share Buttons -->\n <div class=\"share-section\">\n <h3 class=\"section-title\">{{ 'share.social_media' | translate }}</h3>\n <div class=\"social-buttons\">\n <button\n class=\"social-btn twitter\"\n type=\"button\"\n [attr.aria-label]=\"'Share on ' + ('share.twitter' | translate)\"\n (click)=\"shareOnTwitter()\">\n <span class=\"social-icon\">\uD835\uDD4F</span>\n <span class=\"social-label\">{{ 'share.twitter' | translate }}</span>\n </button>\n\n <button\n class=\"social-btn facebook\"\n type=\"button\"\n [attr.aria-label]=\"'Share on ' + ('share.facebook' | translate)\"\n (click)=\"shareOnFacebook()\">\n <span class=\"social-icon\">f</span>\n <span class=\"social-label\">{{ 'share.facebook' | translate }}</span>\n </button>\n\n <button\n class=\"social-btn reddit\"\n type=\"button\"\n [attr.aria-label]=\"'Share on ' + ('share.reddit' | translate)\"\n (click)=\"shareOnReddit()\">\n <span class=\"social-icon\">\u25B2</span>\n <span class=\"social-label\">{{ 'share.reddit' | translate }}</span>\n </button>\n\n <button\n class=\"social-btn email\"\n type=\"button\"\n [attr.aria-label]=\"'Share via ' + ('share.email' | translate)\"\n (click)=\"shareViaEmail()\">\n <span class=\"social-icon\"><pw-icon name=\"lucideMail\" /></span>\n <span class=\"social-label\">{{ 'share.email' | translate }}</span>\n </button>\n </div>\n </div>\n\n <!-- QR Code -->\n <div class=\"share-section\">\n <button\n class=\"qr-toggle-btn\"\n type=\"button\"\n (click)=\"toggleQrCode()\">\n <span class=\"btn-icon\"><pw-icon [name]=\"showQrCode ? 'lucideChevronDown' : 'lucideChevronRight'\" /></span>\n <span class=\"btn-label\">{{ showQrCode ? ('share.hide_qr' | translate) : ('share.show_qr' | translate) }}</span>\n </button>\n\n @if (showQrCode) {\n <div class=\"qr-code-container\">\n @if (qrCodeDataUrl) {\n <img\n [src]=\"qrCodeDataUrl\"\n [alt]=\"'share.qr_code' | translate\"\n class=\"qr-code-image\" />\n <p class=\"qr-hint\">{{ 'share.scan_mobile' | translate }}</p>\n } @else {\n <div class=\"qr-loading\">{{ 'share.generating_qr' | translate }}</div>\n }\n </div>\n }\n </div>\n\n <!-- Share Info -->\n <div class=\"share-info\">\n <p class=\"info-text\">\n <pw-icon name=\"lucideInfo\" /> {{ 'share.share_info' | translate }}\n </p>\n </div>\n }\n </div>\n </div>\n </div>\n}\n", styles: [".share-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.share-container{width:90%;max-width:500px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.share-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.share-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.share-content{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:24px}.share-content::-webkit-scrollbar{width:8px}.share-content::-webkit-scrollbar-track{background:#ffffff0d}.share-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.not-shareable{display:flex;flex-direction:column;align-items:center;gap:16px;padding:40px 20px;text-align:center}.warning-icon{font-size:48px}.not-shareable p{font-size:14px;color:#ffffffb3;margin:0}.share-section{display:flex;flex-direction:column;gap:12px}.section-title{font-size:14px;font-weight:600;color:#ffffffe6;margin:0;text-transform:uppercase;letter-spacing:.5px}.link-container{display:flex;gap:8px}.link-input{flex:1;padding:10px 14px;font-size:13px;font-family:monospace;color:#fffc;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:6px;outline:none}.link-input:focus{border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.copy-btn{padding:10px 16px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease;white-space:nowrap}.copy-btn:hover{background:#ffffff26;border-color:#ffffff4d}.copy-btn.success{background:#00b4784d;border-color:#00b47880;color:#fff}.native-share-btn{display:flex;align-items:center;justify-content:center;gap:8px;padding:12px 20px;font-size:14px;font-weight:500;color:#ffffffe6;background:#0078d74d;border:1px solid rgba(0,120,215,.5);border-radius:8px;cursor:pointer;transition:all .2s ease}.native-share-btn:hover{background:#0078d766;border-color:#0078d799;transform:translateY(-1px)}.social-buttons{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}.social-btn{display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px 12px;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:8px;cursor:pointer;transition:all .2s ease}.social-btn:hover{background:#ffffff1a;border-color:#fff3;transform:translateY(-2px)}.social-btn:active{transform:translateY(0)}.social-icon{font-size:24px;font-weight:700;line-height:1}.social-label{font-size:12px;font-weight:500;color:#fffc}.social-btn.twitter{background:#1d9bf01a;border-color:#1d9bf04d}.social-btn.twitter:hover{background:#1d9bf033;border-color:#1d9bf080}.social-btn.facebook{background:#1877f21a;border-color:#1877f24d}.social-btn.facebook:hover{background:#1877f233;border-color:#1877f280}.social-btn.reddit{background:#ff45001a;border-color:#ff45004d}.social-btn.reddit:hover{background:#ff450033;border-color:#ff450080}.social-btn.email{background:#8080801a;border-color:#8080804d}.social-btn.email:hover{background:#80808033;border-color:#80808080}.qr-toggle-btn{display:flex;align-items:center;gap:8px;padding:10px 14px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:6px;cursor:pointer;transition:all .2s ease}.qr-toggle-btn:hover{background:#ffffff1a;border-color:#fff3}.btn-icon{font-size:14px;line-height:1}.btn-label{line-height:1}.qr-code-container{display:flex;flex-direction:column;align-items:center;gap:12px;padding:20px;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:8px;animation:fadeIn .2s ease}.qr-code-image{width:200px;height:200px;border-radius:8px;background:#fff;padding:8px}.qr-hint{font-size:12px;color:#fff9;margin:0}.qr-loading{padding:40px;font-size:13px;color:#ffffff80}.share-info{padding:12px 16px;background:#0078d71a;border:1px solid rgba(0,120,215,.2);border-radius:6px}.info-text{font-size:12px;line-height:1.5;color:#ffffffb3;margin:0}@media (max-width: 480px){.share-container{width:95%}.share-header{padding:16px 20px}.share-title{font-size:18px}.share-content{padding:20px;gap:20px}.social-buttons{grid-template-columns:1fr}.qr-code-image{width:180px;height:180px}}.close-btn:focus-visible,.copy-btn:focus-visible,.native-share-btn:focus-visible,.social-btn:focus-visible,.qr-toggle-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.share-overlay,.share-container,.qr-code-container,.close-btn,.copy-btn,.social-btn,.qr-toggle-btn{animation:none;transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
16318
16318
|
}
|
|
16319
16319
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ShareModalComponent, decorators: [{
|
|
16320
16320
|
type: Component,
|
|
16321
|
-
args: [{ selector: 'pw-share-modal', imports: [TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\r\n <div class=\"share-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\r\n <div class=\"share-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Share\">\r\n <!-- Header -->\r\n <div class=\"share-header\">\r\n <h2 class=\"share-title\">{{ 'share.title' | translate }}</h2>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'share.close' | translate\"\r\n (click)=\"onClose()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Content -->\r\n <div class=\"share-content\">\r\n @if (!shareable) {\r\n <!-- Not shareable message -->\r\n <div class=\"not-shareable\">\r\n <span class=\"warning-icon\"><pw-icon name=\"lucideTriangleAlert\" /></span>\r\n <p>{{ 'share.not_shareable' | translate }}</p>\r\n </div>\r\n } @else {\r\n <!-- Copy Link -->\r\n <div class=\"share-section\">\r\n <h3 class=\"section-title\">{{ 'share.copy_link' | translate }}</h3>\r\n <div class=\"link-container\">\r\n <input\r\n type=\"text\"\r\n class=\"link-input\"\r\n [value]=\"shareUrl\"\r\n readonly\r\n [attr.aria-label]=\"'share.share_url' | translate\" />\r\n <button\r\n class=\"copy-btn\"\r\n [class.success]=\"copySuccess\"\r\n type=\"button\"\r\n (click)=\"copyLink()\">\r\n <pw-icon [name]=\"copySuccess ? 'lucideCheck' : 'lucideClipboard'\" />\r\n {{ copySuccess ? ('share.copied' | translate) : ('share.copy' | translate) }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Native Share (if supported) -->\r\n @if (isNativeShareSupported) {\r\n <div class=\"share-section\">\r\n <button\r\n class=\"native-share-btn\"\r\n type=\"button\"\r\n (click)=\"useNativeShare()\">\r\n <span class=\"btn-icon\"><pw-icon name=\"lucideShare2\" /></span>\r\n <span class=\"btn-label\">{{ 'share.share_via' | translate }}</span>\r\n </button>\r\n </div>\r\n }\r\n\r\n <!-- Social Share Buttons -->\r\n <div class=\"share-section\">\r\n <h3 class=\"section-title\">{{ 'share.social_media' | translate }}</h3>\r\n <div class=\"social-buttons\">\r\n <button\r\n class=\"social-btn twitter\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'Share on ' + ('share.twitter' | translate)\"\r\n (click)=\"shareOnTwitter()\">\r\n <span class=\"social-icon\">\uD835\uDD4F</span>\r\n <span class=\"social-label\">{{ 'share.twitter' | translate }}</span>\r\n </button>\r\n\r\n <button\r\n class=\"social-btn facebook\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'Share on ' + ('share.facebook' | translate)\"\r\n (click)=\"shareOnFacebook()\">\r\n <span class=\"social-icon\">f</span>\r\n <span class=\"social-label\">{{ 'share.facebook' | translate }}</span>\r\n </button>\r\n\r\n <button\r\n class=\"social-btn reddit\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'Share on ' + ('share.reddit' | translate)\"\r\n (click)=\"shareOnReddit()\">\r\n <span class=\"social-icon\">\u25B2</span>\r\n <span class=\"social-label\">{{ 'share.reddit' | translate }}</span>\r\n </button>\r\n\r\n <button\r\n class=\"social-btn email\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'Share via ' + ('share.email' | translate)\"\r\n (click)=\"shareViaEmail()\">\r\n <span class=\"social-icon\"><pw-icon name=\"lucideMail\" /></span>\r\n <span class=\"social-label\">{{ 'share.email' | translate }}</span>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- QR Code -->\r\n <div class=\"share-section\">\r\n <button\r\n class=\"qr-toggle-btn\"\r\n type=\"button\"\r\n (click)=\"toggleQrCode()\">\r\n <span class=\"btn-icon\"><pw-icon [name]=\"showQrCode ? 'lucideChevronDown' : 'lucideChevronRight'\" /></span>\r\n <span class=\"btn-label\">{{ showQrCode ? ('share.hide_qr' | translate) : ('share.show_qr' | translate) }}</span>\r\n </button>\r\n\r\n @if (showQrCode) {\r\n <div class=\"qr-code-container\">\r\n @if (qrCodeDataUrl) {\r\n <img\r\n [src]=\"qrCodeDataUrl\"\r\n [alt]=\"'share.qr_code' | translate\"\r\n class=\"qr-code-image\" />\r\n <p class=\"qr-hint\">{{ 'share.scan_mobile' | translate }}</p>\r\n } @else {\r\n <div class=\"qr-loading\">{{ 'share.generating_qr' | translate }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Share Info -->\r\n <div class=\"share-info\">\r\n <p class=\"info-text\">\r\n <pw-icon name=\"lucideInfo\" /> {{ 'share.share_info' | translate }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [".share-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.share-container{width:90%;max-width:500px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.share-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.share-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.share-content{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:24px}.share-content::-webkit-scrollbar{width:8px}.share-content::-webkit-scrollbar-track{background:#ffffff0d}.share-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.not-shareable{display:flex;flex-direction:column;align-items:center;gap:16px;padding:40px 20px;text-align:center}.warning-icon{font-size:48px}.not-shareable p{font-size:14px;color:#ffffffb3;margin:0}.share-section{display:flex;flex-direction:column;gap:12px}.section-title{font-size:14px;font-weight:600;color:#ffffffe6;margin:0;text-transform:uppercase;letter-spacing:.5px}.link-container{display:flex;gap:8px}.link-input{flex:1;padding:10px 14px;font-size:13px;font-family:monospace;color:#fffc;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:6px;outline:none}.link-input:focus{border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.copy-btn{padding:10px 16px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease;white-space:nowrap}.copy-btn:hover{background:#ffffff26;border-color:#ffffff4d}.copy-btn.success{background:#00b4784d;border-color:#00b47880;color:#fff}.native-share-btn{display:flex;align-items:center;justify-content:center;gap:8px;padding:12px 20px;font-size:14px;font-weight:500;color:#ffffffe6;background:#0078d74d;border:1px solid rgba(0,120,215,.5);border-radius:8px;cursor:pointer;transition:all .2s ease}.native-share-btn:hover{background:#0078d766;border-color:#0078d799;transform:translateY(-1px)}.social-buttons{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}.social-btn{display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px 12px;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:8px;cursor:pointer;transition:all .2s ease}.social-btn:hover{background:#ffffff1a;border-color:#fff3;transform:translateY(-2px)}.social-btn:active{transform:translateY(0)}.social-icon{font-size:24px;font-weight:700;line-height:1}.social-label{font-size:12px;font-weight:500;color:#fffc}.social-btn.twitter{background:#1d9bf01a;border-color:#1d9bf04d}.social-btn.twitter:hover{background:#1d9bf033;border-color:#1d9bf080}.social-btn.facebook{background:#1877f21a;border-color:#1877f24d}.social-btn.facebook:hover{background:#1877f233;border-color:#1877f280}.social-btn.reddit{background:#ff45001a;border-color:#ff45004d}.social-btn.reddit:hover{background:#ff450033;border-color:#ff450080}.social-btn.email{background:#8080801a;border-color:#8080804d}.social-btn.email:hover{background:#80808033;border-color:#80808080}.qr-toggle-btn{display:flex;align-items:center;gap:8px;padding:10px 14px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:6px;cursor:pointer;transition:all .2s ease}.qr-toggle-btn:hover{background:#ffffff1a;border-color:#fff3}.btn-icon{font-size:14px;line-height:1}.btn-label{line-height:1}.qr-code-container{display:flex;flex-direction:column;align-items:center;gap:12px;padding:20px;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:8px;animation:fadeIn .2s ease}.qr-code-image{width:200px;height:200px;border-radius:8px;background:#fff;padding:8px}.qr-hint{font-size:12px;color:#fff9;margin:0}.qr-loading{padding:40px;font-size:13px;color:#ffffff80}.share-info{padding:12px 16px;background:#0078d71a;border:1px solid rgba(0,120,215,.2);border-radius:6px}.info-text{font-size:12px;line-height:1.5;color:#ffffffb3;margin:0}@media (max-width: 480px){.share-container{width:95%}.share-header{padding:16px 20px}.share-title{font-size:18px}.share-content{padding:20px;gap:20px}.social-buttons{grid-template-columns:1fr}.qr-code-image{width:180px;height:180px}}.close-btn:focus-visible,.copy-btn:focus-visible,.native-share-btn:focus-visible,.social-btn:focus-visible,.qr-toggle-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.share-overlay,.share-container,.qr-code-container,.close-btn,.copy-btn,.social-btn,.qr-toggle-btn{animation:none;transition:none}}\n"] }]
|
|
16321
|
+
args: [{ selector: 'pw-share-modal', imports: [TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\n <div class=\"share-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\n <div class=\"share-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Share\">\n <!-- Header -->\n <div class=\"share-header\">\n <h2 class=\"share-title\">{{ 'share.title' | translate }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'share.close' | translate\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"share-content\">\n @if (!shareable) {\n <!-- Not shareable message -->\n <div class=\"not-shareable\">\n <span class=\"warning-icon\"><pw-icon name=\"lucideTriangleAlert\" /></span>\n <p>{{ 'share.not_shareable' | translate }}</p>\n </div>\n } @else {\n <!-- Copy Link -->\n <div class=\"share-section\">\n <h3 class=\"section-title\">{{ 'share.copy_link' | translate }}</h3>\n <div class=\"link-container\">\n <input\n type=\"text\"\n class=\"link-input\"\n [value]=\"shareUrl\"\n readonly\n [attr.aria-label]=\"'share.share_url' | translate\" />\n <button\n class=\"copy-btn\"\n [class.success]=\"copySuccess\"\n type=\"button\"\n (click)=\"copyLink()\">\n <pw-icon [name]=\"copySuccess ? 'lucideCheck' : 'lucideClipboard'\" />\n {{ copySuccess ? ('share.copied' | translate) : ('share.copy' | translate) }}\n </button>\n </div>\n </div>\n\n <!-- Native Share (if supported) -->\n @if (isNativeShareSupported) {\n <div class=\"share-section\">\n <button\n class=\"native-share-btn\"\n type=\"button\"\n (click)=\"useNativeShare()\">\n <span class=\"btn-icon\"><pw-icon name=\"lucideShare2\" /></span>\n <span class=\"btn-label\">{{ 'share.share_via' | translate }}</span>\n </button>\n </div>\n }\n\n <!-- Social Share Buttons -->\n <div class=\"share-section\">\n <h3 class=\"section-title\">{{ 'share.social_media' | translate }}</h3>\n <div class=\"social-buttons\">\n <button\n class=\"social-btn twitter\"\n type=\"button\"\n [attr.aria-label]=\"'Share on ' + ('share.twitter' | translate)\"\n (click)=\"shareOnTwitter()\">\n <span class=\"social-icon\">\uD835\uDD4F</span>\n <span class=\"social-label\">{{ 'share.twitter' | translate }}</span>\n </button>\n\n <button\n class=\"social-btn facebook\"\n type=\"button\"\n [attr.aria-label]=\"'Share on ' + ('share.facebook' | translate)\"\n (click)=\"shareOnFacebook()\">\n <span class=\"social-icon\">f</span>\n <span class=\"social-label\">{{ 'share.facebook' | translate }}</span>\n </button>\n\n <button\n class=\"social-btn reddit\"\n type=\"button\"\n [attr.aria-label]=\"'Share on ' + ('share.reddit' | translate)\"\n (click)=\"shareOnReddit()\">\n <span class=\"social-icon\">\u25B2</span>\n <span class=\"social-label\">{{ 'share.reddit' | translate }}</span>\n </button>\n\n <button\n class=\"social-btn email\"\n type=\"button\"\n [attr.aria-label]=\"'Share via ' + ('share.email' | translate)\"\n (click)=\"shareViaEmail()\">\n <span class=\"social-icon\"><pw-icon name=\"lucideMail\" /></span>\n <span class=\"social-label\">{{ 'share.email' | translate }}</span>\n </button>\n </div>\n </div>\n\n <!-- QR Code -->\n <div class=\"share-section\">\n <button\n class=\"qr-toggle-btn\"\n type=\"button\"\n (click)=\"toggleQrCode()\">\n <span class=\"btn-icon\"><pw-icon [name]=\"showQrCode ? 'lucideChevronDown' : 'lucideChevronRight'\" /></span>\n <span class=\"btn-label\">{{ showQrCode ? ('share.hide_qr' | translate) : ('share.show_qr' | translate) }}</span>\n </button>\n\n @if (showQrCode) {\n <div class=\"qr-code-container\">\n @if (qrCodeDataUrl) {\n <img\n [src]=\"qrCodeDataUrl\"\n [alt]=\"'share.qr_code' | translate\"\n class=\"qr-code-image\" />\n <p class=\"qr-hint\">{{ 'share.scan_mobile' | translate }}</p>\n } @else {\n <div class=\"qr-loading\">{{ 'share.generating_qr' | translate }}</div>\n }\n </div>\n }\n </div>\n\n <!-- Share Info -->\n <div class=\"share-info\">\n <p class=\"info-text\">\n <pw-icon name=\"lucideInfo\" /> {{ 'share.share_info' | translate }}\n </p>\n </div>\n }\n </div>\n </div>\n </div>\n}\n", styles: [".share-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000000b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.share-container{width:90%;max-width:500px;max-height:85vh;background:#1a1a1a;border-radius:12px;box-shadow:0 8px 32px #0006;display:flex;flex-direction:column;overflow:hidden;animation:slideUp .3s cubic-bezier(.4,0,.2,1)}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.share-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.share-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.close-btn:active{background:#ffffff26;transform:scale(.95)}.share-content{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:24px}.share-content::-webkit-scrollbar{width:8px}.share-content::-webkit-scrollbar-track{background:#ffffff0d}.share-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.not-shareable{display:flex;flex-direction:column;align-items:center;gap:16px;padding:40px 20px;text-align:center}.warning-icon{font-size:48px}.not-shareable p{font-size:14px;color:#ffffffb3;margin:0}.share-section{display:flex;flex-direction:column;gap:12px}.section-title{font-size:14px;font-weight:600;color:#ffffffe6;margin:0;text-transform:uppercase;letter-spacing:.5px}.link-container{display:flex;gap:8px}.link-input{flex:1;padding:10px 14px;font-size:13px;font-family:monospace;color:#fffc;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:6px;outline:none}.link-input:focus{border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.copy-btn{padding:10px 16px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease;white-space:nowrap}.copy-btn:hover{background:#ffffff26;border-color:#ffffff4d}.copy-btn.success{background:#00b4784d;border-color:#00b47880;color:#fff}.native-share-btn{display:flex;align-items:center;justify-content:center;gap:8px;padding:12px 20px;font-size:14px;font-weight:500;color:#ffffffe6;background:#0078d74d;border:1px solid rgba(0,120,215,.5);border-radius:8px;cursor:pointer;transition:all .2s ease}.native-share-btn:hover{background:#0078d766;border-color:#0078d799;transform:translateY(-1px)}.social-buttons{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}.social-btn{display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px 12px;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:8px;cursor:pointer;transition:all .2s ease}.social-btn:hover{background:#ffffff1a;border-color:#fff3;transform:translateY(-2px)}.social-btn:active{transform:translateY(0)}.social-icon{font-size:24px;font-weight:700;line-height:1}.social-label{font-size:12px;font-weight:500;color:#fffc}.social-btn.twitter{background:#1d9bf01a;border-color:#1d9bf04d}.social-btn.twitter:hover{background:#1d9bf033;border-color:#1d9bf080}.social-btn.facebook{background:#1877f21a;border-color:#1877f24d}.social-btn.facebook:hover{background:#1877f233;border-color:#1877f280}.social-btn.reddit{background:#ff45001a;border-color:#ff45004d}.social-btn.reddit:hover{background:#ff450033;border-color:#ff450080}.social-btn.email{background:#8080801a;border-color:#8080804d}.social-btn.email:hover{background:#80808033;border-color:#80808080}.qr-toggle-btn{display:flex;align-items:center;gap:8px;padding:10px 14px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:6px;cursor:pointer;transition:all .2s ease}.qr-toggle-btn:hover{background:#ffffff1a;border-color:#fff3}.btn-icon{font-size:14px;line-height:1}.btn-label{line-height:1}.qr-code-container{display:flex;flex-direction:column;align-items:center;gap:12px;padding:20px;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:8px;animation:fadeIn .2s ease}.qr-code-image{width:200px;height:200px;border-radius:8px;background:#fff;padding:8px}.qr-hint{font-size:12px;color:#fff9;margin:0}.qr-loading{padding:40px;font-size:13px;color:#ffffff80}.share-info{padding:12px 16px;background:#0078d71a;border:1px solid rgba(0,120,215,.2);border-radius:6px}.info-text{font-size:12px;line-height:1.5;color:#ffffffb3;margin:0}@media (max-width: 480px){.share-container{width:95%}.share-header{padding:16px 20px}.share-title{font-size:18px}.share-content{padding:20px;gap:20px}.social-buttons{grid-template-columns:1fr}.qr-code-image{width:180px;height:180px}}.close-btn:focus-visible,.copy-btn:focus-visible,.native-share-btn:focus-visible,.social-btn:focus-visible,.qr-toggle-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.share-overlay,.share-container,.qr-code-container,.close-btn,.copy-btn,.social-btn,.qr-toggle-btn{animation:none;transition:none}}\n"] }]
|
|
16322
16322
|
}], propDecorators: { shareUrl: [{
|
|
16323
16323
|
type: Input
|
|
16324
16324
|
}], shareTitle: [{
|
|
@@ -16557,11 +16557,11 @@ class CommentsDrawerComponent {
|
|
|
16557
16557
|
}
|
|
16558
16558
|
}
|
|
16559
16559
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CommentsDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
16560
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: CommentsDrawerComponent, isStandalone: true, selector: "pw-comments-drawer", inputs: { comments: "comments", visible: "visible", loading: "loading", currentPage: "currentPage", totalPages: "totalPages", hasMore: "hasMore", isAuthenticated: "isAuthenticated" }, outputs: { postComment: "postComment", loadMore: "loadMore", likeComment: "likeComment", close: "close" }, host: { listeners: { "window:keydown": "handleKeyboard($event)" } }, ngImport: i0, template: "@if (visible) {\r\n <div class=\"drawer-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\r\n <div class=\"drawer-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Comments\">\r\n <!-- Header -->\r\n <div class=\"drawer-header\">\r\n <h2 class=\"drawer-title\">{{ 'comments.title' | translate }}</h2>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'comments.close' | translate\"\r\n (click)=\"onClose()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Comment Composer (Top) -->\r\n @if (isAuthenticated) {\r\n <div class=\"comment-composer\">\r\n <textarea\r\n class=\"composer-input\"\r\n [placeholder]=\"'comments.write_comment' | translate\"\r\n rows=\"3\"\r\n [(ngModel)]=\"newCommentContent\"\r\n [attr.aria-label]=\"'comments.new_comment_label' | translate\"></textarea>\r\n <div class=\"composer-actions\">\r\n <button\r\n class=\"post-btn\"\r\n type=\"button\"\r\n [disabled]=\"!newCommentContent.trim()\"\r\n (click)=\"submitComment()\">\r\n {{ 'comments.post_comment' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"auth-notice\">\r\n <p>{{ 'comments.sign_in' | translate }}</p>\r\n </div>\r\n }\r\n\r\n <!-- Comments List -->\r\n <div class=\"comments-content\">\r\n @if (loading && comments.length === 0) {\r\n <div class=\"loading-state\">\r\n <div class=\"loading-spinner\"></div>\r\n <p>{{ 'comments.loading' | translate }}</p>\r\n </div>\r\n } @else if (comments.length === 0) {\r\n <div class=\"empty-state\">\r\n <span class=\"empty-icon\"><pw-icon name=\"lucideMessageCircle\" /></span>\r\n <p>{{ 'comments.no_comments' | translate }}</p>\r\n </div>\r\n } @else {\r\n <div class=\"comments-list\">\r\n @for (comment of comments; track comment.id) {\r\n <div class=\"comment-item\">\r\n <!-- Avatar -->\r\n <div class=\"comment-avatar\">\r\n @if (comment.authorAvatar) {\r\n <img\r\n [src]=\"comment.authorAvatar\"\r\n [alt]=\"comment.author\"\r\n class=\"avatar-image\" />\r\n } @else {\r\n <div class=\"avatar-placeholder\">\r\n {{ getAuthorInitials(comment.author) }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Content -->\r\n <div class=\"comment-content\">\r\n <div class=\"comment-header\">\r\n <span class=\"comment-author\">{{ comment.author }}</span>\r\n <span class=\"comment-timestamp\">{{ formatTimestamp(comment.timestamp) }}</span>\r\n </div>\r\n\r\n <p class=\"comment-text\">{{ comment.content }}</p>\r\n\r\n <!-- Actions -->\r\n <div class=\"comment-actions\">\r\n @if (isAuthenticated) {\r\n <button\r\n class=\"action-btn\"\r\n [class.active]=\"comment.isLiked\"\r\n type=\"button\"\r\n (click)=\"onLikeComment(comment)\">\r\n <pw-icon name=\"lucideHeart\" />\r\n @if (comment.likes && comment.likes > 0) {\r\n <span>{{ comment.likes }}</span>\r\n }\r\n </button>\r\n\r\n <button\r\n class=\"action-btn\"\r\n type=\"button\"\r\n (click)=\"startReply(comment)\">\r\n {{ 'comments.reply' | translate }}\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Replies -->\r\n @if (comment.replies && comment.replies.length > 0) {\r\n <div class=\"comment-replies\">\r\n @for (reply of comment.replies; track reply.id) {\r\n <div class=\"reply-item\">\r\n <div class=\"comment-avatar small\">\r\n @if (reply.authorAvatar) {\r\n <img\r\n [src]=\"reply.authorAvatar\"\r\n [alt]=\"reply.author\"\r\n class=\"avatar-image\" />\r\n } @else {\r\n <div class=\"avatar-placeholder\">\r\n {{ getAuthorInitials(reply.author) }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"comment-content\">\r\n <div class=\"comment-header\">\r\n <span class=\"comment-author\">{{ reply.author }}</span>\r\n <span class=\"comment-timestamp\">{{ formatTimestamp(reply.timestamp) }}</span>\r\n </div>\r\n <p class=\"comment-text\">{{ reply.content }}</p>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Reply Composer -->\r\n @if (showReplyComposer && replyTarget?.id === comment.id) {\r\n <div class=\"reply-composer\">\r\n <textarea\r\n class=\"composer-input\"\r\n [placeholder]=\"'comments.write_reply' | translate\"\r\n rows=\"2\"\r\n [(ngModel)]=\"replyContent\"\r\n [attr.aria-label]=\"'comments.reply_label' | translate\"></textarea>\r\n <div class=\"composer-actions\">\r\n <button\r\n class=\"cancel-btn\"\r\n type=\"button\"\r\n (click)=\"cancelReply()\">\r\n {{ 'comments.cancel' | translate }}\r\n </button>\r\n <button\r\n class=\"post-btn\"\r\n type=\"button\"\r\n [disabled]=\"!replyContent.trim()\"\r\n (click)=\"submitReply()\">\r\n {{ 'comments.reply' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Load More -->\r\n @if (hasMore) {\r\n <div class=\"load-more\">\r\n @if (loading) {\r\n <div class=\"loading-spinner small\"></div>\r\n } @else {\r\n <button\r\n class=\"load-more-btn\"\r\n type=\"button\"\r\n (click)=\"onLoadMore()\">\r\n {{ 'comments.load_more' | translate }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer Info -->\r\n <div class=\"drawer-footer\">\r\n <p class=\"footer-text\">\r\n {{ comments.length }} {{ comments.length === 1 ? ('comments.comment_count_singular' | translate) : ('comments.comment_count_plural' | translate) }}\r\n @if (totalPages > 1) {\r\n <span> \u2022 {{ 'comments.page_of' | translate: {current: currentPage, total: totalPages} }}</span>\r\n }\r\n </p>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [".drawer-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:stretch;justify-content:flex-end;background:#00000080;animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.drawer-container{width:90%;max-width:450px;background:#1a1a1a;box-shadow:-4px 0 16px #0000004d;display:flex;flex-direction:column;overflow:hidden;animation:slideInRight .3s cubic-bezier(.4,0,.2,1)}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}.drawer-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.drawer-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.comment-composer{padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.composer-input{width:100%;padding:12px;font-size:14px;line-height:1.5;color:#fff;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:8px;outline:none;resize:vertical;font-family:inherit;transition:all .2s ease}.composer-input::placeholder{color:#fff6}.composer-input:focus{background:#ffffff14;border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.composer-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.post-btn{padding:8px 16px;font-size:13px;font-weight:500;color:#fff;background:#0078d7cc;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.post-btn:hover:not(:disabled){background:#0078d7e6;transform:translateY(-1px)}.post-btn:disabled{opacity:.5;cursor:not-allowed;transform:none}.cancel-btn{padding:8px 16px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease}.cancel-btn:hover{background:#ffffff26}.auth-notice{padding:16px 24px;background:#ffa5001a;border-bottom:1px solid rgba(255,165,0,.2);flex-shrink:0}.auth-notice p{font-size:13px;color:#fffc;margin:0;text-align:center}.comments-content{flex:1;overflow-y:auto;padding:16px 24px}.comments-content::-webkit-scrollbar{width:8px}.comments-content::-webkit-scrollbar-track{background:#ffffff0d}.comments-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.loading-state{display:flex;flex-direction:column;align-items:center;gap:16px;padding:60px 20px}.loading-spinner{width:40px;height:40px;border:3px solid rgba(255,255,255,.1);border-top-color:#fff;border-radius:50%;animation:spin 1s linear infinite}.loading-spinner.small{width:24px;height:24px;border-width:2px}@keyframes spin{to{transform:rotate(360deg)}}.loading-state p{font-size:14px;color:#ffffff80;margin:0}.empty-state{display:flex;flex-direction:column;align-items:center;gap:16px;padding:60px 20px;text-align:center}.empty-icon{font-size:48px}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.comments-list{display:flex;flex-direction:column;gap:20px}.comment-item{display:flex;gap:12px}.comment-avatar{width:40px;height:40px;flex-shrink:0;border-radius:50%;overflow:hidden}.comment-avatar.small{width:32px;height:32px}.avatar-image{width:100%;height:100%;object-fit:cover;display:block}.avatar-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:600;color:#ffffffe6;background:linear-gradient(135deg,#0078d74d,#00b4784d)}.comment-avatar.small .avatar-placeholder{font-size:12px}.comment-content{flex:1;min-width:0}.comment-header{display:flex;align-items:baseline;gap:8px;margin-bottom:6px}.comment-author{font-size:14px;font-weight:600;color:#ffffffe6}.comment-timestamp{font-size:12px;color:#ffffff80}.comment-text{font-size:14px;line-height:1.5;color:#fffc;margin:0 0 8px;word-wrap:break-word}.comment-actions{display:flex;gap:12px;margin-top:8px}.action-btn{display:flex;align-items:center;gap:4px;padding:4px 8px;font-size:12px;font-weight:500;color:#fff9;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:all .2s ease}.action-btn:hover{color:#ffffffe6;background:#ffffff1a}.action-btn.active{color:#ff4d6d}.comment-replies{margin-top:12px;padding-left:12px;border-left:2px solid rgba(255,255,255,.1);display:flex;flex-direction:column;gap:16px}.reply-item{display:flex;gap:10px}.reply-composer{margin-top:12px;padding-top:12px;border-top:1px solid rgba(255,255,255,.1)}.load-more{display:flex;justify-content:center;padding:16px 0;margin-top:16px;border-top:1px solid rgba(255,255,255,.1)}.load-more-btn{padding:10px 20px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease}.load-more-btn:hover{background:#ffffff26;border-color:#ffffff4d}.drawer-footer{padding:12px 24px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.footer-text{font-size:12px;color:#ffffff80;margin:0;text-align:center}@media (max-width: 768px){.drawer-container{width:100%;max-width:none}.drawer-header{padding:16px 20px}.drawer-title{font-size:18px}.comment-composer,.auth-notice,.comments-content{padding:14px 20px}.comment-avatar{width:36px;height:36px}.comment-avatar.small{width:28px;height:28px}.avatar-placeholder{font-size:13px}.comment-avatar.small .avatar-placeholder{font-size:11px}}.close-btn:focus-visible,.composer-input:focus-visible,.post-btn:focus-visible,.cancel-btn:focus-visible,.action-btn:focus-visible,.load-more-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.drawer-overlay,.drawer-container,.loading-spinner,.close-btn,.post-btn,.cancel-btn,.action-btn,.load-more-btn{animation:none;transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
16560
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: CommentsDrawerComponent, isStandalone: true, selector: "pw-comments-drawer", inputs: { comments: "comments", visible: "visible", loading: "loading", currentPage: "currentPage", totalPages: "totalPages", hasMore: "hasMore", isAuthenticated: "isAuthenticated" }, outputs: { postComment: "postComment", loadMore: "loadMore", likeComment: "likeComment", close: "close" }, host: { listeners: { "window:keydown": "handleKeyboard($event)" } }, ngImport: i0, template: "@if (visible) {\n <div class=\"drawer-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\n <div class=\"drawer-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Comments\">\n <!-- Header -->\n <div class=\"drawer-header\">\n <h2 class=\"drawer-title\">{{ 'comments.title' | translate }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'comments.close' | translate\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Comment Composer (Top) -->\n @if (isAuthenticated) {\n <div class=\"comment-composer\">\n <textarea\n class=\"composer-input\"\n [placeholder]=\"'comments.write_comment' | translate\"\n rows=\"3\"\n [(ngModel)]=\"newCommentContent\"\n [attr.aria-label]=\"'comments.new_comment_label' | translate\"></textarea>\n <div class=\"composer-actions\">\n <button\n class=\"post-btn\"\n type=\"button\"\n [disabled]=\"!newCommentContent.trim()\"\n (click)=\"submitComment()\">\n {{ 'comments.post_comment' | translate }}\n </button>\n </div>\n </div>\n } @else {\n <div class=\"auth-notice\">\n <p>{{ 'comments.sign_in' | translate }}</p>\n </div>\n }\n\n <!-- Comments List -->\n <div class=\"comments-content\">\n @if (loading && comments.length === 0) {\n <div class=\"loading-state\">\n <div class=\"loading-spinner\"></div>\n <p>{{ 'comments.loading' | translate }}</p>\n </div>\n } @else if (comments.length === 0) {\n <div class=\"empty-state\">\n <span class=\"empty-icon\"><pw-icon name=\"lucideMessageCircle\" /></span>\n <p>{{ 'comments.no_comments' | translate }}</p>\n </div>\n } @else {\n <div class=\"comments-list\">\n @for (comment of comments; track comment.id) {\n <div class=\"comment-item\">\n <!-- Avatar -->\n <div class=\"comment-avatar\">\n @if (comment.authorAvatar) {\n <img\n [src]=\"comment.authorAvatar\"\n [alt]=\"comment.author\"\n class=\"avatar-image\" />\n } @else {\n <div class=\"avatar-placeholder\">\n {{ getAuthorInitials(comment.author) }}\n </div>\n }\n </div>\n\n <!-- Content -->\n <div class=\"comment-content\">\n <div class=\"comment-header\">\n <span class=\"comment-author\">{{ comment.author }}</span>\n <span class=\"comment-timestamp\">{{ formatTimestamp(comment.timestamp) }}</span>\n </div>\n\n <p class=\"comment-text\">{{ comment.content }}</p>\n\n <!-- Actions -->\n <div class=\"comment-actions\">\n @if (isAuthenticated) {\n <button\n class=\"action-btn\"\n [class.active]=\"comment.isLiked\"\n type=\"button\"\n (click)=\"onLikeComment(comment)\">\n <pw-icon name=\"lucideHeart\" />\n @if (comment.likes && comment.likes > 0) {\n <span>{{ comment.likes }}</span>\n }\n </button>\n\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"startReply(comment)\">\n {{ 'comments.reply' | translate }}\n </button>\n }\n </div>\n\n <!-- Replies -->\n @if (comment.replies && comment.replies.length > 0) {\n <div class=\"comment-replies\">\n @for (reply of comment.replies; track reply.id) {\n <div class=\"reply-item\">\n <div class=\"comment-avatar small\">\n @if (reply.authorAvatar) {\n <img\n [src]=\"reply.authorAvatar\"\n [alt]=\"reply.author\"\n class=\"avatar-image\" />\n } @else {\n <div class=\"avatar-placeholder\">\n {{ getAuthorInitials(reply.author) }}\n </div>\n }\n </div>\n\n <div class=\"comment-content\">\n <div class=\"comment-header\">\n <span class=\"comment-author\">{{ reply.author }}</span>\n <span class=\"comment-timestamp\">{{ formatTimestamp(reply.timestamp) }}</span>\n </div>\n <p class=\"comment-text\">{{ reply.content }}</p>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Reply Composer -->\n @if (showReplyComposer && replyTarget?.id === comment.id) {\n <div class=\"reply-composer\">\n <textarea\n class=\"composer-input\"\n [placeholder]=\"'comments.write_reply' | translate\"\n rows=\"2\"\n [(ngModel)]=\"replyContent\"\n [attr.aria-label]=\"'comments.reply_label' | translate\"></textarea>\n <div class=\"composer-actions\">\n <button\n class=\"cancel-btn\"\n type=\"button\"\n (click)=\"cancelReply()\">\n {{ 'comments.cancel' | translate }}\n </button>\n <button\n class=\"post-btn\"\n type=\"button\"\n [disabled]=\"!replyContent.trim()\"\n (click)=\"submitReply()\">\n {{ 'comments.reply' | translate }}\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Load More -->\n @if (hasMore) {\n <div class=\"load-more\">\n @if (loading) {\n <div class=\"loading-spinner small\"></div>\n } @else {\n <button\n class=\"load-more-btn\"\n type=\"button\"\n (click)=\"onLoadMore()\">\n {{ 'comments.load_more' | translate }}\n </button>\n }\n </div>\n }\n }\n </div>\n\n <!-- Footer Info -->\n <div class=\"drawer-footer\">\n <p class=\"footer-text\">\n {{ comments.length }} {{ comments.length === 1 ? ('comments.comment_count_singular' | translate) : ('comments.comment_count_plural' | translate) }}\n @if (totalPages > 1) {\n <span> \u2022 {{ 'comments.page_of' | translate: {current: currentPage, total: totalPages} }}</span>\n }\n </p>\n </div>\n </div>\n </div>\n}\n", styles: [".drawer-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:stretch;justify-content:flex-end;background:#00000080;animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.drawer-container{width:90%;max-width:450px;background:#1a1a1a;box-shadow:-4px 0 16px #0000004d;display:flex;flex-direction:column;overflow:hidden;animation:slideInRight .3s cubic-bezier(.4,0,.2,1)}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}.drawer-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.drawer-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.comment-composer{padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.composer-input{width:100%;padding:12px;font-size:14px;line-height:1.5;color:#fff;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:8px;outline:none;resize:vertical;font-family:inherit;transition:all .2s ease}.composer-input::placeholder{color:#fff6}.composer-input:focus{background:#ffffff14;border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.composer-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.post-btn{padding:8px 16px;font-size:13px;font-weight:500;color:#fff;background:#0078d7cc;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.post-btn:hover:not(:disabled){background:#0078d7e6;transform:translateY(-1px)}.post-btn:disabled{opacity:.5;cursor:not-allowed;transform:none}.cancel-btn{padding:8px 16px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease}.cancel-btn:hover{background:#ffffff26}.auth-notice{padding:16px 24px;background:#ffa5001a;border-bottom:1px solid rgba(255,165,0,.2);flex-shrink:0}.auth-notice p{font-size:13px;color:#fffc;margin:0;text-align:center}.comments-content{flex:1;overflow-y:auto;padding:16px 24px}.comments-content::-webkit-scrollbar{width:8px}.comments-content::-webkit-scrollbar-track{background:#ffffff0d}.comments-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.loading-state{display:flex;flex-direction:column;align-items:center;gap:16px;padding:60px 20px}.loading-spinner{width:40px;height:40px;border:3px solid rgba(255,255,255,.1);border-top-color:#fff;border-radius:50%;animation:spin 1s linear infinite}.loading-spinner.small{width:24px;height:24px;border-width:2px}@keyframes spin{to{transform:rotate(360deg)}}.loading-state p{font-size:14px;color:#ffffff80;margin:0}.empty-state{display:flex;flex-direction:column;align-items:center;gap:16px;padding:60px 20px;text-align:center}.empty-icon{font-size:48px}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.comments-list{display:flex;flex-direction:column;gap:20px}.comment-item{display:flex;gap:12px}.comment-avatar{width:40px;height:40px;flex-shrink:0;border-radius:50%;overflow:hidden}.comment-avatar.small{width:32px;height:32px}.avatar-image{width:100%;height:100%;object-fit:cover;display:block}.avatar-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:600;color:#ffffffe6;background:linear-gradient(135deg,#0078d74d,#00b4784d)}.comment-avatar.small .avatar-placeholder{font-size:12px}.comment-content{flex:1;min-width:0}.comment-header{display:flex;align-items:baseline;gap:8px;margin-bottom:6px}.comment-author{font-size:14px;font-weight:600;color:#ffffffe6}.comment-timestamp{font-size:12px;color:#ffffff80}.comment-text{font-size:14px;line-height:1.5;color:#fffc;margin:0 0 8px;word-wrap:break-word}.comment-actions{display:flex;gap:12px;margin-top:8px}.action-btn{display:flex;align-items:center;gap:4px;padding:4px 8px;font-size:12px;font-weight:500;color:#fff9;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:all .2s ease}.action-btn:hover{color:#ffffffe6;background:#ffffff1a}.action-btn.active{color:#ff4d6d}.comment-replies{margin-top:12px;padding-left:12px;border-left:2px solid rgba(255,255,255,.1);display:flex;flex-direction:column;gap:16px}.reply-item{display:flex;gap:10px}.reply-composer{margin-top:12px;padding-top:12px;border-top:1px solid rgba(255,255,255,.1)}.load-more{display:flex;justify-content:center;padding:16px 0;margin-top:16px;border-top:1px solid rgba(255,255,255,.1)}.load-more-btn{padding:10px 20px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease}.load-more-btn:hover{background:#ffffff26;border-color:#ffffff4d}.drawer-footer{padding:12px 24px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.footer-text{font-size:12px;color:#ffffff80;margin:0;text-align:center}@media (max-width: 768px){.drawer-container{width:100%;max-width:none}.drawer-header{padding:16px 20px}.drawer-title{font-size:18px}.comment-composer,.auth-notice,.comments-content{padding:14px 20px}.comment-avatar{width:36px;height:36px}.comment-avatar.small{width:28px;height:28px}.avatar-placeholder{font-size:13px}.comment-avatar.small .avatar-placeholder{font-size:11px}}.close-btn:focus-visible,.composer-input:focus-visible,.post-btn:focus-visible,.cancel-btn:focus-visible,.action-btn:focus-visible,.load-more-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.drawer-overlay,.drawer-container,.loading-spinner,.close-btn,.post-btn,.cancel-btn,.action-btn,.load-more-btn{animation:none;transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: PwIconComponent, selector: "pw-icon", inputs: ["name"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
16561
16561
|
}
|
|
16562
16562
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CommentsDrawerComponent, decorators: [{
|
|
16563
16563
|
type: Component,
|
|
16564
|
-
args: [{ selector: 'pw-comments-drawer', imports: [FormsModule, TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\r\n <div class=\"drawer-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\r\n <div class=\"drawer-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Comments\">\r\n <!-- Header -->\r\n <div class=\"drawer-header\">\r\n <h2 class=\"drawer-title\">{{ 'comments.title' | translate }}</h2>\r\n <button\r\n class=\"close-btn\"\r\n type=\"button\"\r\n [attr.aria-label]=\"'comments.close' | translate\"\r\n (click)=\"onClose()\">\r\n <pw-icon name=\"lucideX\" />\r\n </button>\r\n </div>\r\n\r\n <!-- Comment Composer (Top) -->\r\n @if (isAuthenticated) {\r\n <div class=\"comment-composer\">\r\n <textarea\r\n class=\"composer-input\"\r\n [placeholder]=\"'comments.write_comment' | translate\"\r\n rows=\"3\"\r\n [(ngModel)]=\"newCommentContent\"\r\n [attr.aria-label]=\"'comments.new_comment_label' | translate\"></textarea>\r\n <div class=\"composer-actions\">\r\n <button\r\n class=\"post-btn\"\r\n type=\"button\"\r\n [disabled]=\"!newCommentContent.trim()\"\r\n (click)=\"submitComment()\">\r\n {{ 'comments.post_comment' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"auth-notice\">\r\n <p>{{ 'comments.sign_in' | translate }}</p>\r\n </div>\r\n }\r\n\r\n <!-- Comments List -->\r\n <div class=\"comments-content\">\r\n @if (loading && comments.length === 0) {\r\n <div class=\"loading-state\">\r\n <div class=\"loading-spinner\"></div>\r\n <p>{{ 'comments.loading' | translate }}</p>\r\n </div>\r\n } @else if (comments.length === 0) {\r\n <div class=\"empty-state\">\r\n <span class=\"empty-icon\"><pw-icon name=\"lucideMessageCircle\" /></span>\r\n <p>{{ 'comments.no_comments' | translate }}</p>\r\n </div>\r\n } @else {\r\n <div class=\"comments-list\">\r\n @for (comment of comments; track comment.id) {\r\n <div class=\"comment-item\">\r\n <!-- Avatar -->\r\n <div class=\"comment-avatar\">\r\n @if (comment.authorAvatar) {\r\n <img\r\n [src]=\"comment.authorAvatar\"\r\n [alt]=\"comment.author\"\r\n class=\"avatar-image\" />\r\n } @else {\r\n <div class=\"avatar-placeholder\">\r\n {{ getAuthorInitials(comment.author) }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Content -->\r\n <div class=\"comment-content\">\r\n <div class=\"comment-header\">\r\n <span class=\"comment-author\">{{ comment.author }}</span>\r\n <span class=\"comment-timestamp\">{{ formatTimestamp(comment.timestamp) }}</span>\r\n </div>\r\n\r\n <p class=\"comment-text\">{{ comment.content }}</p>\r\n\r\n <!-- Actions -->\r\n <div class=\"comment-actions\">\r\n @if (isAuthenticated) {\r\n <button\r\n class=\"action-btn\"\r\n [class.active]=\"comment.isLiked\"\r\n type=\"button\"\r\n (click)=\"onLikeComment(comment)\">\r\n <pw-icon name=\"lucideHeart\" />\r\n @if (comment.likes && comment.likes > 0) {\r\n <span>{{ comment.likes }}</span>\r\n }\r\n </button>\r\n\r\n <button\r\n class=\"action-btn\"\r\n type=\"button\"\r\n (click)=\"startReply(comment)\">\r\n {{ 'comments.reply' | translate }}\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Replies -->\r\n @if (comment.replies && comment.replies.length > 0) {\r\n <div class=\"comment-replies\">\r\n @for (reply of comment.replies; track reply.id) {\r\n <div class=\"reply-item\">\r\n <div class=\"comment-avatar small\">\r\n @if (reply.authorAvatar) {\r\n <img\r\n [src]=\"reply.authorAvatar\"\r\n [alt]=\"reply.author\"\r\n class=\"avatar-image\" />\r\n } @else {\r\n <div class=\"avatar-placeholder\">\r\n {{ getAuthorInitials(reply.author) }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"comment-content\">\r\n <div class=\"comment-header\">\r\n <span class=\"comment-author\">{{ reply.author }}</span>\r\n <span class=\"comment-timestamp\">{{ formatTimestamp(reply.timestamp) }}</span>\r\n </div>\r\n <p class=\"comment-text\">{{ reply.content }}</p>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Reply Composer -->\r\n @if (showReplyComposer && replyTarget?.id === comment.id) {\r\n <div class=\"reply-composer\">\r\n <textarea\r\n class=\"composer-input\"\r\n [placeholder]=\"'comments.write_reply' | translate\"\r\n rows=\"2\"\r\n [(ngModel)]=\"replyContent\"\r\n [attr.aria-label]=\"'comments.reply_label' | translate\"></textarea>\r\n <div class=\"composer-actions\">\r\n <button\r\n class=\"cancel-btn\"\r\n type=\"button\"\r\n (click)=\"cancelReply()\">\r\n {{ 'comments.cancel' | translate }}\r\n </button>\r\n <button\r\n class=\"post-btn\"\r\n type=\"button\"\r\n [disabled]=\"!replyContent.trim()\"\r\n (click)=\"submitReply()\">\r\n {{ 'comments.reply' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Load More -->\r\n @if (hasMore) {\r\n <div class=\"load-more\">\r\n @if (loading) {\r\n <div class=\"loading-spinner small\"></div>\r\n } @else {\r\n <button\r\n class=\"load-more-btn\"\r\n type=\"button\"\r\n (click)=\"onLoadMore()\">\r\n {{ 'comments.load_more' | translate }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer Info -->\r\n <div class=\"drawer-footer\">\r\n <p class=\"footer-text\">\r\n {{ comments.length }} {{ comments.length === 1 ? ('comments.comment_count_singular' | translate) : ('comments.comment_count_plural' | translate) }}\r\n @if (totalPages > 1) {\r\n <span> \u2022 {{ 'comments.page_of' | translate: {current: currentPage, total: totalPages} }}</span>\r\n }\r\n </p>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [".drawer-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:stretch;justify-content:flex-end;background:#00000080;animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.drawer-container{width:90%;max-width:450px;background:#1a1a1a;box-shadow:-4px 0 16px #0000004d;display:flex;flex-direction:column;overflow:hidden;animation:slideInRight .3s cubic-bezier(.4,0,.2,1)}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}.drawer-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.drawer-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.comment-composer{padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.composer-input{width:100%;padding:12px;font-size:14px;line-height:1.5;color:#fff;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:8px;outline:none;resize:vertical;font-family:inherit;transition:all .2s ease}.composer-input::placeholder{color:#fff6}.composer-input:focus{background:#ffffff14;border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.composer-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.post-btn{padding:8px 16px;font-size:13px;font-weight:500;color:#fff;background:#0078d7cc;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.post-btn:hover:not(:disabled){background:#0078d7e6;transform:translateY(-1px)}.post-btn:disabled{opacity:.5;cursor:not-allowed;transform:none}.cancel-btn{padding:8px 16px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease}.cancel-btn:hover{background:#ffffff26}.auth-notice{padding:16px 24px;background:#ffa5001a;border-bottom:1px solid rgba(255,165,0,.2);flex-shrink:0}.auth-notice p{font-size:13px;color:#fffc;margin:0;text-align:center}.comments-content{flex:1;overflow-y:auto;padding:16px 24px}.comments-content::-webkit-scrollbar{width:8px}.comments-content::-webkit-scrollbar-track{background:#ffffff0d}.comments-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.loading-state{display:flex;flex-direction:column;align-items:center;gap:16px;padding:60px 20px}.loading-spinner{width:40px;height:40px;border:3px solid rgba(255,255,255,.1);border-top-color:#fff;border-radius:50%;animation:spin 1s linear infinite}.loading-spinner.small{width:24px;height:24px;border-width:2px}@keyframes spin{to{transform:rotate(360deg)}}.loading-state p{font-size:14px;color:#ffffff80;margin:0}.empty-state{display:flex;flex-direction:column;align-items:center;gap:16px;padding:60px 20px;text-align:center}.empty-icon{font-size:48px}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.comments-list{display:flex;flex-direction:column;gap:20px}.comment-item{display:flex;gap:12px}.comment-avatar{width:40px;height:40px;flex-shrink:0;border-radius:50%;overflow:hidden}.comment-avatar.small{width:32px;height:32px}.avatar-image{width:100%;height:100%;object-fit:cover;display:block}.avatar-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:600;color:#ffffffe6;background:linear-gradient(135deg,#0078d74d,#00b4784d)}.comment-avatar.small .avatar-placeholder{font-size:12px}.comment-content{flex:1;min-width:0}.comment-header{display:flex;align-items:baseline;gap:8px;margin-bottom:6px}.comment-author{font-size:14px;font-weight:600;color:#ffffffe6}.comment-timestamp{font-size:12px;color:#ffffff80}.comment-text{font-size:14px;line-height:1.5;color:#fffc;margin:0 0 8px;word-wrap:break-word}.comment-actions{display:flex;gap:12px;margin-top:8px}.action-btn{display:flex;align-items:center;gap:4px;padding:4px 8px;font-size:12px;font-weight:500;color:#fff9;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:all .2s ease}.action-btn:hover{color:#ffffffe6;background:#ffffff1a}.action-btn.active{color:#ff4d6d}.comment-replies{margin-top:12px;padding-left:12px;border-left:2px solid rgba(255,255,255,.1);display:flex;flex-direction:column;gap:16px}.reply-item{display:flex;gap:10px}.reply-composer{margin-top:12px;padding-top:12px;border-top:1px solid rgba(255,255,255,.1)}.load-more{display:flex;justify-content:center;padding:16px 0;margin-top:16px;border-top:1px solid rgba(255,255,255,.1)}.load-more-btn{padding:10px 20px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease}.load-more-btn:hover{background:#ffffff26;border-color:#ffffff4d}.drawer-footer{padding:12px 24px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.footer-text{font-size:12px;color:#ffffff80;margin:0;text-align:center}@media (max-width: 768px){.drawer-container{width:100%;max-width:none}.drawer-header{padding:16px 20px}.drawer-title{font-size:18px}.comment-composer,.auth-notice,.comments-content{padding:14px 20px}.comment-avatar{width:36px;height:36px}.comment-avatar.small{width:28px;height:28px}.avatar-placeholder{font-size:13px}.comment-avatar.small .avatar-placeholder{font-size:11px}}.close-btn:focus-visible,.composer-input:focus-visible,.post-btn:focus-visible,.cancel-btn:focus-visible,.action-btn:focus-visible,.load-more-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.drawer-overlay,.drawer-container,.loading-spinner,.close-btn,.post-btn,.cancel-btn,.action-btn,.load-more-btn{animation:none;transition:none}}\n"] }]
|
|
16564
|
+
args: [{ selector: 'pw-comments-drawer', imports: [FormsModule, TranslateModule, PwIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible) {\n <div class=\"drawer-overlay\" tabindex=\"-1\" (click)=\"onBackdropClick($event)\" (keydown)=\"onBackdropKeydown($event)\">\n <div class=\"drawer-container\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Comments\">\n <!-- Header -->\n <div class=\"drawer-header\">\n <h2 class=\"drawer-title\">{{ 'comments.title' | translate }}</h2>\n <button\n class=\"close-btn\"\n type=\"button\"\n [attr.aria-label]=\"'comments.close' | translate\"\n (click)=\"onClose()\">\n <pw-icon name=\"lucideX\" />\n </button>\n </div>\n\n <!-- Comment Composer (Top) -->\n @if (isAuthenticated) {\n <div class=\"comment-composer\">\n <textarea\n class=\"composer-input\"\n [placeholder]=\"'comments.write_comment' | translate\"\n rows=\"3\"\n [(ngModel)]=\"newCommentContent\"\n [attr.aria-label]=\"'comments.new_comment_label' | translate\"></textarea>\n <div class=\"composer-actions\">\n <button\n class=\"post-btn\"\n type=\"button\"\n [disabled]=\"!newCommentContent.trim()\"\n (click)=\"submitComment()\">\n {{ 'comments.post_comment' | translate }}\n </button>\n </div>\n </div>\n } @else {\n <div class=\"auth-notice\">\n <p>{{ 'comments.sign_in' | translate }}</p>\n </div>\n }\n\n <!-- Comments List -->\n <div class=\"comments-content\">\n @if (loading && comments.length === 0) {\n <div class=\"loading-state\">\n <div class=\"loading-spinner\"></div>\n <p>{{ 'comments.loading' | translate }}</p>\n </div>\n } @else if (comments.length === 0) {\n <div class=\"empty-state\">\n <span class=\"empty-icon\"><pw-icon name=\"lucideMessageCircle\" /></span>\n <p>{{ 'comments.no_comments' | translate }}</p>\n </div>\n } @else {\n <div class=\"comments-list\">\n @for (comment of comments; track comment.id) {\n <div class=\"comment-item\">\n <!-- Avatar -->\n <div class=\"comment-avatar\">\n @if (comment.authorAvatar) {\n <img\n [src]=\"comment.authorAvatar\"\n [alt]=\"comment.author\"\n class=\"avatar-image\" />\n } @else {\n <div class=\"avatar-placeholder\">\n {{ getAuthorInitials(comment.author) }}\n </div>\n }\n </div>\n\n <!-- Content -->\n <div class=\"comment-content\">\n <div class=\"comment-header\">\n <span class=\"comment-author\">{{ comment.author }}</span>\n <span class=\"comment-timestamp\">{{ formatTimestamp(comment.timestamp) }}</span>\n </div>\n\n <p class=\"comment-text\">{{ comment.content }}</p>\n\n <!-- Actions -->\n <div class=\"comment-actions\">\n @if (isAuthenticated) {\n <button\n class=\"action-btn\"\n [class.active]=\"comment.isLiked\"\n type=\"button\"\n (click)=\"onLikeComment(comment)\">\n <pw-icon name=\"lucideHeart\" />\n @if (comment.likes && comment.likes > 0) {\n <span>{{ comment.likes }}</span>\n }\n </button>\n\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"startReply(comment)\">\n {{ 'comments.reply' | translate }}\n </button>\n }\n </div>\n\n <!-- Replies -->\n @if (comment.replies && comment.replies.length > 0) {\n <div class=\"comment-replies\">\n @for (reply of comment.replies; track reply.id) {\n <div class=\"reply-item\">\n <div class=\"comment-avatar small\">\n @if (reply.authorAvatar) {\n <img\n [src]=\"reply.authorAvatar\"\n [alt]=\"reply.author\"\n class=\"avatar-image\" />\n } @else {\n <div class=\"avatar-placeholder\">\n {{ getAuthorInitials(reply.author) }}\n </div>\n }\n </div>\n\n <div class=\"comment-content\">\n <div class=\"comment-header\">\n <span class=\"comment-author\">{{ reply.author }}</span>\n <span class=\"comment-timestamp\">{{ formatTimestamp(reply.timestamp) }}</span>\n </div>\n <p class=\"comment-text\">{{ reply.content }}</p>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Reply Composer -->\n @if (showReplyComposer && replyTarget?.id === comment.id) {\n <div class=\"reply-composer\">\n <textarea\n class=\"composer-input\"\n [placeholder]=\"'comments.write_reply' | translate\"\n rows=\"2\"\n [(ngModel)]=\"replyContent\"\n [attr.aria-label]=\"'comments.reply_label' | translate\"></textarea>\n <div class=\"composer-actions\">\n <button\n class=\"cancel-btn\"\n type=\"button\"\n (click)=\"cancelReply()\">\n {{ 'comments.cancel' | translate }}\n </button>\n <button\n class=\"post-btn\"\n type=\"button\"\n [disabled]=\"!replyContent.trim()\"\n (click)=\"submitReply()\">\n {{ 'comments.reply' | translate }}\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Load More -->\n @if (hasMore) {\n <div class=\"load-more\">\n @if (loading) {\n <div class=\"loading-spinner small\"></div>\n } @else {\n <button\n class=\"load-more-btn\"\n type=\"button\"\n (click)=\"onLoadMore()\">\n {{ 'comments.load_more' | translate }}\n </button>\n }\n </div>\n }\n }\n </div>\n\n <!-- Footer Info -->\n <div class=\"drawer-footer\">\n <p class=\"footer-text\">\n {{ comments.length }} {{ comments.length === 1 ? ('comments.comment_count_singular' | translate) : ('comments.comment_count_plural' | translate) }}\n @if (totalPages > 1) {\n <span> \u2022 {{ 'comments.page_of' | translate: {current: currentPage, total: totalPages} }}</span>\n }\n </p>\n </div>\n </div>\n </div>\n}\n", styles: [".drawer-overlay{position:fixed;inset:0;z-index:200;display:flex;align-items:stretch;justify-content:flex-end;background:#00000080;animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.drawer-container{width:90%;max-width:450px;background:#1a1a1a;box-shadow:-4px 0 16px #0000004d;display:flex;flex-direction:column;overflow:hidden;animation:slideInRight .3s cubic-bezier(.4,0,.2,1)}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}.drawer-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.drawer-title{font-size:20px;font-weight:600;color:#fff;margin:0}.close-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-size:20px;color:#ffffffb3;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.close-btn:hover{background:#ffffff1a;color:#fff}.comment-composer{padding:16px 24px;border-bottom:1px solid rgba(255,255,255,.1);flex-shrink:0}.composer-input{width:100%;padding:12px;font-size:14px;line-height:1.5;color:#fff;background:#ffffff0d;border:1px solid rgba(255,255,255,.2);border-radius:8px;outline:none;resize:vertical;font-family:inherit;transition:all .2s ease}.composer-input::placeholder{color:#fff6}.composer-input:focus{background:#ffffff14;border-color:#0078d7cc;box-shadow:0 0 0 3px #0078d733}.composer-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.post-btn{padding:8px 16px;font-size:13px;font-weight:500;color:#fff;background:#0078d7cc;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease}.post-btn:hover:not(:disabled){background:#0078d7e6;transform:translateY(-1px)}.post-btn:disabled{opacity:.5;cursor:not-allowed;transform:none}.cancel-btn{padding:8px 16px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease}.cancel-btn:hover{background:#ffffff26}.auth-notice{padding:16px 24px;background:#ffa5001a;border-bottom:1px solid rgba(255,165,0,.2);flex-shrink:0}.auth-notice p{font-size:13px;color:#fffc;margin:0;text-align:center}.comments-content{flex:1;overflow-y:auto;padding:16px 24px}.comments-content::-webkit-scrollbar{width:8px}.comments-content::-webkit-scrollbar-track{background:#ffffff0d}.comments-content::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.loading-state{display:flex;flex-direction:column;align-items:center;gap:16px;padding:60px 20px}.loading-spinner{width:40px;height:40px;border:3px solid rgba(255,255,255,.1);border-top-color:#fff;border-radius:50%;animation:spin 1s linear infinite}.loading-spinner.small{width:24px;height:24px;border-width:2px}@keyframes spin{to{transform:rotate(360deg)}}.loading-state p{font-size:14px;color:#ffffff80;margin:0}.empty-state{display:flex;flex-direction:column;align-items:center;gap:16px;padding:60px 20px;text-align:center}.empty-icon{font-size:48px}.empty-state p{font-size:14px;color:#ffffff80;margin:0}.comments-list{display:flex;flex-direction:column;gap:20px}.comment-item{display:flex;gap:12px}.comment-avatar{width:40px;height:40px;flex-shrink:0;border-radius:50%;overflow:hidden}.comment-avatar.small{width:32px;height:32px}.avatar-image{width:100%;height:100%;object-fit:cover;display:block}.avatar-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:600;color:#ffffffe6;background:linear-gradient(135deg,#0078d74d,#00b4784d)}.comment-avatar.small .avatar-placeholder{font-size:12px}.comment-content{flex:1;min-width:0}.comment-header{display:flex;align-items:baseline;gap:8px;margin-bottom:6px}.comment-author{font-size:14px;font-weight:600;color:#ffffffe6}.comment-timestamp{font-size:12px;color:#ffffff80}.comment-text{font-size:14px;line-height:1.5;color:#fffc;margin:0 0 8px;word-wrap:break-word}.comment-actions{display:flex;gap:12px;margin-top:8px}.action-btn{display:flex;align-items:center;gap:4px;padding:4px 8px;font-size:12px;font-weight:500;color:#fff9;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:all .2s ease}.action-btn:hover{color:#ffffffe6;background:#ffffff1a}.action-btn.active{color:#ff4d6d}.comment-replies{margin-top:12px;padding-left:12px;border-left:2px solid rgba(255,255,255,.1);display:flex;flex-direction:column;gap:16px}.reply-item{display:flex;gap:10px}.reply-composer{margin-top:12px;padding-top:12px;border-top:1px solid rgba(255,255,255,.1)}.load-more{display:flex;justify-content:center;padding:16px 0;margin-top:16px;border-top:1px solid rgba(255,255,255,.1)}.load-more-btn{padding:10px 20px;font-size:13px;font-weight:500;color:#ffffffe6;background:#ffffff1a;border:1px solid rgba(255,255,255,.2);border-radius:6px;cursor:pointer;transition:all .2s ease}.load-more-btn:hover{background:#ffffff26;border-color:#ffffff4d}.drawer-footer{padding:12px 24px;border-top:1px solid rgba(255,255,255,.1);flex-shrink:0}.footer-text{font-size:12px;color:#ffffff80;margin:0;text-align:center}@media (max-width: 768px){.drawer-container{width:100%;max-width:none}.drawer-header{padding:16px 20px}.drawer-title{font-size:18px}.comment-composer,.auth-notice,.comments-content{padding:14px 20px}.comment-avatar{width:36px;height:36px}.comment-avatar.small{width:28px;height:28px}.avatar-placeholder{font-size:13px}.comment-avatar.small .avatar-placeholder{font-size:11px}}.close-btn:focus-visible,.composer-input:focus-visible,.post-btn:focus-visible,.cancel-btn:focus-visible,.action-btn:focus-visible,.load-more-btn:focus-visible{outline:2px solid rgba(0,120,215,.8);outline-offset:2px}@media (prefers-reduced-motion: reduce){.drawer-overlay,.drawer-container,.loading-spinner,.close-btn,.post-btn,.cancel-btn,.action-btn,.load-more-btn{animation:none;transition:none}}\n"] }]
|
|
16565
16565
|
}], propDecorators: { comments: [{
|
|
16566
16566
|
type: Input
|
|
16567
16567
|
}], visible: [{
|
|
@@ -18954,18 +18954,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
18954
18954
|
|
|
18955
18955
|
class PlayerComponent {
|
|
18956
18956
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: PlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
18957
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: PlayerComponent, isStandalone: true, selector: "pw-player", ngImport: i0, template: `
|
|
18958
|
-
<p>
|
|
18959
|
-
player works!
|
|
18960
|
-
</p>
|
|
18957
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: PlayerComponent, isStandalone: true, selector: "pw-player", ngImport: i0, template: `
|
|
18958
|
+
<p>
|
|
18959
|
+
player works!
|
|
18960
|
+
</p>
|
|
18961
18961
|
`, isInline: true, styles: [""] }); }
|
|
18962
18962
|
}
|
|
18963
18963
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: PlayerComponent, decorators: [{
|
|
18964
18964
|
type: Component,
|
|
18965
|
-
args: [{ selector: 'pw-player', imports: [], template: `
|
|
18966
|
-
<p>
|
|
18967
|
-
player works!
|
|
18968
|
-
</p>
|
|
18965
|
+
args: [{ selector: 'pw-player', imports: [], template: `
|
|
18966
|
+
<p>
|
|
18967
|
+
player works!
|
|
18968
|
+
</p>
|
|
18969
18969
|
` }]
|
|
18970
18970
|
}] });
|
|
18971
18971
|
|