Preview preload pages count
This commit is contained in:
@ -1,74 +1,77 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onDestroy, tick } from 'svelte';
|
import { onDestroy, tick } from 'svelte';
|
||||||
import SelectionPage from './preview/SelectionPage.svelte';
|
import SelectionPage from './preview/SelectionPage.svelte';
|
||||||
import PreviewPage from './preview/PreviewPage.svelte';
|
import PreviewPage from './preview/PreviewPage.svelte';
|
||||||
|
|
||||||
let { onBack = () => {}, onPrint = () => {} } = $props();
|
let { onBack = () => {}, onPrint = () => {} } = $props();
|
||||||
|
|
||||||
// ── Модель данных ──
|
// ── Модель данных ──
|
||||||
/** @typedef {{ id: number, file: File, previewUrl: string | null, fileType: 'image' | 'pdf' | 'other', expanded: boolean, format: 'A4' | 'A5', colorMode: 'bw' | 'color', qualityIndex: number, copies: number, pageRange: string, totalPages: number, selectedPages: Set<number>, pdfDoc: any }} FileEntry */
|
/** @typedef {{ id: number, file: File, previewUrl: string | null, fileType: 'image' | 'pdf' | 'other', expanded: boolean, format: 'A4' | 'A5', colorMode: 'bw' | 'color', qualityIndex: number, copies: number, pageRange: string, totalPages: number, selectedPages: Set<number>, pdfDoc: any }} FileEntry */
|
||||||
/** @type {FileEntry[]} */
|
/** @type {FileEntry[]} */
|
||||||
let files = $state([]);
|
let files = $state([]);
|
||||||
|
|
||||||
// ── Общие состояния оверлея ──
|
// ── Общие состояния оверлея ──
|
||||||
/** @type {FileEntry | null} */
|
/** @type {FileEntry | null} */
|
||||||
let activeFile = $state(null);
|
let activeFile = $state(null);
|
||||||
|
/** Режим просмотра: 'gallery' (выбор страниц) или 'preview' (чтение) */
|
||||||
|
let viewMode = $state(/** @type {'gallery' | 'preview'} */ ('gallery'));
|
||||||
|
|
||||||
/** Режим просмотра: 'gallery' (выбор страниц) или 'preview' (чтение) */
|
// ── Данные для галереи (выбор страниц) ──
|
||||||
let viewMode = $state(/** @type {'gallery' | 'preview'} */ ('gallery'));
|
let galleryThumbnails = $state([]);
|
||||||
|
let galleryLoading = $state(false);
|
||||||
|
|
||||||
// ── Данные для галереи (выбор страниц) ──
|
// ── Данные для превью (чтение) ──
|
||||||
let galleryThumbnails = $state([]);
|
/** Кэшированные URL больших страниц для превью */
|
||||||
let galleryLoading = $state(false);
|
let previewPagesCache = $state({});
|
||||||
|
let isPreviewRendering = $state(false);
|
||||||
|
|
||||||
// ── Данные для превью (чтение) ──
|
let uid = 0;
|
||||||
/** Кэшированные URL больших страниц для превью */
|
|
||||||
let previewPagesCache = $state({});
|
|
||||||
let isPreviewRendering = $state(false);
|
|
||||||
|
|
||||||
let uid = 0;
|
const qualities = [
|
||||||
|
|
||||||
const qualities = [
|
|
||||||
{ id: 'low', label: 'Эконом' },
|
{ id: 'low', label: 'Эконом' },
|
||||||
{ id: 'medium', label: 'Стандарт' },
|
{ id: 'medium', label: 'Стандарт' },
|
||||||
{ id: 'high', label: 'Максимум' }
|
{ id: 'high', label: 'Максимум' }
|
||||||
];
|
];
|
||||||
const formats = ['A4', 'A5'];
|
const formats = ['A4', 'A5'];
|
||||||
const colorModes = [
|
const colorModes = [
|
||||||
{ id: 'bw', label: 'Чб' },
|
{ id: 'bw', label: 'Чб' },
|
||||||
{ id: 'color', label: 'Цвет' }
|
{ id: 'color', label: 'Цвет' }
|
||||||
];
|
];
|
||||||
|
|
||||||
// ── PDF.js Core (Остается в родителе для управления памятью) ──
|
// ── PDF.js Core ──
|
||||||
async function loadPdfDocument(entry) {
|
async function loadPdfDocument(entry) {
|
||||||
if (entry.pdfDoc) return entry.pdfDoc;
|
if (entry.pdfDoc) return entry.pdfDoc;
|
||||||
try {
|
try {
|
||||||
const pdfjsLib = await import('https://mozilla.github.io/pdf.js/build/pdf.mjs');
|
const pdfjsLib = await import('https://mozilla.github.io/pdf.js/build/pdf.mjs');
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://mozilla.github.io/pdf.js/build/pdf.worker.mjs';
|
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://mozilla.github.io/pdf.js/build/pdf.worker.mjs';
|
||||||
|
|
||||||
const arrayBuffer = await entry.file.arrayBuffer();
|
const arrayBuffer = await entry.file.arrayBuffer();
|
||||||
const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer });
|
const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer });
|
||||||
const pdf = await loadingTask.promise;
|
const pdf = await loadingTask.promise;
|
||||||
|
|
||||||
entry.pdfDoc = pdf;
|
entry.pdfDoc = pdf;
|
||||||
entry.totalPages = pdf.numPages;
|
entry.totalPages = pdf.numPages;
|
||||||
|
// Инициализируем выбранные страницы только если они еще не заданы
|
||||||
|
if (!entry.selectedPages || entry.selectedPages.size === 0) {
|
||||||
entry.selectedPages = new Set(Array.from({ length: pdf.numPages }, (_, i) => i + 1));
|
entry.selectedPages = new Set(Array.from({ length: pdf.numPages }, (_, i) => i + 1));
|
||||||
|
}
|
||||||
return pdf;
|
return pdf;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('PDF load error:', err);
|
console.error('PDF load error:', err);
|
||||||
entry.totalPages = 0;
|
entry.totalPages = 0;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderPageToImage(pdfDoc, pageNum, scale = 1.0) {
|
async function renderPageToImage(pdfDoc, pageNum, scale = 1.0) {
|
||||||
const page = await pdfDoc.getPage(pageNum);
|
const page = await pdfDoc.getPage(pageNum);
|
||||||
const viewport = page.getViewport({ scale });
|
const viewport = page.getViewport({ scale });
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
const context = canvas.getContext('2d');
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
const outputScale = window.devicePixelRatio || 1;
|
const outputScale = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
canvas.width = Math.floor(viewport.width * outputScale);
|
canvas.width = Math.floor(viewport.width * outputScale);
|
||||||
canvas.height = Math.floor(viewport.height * outputScale);
|
canvas.height = Math.floor(viewport.height * outputScale);
|
||||||
|
|
||||||
canvas.style.width = `${Math.floor(viewport.width)}px`;
|
canvas.style.width = `${Math.floor(viewport.width)}px`;
|
||||||
canvas.style.height = `${Math.floor(viewport.height)}px`;
|
canvas.style.height = `${Math.floor(viewport.height)}px`;
|
||||||
|
|
||||||
@ -81,10 +84,10 @@
|
|||||||
}).promise;
|
}).promise;
|
||||||
|
|
||||||
return canvas.toDataURL('image/jpeg', 0.85);
|
return canvas.toDataURL('image/jpeg', 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Логика Галереи ──
|
// ── Логика Галереи ──
|
||||||
async function openPageGallery(entry) {
|
async function openPageGallery(entry) {
|
||||||
activeFile = entry;
|
activeFile = entry;
|
||||||
viewMode = 'gallery';
|
viewMode = 'gallery';
|
||||||
galleryThumbnails = [];
|
galleryThumbnails = [];
|
||||||
@ -92,6 +95,8 @@
|
|||||||
previewPagesCache = {};
|
previewPagesCache = {};
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
|
// Используем уже загруженный документ или загружаем при необходимости
|
||||||
if (!entry.pdfDoc) await loadPdfDocument(entry);
|
if (!entry.pdfDoc) await loadPdfDocument(entry);
|
||||||
|
|
||||||
if (!entry.pdfDoc || entry.totalPages === 0) {
|
if (!entry.pdfDoc || entry.totalPages === 0) {
|
||||||
@ -111,10 +116,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
galleryLoading = false;
|
galleryLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Логика Превью ──
|
// ── Логика Превью ──
|
||||||
async function openPreview(entry) {
|
async function openPreview(entry) {
|
||||||
activeFile = entry;
|
activeFile = entry;
|
||||||
viewMode = 'preview';
|
viewMode = 'preview';
|
||||||
previewPagesCache = {};
|
previewPagesCache = {};
|
||||||
@ -123,17 +128,22 @@
|
|||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
if (entry.fileType === 'pdf') {
|
if (entry.fileType === 'pdf') {
|
||||||
|
// Документ должен быть уже загружен при добавлении файла,
|
||||||
|
// но на всякий случай проверяем
|
||||||
if (!entry.pdfDoc) await loadPdfDocument(entry);
|
if (!entry.pdfDoc) await loadPdfDocument(entry);
|
||||||
|
|
||||||
|
// Берем totalPages из entry, которое было получено при добавлении файла
|
||||||
|
if (entry.totalPages > 0) {
|
||||||
preloadPreviewPages(entry, 1, 5);
|
preloadPreviewPages(entry, 1, 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function preloadPreviewPages(entry, startPage, count, direction = 'forward') {
|
async function preloadPreviewPages(entry, startPage, count, direction = 'forward') {
|
||||||
if (!entry.pdfDoc || isPreviewRendering) return;
|
if (!entry.pdfDoc || isPreviewRendering) return;
|
||||||
isPreviewRendering = true;
|
isPreviewRendering = true;
|
||||||
|
|
||||||
let pagesToLoad = [];
|
let pagesToLoad = [];
|
||||||
|
|
||||||
if (direction === 'around') {
|
if (direction === 'around') {
|
||||||
const half = Math.floor(count / 2);
|
const half = Math.floor(count / 2);
|
||||||
const from = Math.max(1, startPage - half);
|
const from = Math.max(1, startPage - half);
|
||||||
@ -148,7 +158,6 @@
|
|||||||
|
|
||||||
if (pagesToLoad.length > 0) {
|
if (pagesToLoad.length > 0) {
|
||||||
const newCacheEntries = {};
|
const newCacheEntries = {};
|
||||||
|
|
||||||
for (const pageNum of pagesToLoad) {
|
for (const pageNum of pagesToLoad) {
|
||||||
try {
|
try {
|
||||||
const url = await renderPageToImage(entry.pdfDoc, pageNum, 1.5);
|
const url = await renderPageToImage(entry.pdfDoc, pageNum, 1.5);
|
||||||
@ -157,29 +166,26 @@
|
|||||||
console.error(`Failed to render preview page ${pageNum}`, e);
|
console.error(`Failed to render preview page ${pageNum}`, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(newCacheEntries).length > 0) {
|
if (Object.keys(newCacheEntries).length > 0) {
|
||||||
previewPagesCache = { ...previewPagesCache, ...newCacheEntries };
|
previewPagesCache = { ...previewPagesCache, ...newCacheEntries };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isPreviewRendering = false;
|
isPreviewRendering = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработчик запроса страниц от дочернего PreviewPage
|
function handlePreviewRequestPages(start, count, direction) {
|
||||||
function handlePreviewRequestPages(start, count, direction) {
|
|
||||||
if (activeFile) preloadPreviewPages(activeFile, start, count, direction);
|
if (activeFile) preloadPreviewPages(activeFile, start, count, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Общие функции закрытия ──
|
// ── Общие функции закрытия ──
|
||||||
function closeOverlay() {
|
function closeOverlay() {
|
||||||
activeFile = null;
|
activeFile = null;
|
||||||
galleryThumbnails = [];
|
galleryThumbnails = [];
|
||||||
previewPagesCache = {};
|
previewPagesCache = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Управление выбором страниц ──
|
// ── Управление выбором страниц ──
|
||||||
function togglePageSelection(pageNum) {
|
function togglePageSelection(pageNum) {
|
||||||
if (!activeFile?.selectedPages) return;
|
if (!activeFile?.selectedPages) return;
|
||||||
const newSet = new Set(activeFile.selectedPages);
|
const newSet = new Set(activeFile.selectedPages);
|
||||||
if (newSet.has(pageNum)) {
|
if (newSet.has(pageNum)) {
|
||||||
@ -188,37 +194,37 @@
|
|||||||
newSet.add(pageNum);
|
newSet.add(pageNum);
|
||||||
}
|
}
|
||||||
activeFile.selectedPages = newSet;
|
activeFile.selectedPages = newSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectAllPages() {
|
function selectAllPages() {
|
||||||
if (!activeFile?.totalPages) return;
|
if (!activeFile?.totalPages) return;
|
||||||
activeFile.selectedPages = new Set(Array.from({ length: activeFile.totalPages }, (_, i) => i + 1));
|
activeFile.selectedPages = new Set(Array.from({ length: activeFile.totalPages }, (_, i) => i + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
function deselectAllPages() {
|
function deselectAllPages() {
|
||||||
if (!activeFile) return;
|
if (!activeFile) return;
|
||||||
activeFile.selectedPages = new Set([1]);
|
activeFile.selectedPages = new Set([1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pagesLabel(entry) {
|
function pagesLabel(entry) {
|
||||||
if (!entry.totalPages || entry.totalPages === 0) return 'Все';
|
if (!entry.totalPages || entry.totalPages === 0) return 'Все';
|
||||||
if (!entry.selectedPages || entry.selectedPages.size === 0) return 'Все';
|
if (!entry.selectedPages || entry.selectedPages.size === 0) return 'Все';
|
||||||
if (entry.selectedPages.size === entry.totalPages) return 'Все';
|
if (entry.selectedPages.size === entry.totalPages) return 'Все';
|
||||||
if (entry.selectedPages.size === 1) return `стр. ${[...entry.selectedPages][0]}`;
|
if (entry.selectedPages.size === 1) return `стр. ${[...entry.selectedPages][0]}`;
|
||||||
return `${entry.selectedPages.size} из ${entry.totalPages}`;
|
return `${entry.selectedPages.size} из ${entry.totalPages}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Файловые операции ──
|
// ── Файловые операции ──
|
||||||
let fileInput;
|
let fileInput;
|
||||||
|
|
||||||
function detectFileType(file) {
|
function detectFileType(file) {
|
||||||
if (!file) return 'other';
|
if (!file) return 'other';
|
||||||
if (file.type.startsWith('image/')) return 'image';
|
if (file.type.startsWith('image/')) return 'image';
|
||||||
if (file.type === 'application/pdf') return 'pdf';
|
if (file.type === 'application/pdf') return 'pdf';
|
||||||
return 'other';
|
return 'other';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFileSelect(event) {
|
async function handleFileSelect(event) {
|
||||||
const input = /** @type {HTMLInputElement} */(event.target);
|
const input = /** @type {HTMLInputElement} */(event.target);
|
||||||
const chosen = input.files;
|
const chosen = input.files;
|
||||||
if (!chosen || chosen.length === 0) return;
|
if (!chosen || chosen.length === 0) return;
|
||||||
@ -240,56 +246,62 @@
|
|||||||
selectedPages: fType === 'image' ? new Set([1]) : new Set(),
|
selectedPages: fType === 'image' ? new Set([1]) : new Set(),
|
||||||
pdfDoc: null,
|
pdfDoc: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
files.push(entry);
|
files.push(entry);
|
||||||
if (fType === 'pdf') loadPdfDocument(entry);
|
|
||||||
|
// ВАЖНО: Загружаем PDF сразу при добавлении, чтобы сохранить totalPages
|
||||||
|
if (fType === 'pdf') {
|
||||||
|
loadPdfDocument(entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
input.value = '';
|
input.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerFileInput(event) {
|
function triggerFileInput(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
fileInput.click();
|
fileInput.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleExpanded(file) { file.expanded = !file.expanded; }
|
function toggleExpanded(file) { file.expanded = !file.expanded; }
|
||||||
function removeFile(file) {
|
|
||||||
|
function removeFile(file) {
|
||||||
if (file.previewUrl) URL.revokeObjectURL(file.previewUrl);
|
if (file.previewUrl) URL.revokeObjectURL(file.previewUrl);
|
||||||
files = files.filter((f) => f.id !== file.id);
|
files = files.filter((f) => f.id !== file.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Опции печати ──
|
// ── Опции печати ──
|
||||||
function toggleFormat(file, f) { file.format = f; }
|
function toggleFormat(file, f) { file.format = f; }
|
||||||
function toggleColor(file, c) { file.colorMode = c.id; }
|
function toggleColor(file, c) { file.colorMode = c.id; }
|
||||||
function incrementCopies(file) { file.copies += 1; }
|
function incrementCopies(file) { file.copies += 1; }
|
||||||
function decrementCopies(file) { if (file.copies > 1) file.copies -= 1; }
|
function decrementCopies(file) { if (file.copies > 1) file.copies -= 1; }
|
||||||
function qualityFillFor(file) { return Math.round((file.qualityIndex / (qualities.length - 1)) * 100); }
|
function qualityFillFor(file) { return Math.round((file.qualityIndex / (qualities.length - 1)) * 100); }
|
||||||
function pageStubFor() { return 0; }
|
function pageStubFor() { return 0; }
|
||||||
|
|
||||||
const totalPrice = $derived(`${files.reduce((sum, f) => sum + pageStubFor(f), 0)}₽`);
|
const totalPrice = $derived(`${files.reduce((sum, f) => sum + pageStubFor(f), 0)}₽`);
|
||||||
const extraCopies = $derived(files.reduce((sum, f) => sum + (f.copies - 1), 0));
|
const extraCopies = $derived(files.reduce((sum, f) => sum + (f.copies - 1), 0));
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
files.forEach((f) => { if (f.previewUrl) URL.revokeObjectURL(f.previewUrl); });
|
files.forEach((f) => { if (f.previewUrl) URL.revokeObjectURL(f.previewUrl); });
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Плюрализация ──
|
// ── Плюрализация ──
|
||||||
function pluralize(n, forms) {
|
function pluralize(n, forms) {
|
||||||
const mod10 = n % 10, mod100 = n % 100;
|
const mod10 = n % 10, mod100 = n % 100;
|
||||||
if (mod10 === 1 && mod100 !== 11) return forms[0];
|
if (mod10 === 1 && mod100 !== 11) return forms[0];
|
||||||
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 > 14)) return forms[1];
|
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 > 14)) return forms[1];
|
||||||
return forms[2];
|
return forms[2];
|
||||||
}
|
}
|
||||||
const plFiles = (n) => pluralize(n, ['файл', 'файла', 'файлов']);
|
const plFiles = (n) => pluralize(n, ['файл', 'файла', 'файлов']);
|
||||||
const plCopies = (n) => pluralize(n, ['копия', 'копии', 'копий']);
|
const plCopies = (n) => pluralize(n, ['копия', 'копии', 'копий']);
|
||||||
|
|
||||||
function handlePagesClick(file) {
|
function handlePagesClick(file) {
|
||||||
if (file.fileType === 'pdf') openPageGallery(file);
|
if (file.fileType === 'pdf') openPageGallery(file);
|
||||||
else openPreview(file);
|
else openPreview(file);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !activeFile}
|
{#if !activeFile}
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<button class="back-btn" onclick={onBack}>← Назад</button>
|
<button class="back-btn" onclick={onBack}>← Назад</button>
|
||||||
<h1>Печать</h1>
|
<h1>Печать</h1>
|
||||||
@ -363,6 +375,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<button type="button" class="add-file-btn" onclick={triggerFileInput}>+ Добавить файлы</button>
|
<button type="button" class="add-file-btn" onclick={triggerFileInput}>+ Добавить файлы</button>
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
@ -381,7 +394,7 @@
|
|||||||
<button type="button" class="action-btn secondary" onclick={onPrint}>Печать</button>
|
<button type="button" class="action-btn secondary" onclick={onPrint}>Печать</button>
|
||||||
<input type="file" bind:this={fileInput} accept="image/*,.pdf" multiple onchange={handleFileSelect} style="display:none" />
|
<input type="file" bind:this={fileInput} accept="image/*,.pdf" multiple onchange={handleFileSelect} style="display:none" />
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
@ -389,9 +402,7 @@
|
|||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
{#if activeFile}
|
{#if activeFile}
|
||||||
<div class="overlay-backdrop" onclick={closeOverlay} role="dialog" aria-modal="true">
|
<div class="overlay-backdrop" onclick={closeOverlay} role="dialog" aria-modal="true">
|
||||||
<!-- stopPropagation чтобы клики внутри окна не закрывали его -->
|
|
||||||
<div class="overlay-window-wrapper" onclick={(e) => e.stopPropagation()}>
|
<div class="overlay-window-wrapper" onclick={(e) => e.stopPropagation()}>
|
||||||
|
|
||||||
{#if viewMode === 'gallery'}
|
{#if viewMode === 'gallery'}
|
||||||
<SelectionPage
|
<SelectionPage
|
||||||
file={activeFile}
|
file={activeFile}
|
||||||
@ -411,13 +422,11 @@
|
|||||||
onClose={closeOverlay}
|
onClose={closeOverlay}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Стили основного меню остались без изменений */
|
|
||||||
.page-container { width: 100%; min-height: 100dvh; display: flex; flex-direction: column; background: radial-gradient(120% 60% at 50% -10%, rgba(99, 102, 241, 0.18) 0%, transparent 60%); font-family: system-ui, -apple-system, sans-serif; color: #f5f7fa; }
|
.page-container { width: 100%; min-height: 100dvh; display: flex; flex-direction: column; background: radial-gradient(120% 60% at 50% -10%, rgba(99, 102, 241, 0.18) 0%, transparent 60%); font-family: system-ui, -apple-system, sans-serif; color: #f5f7fa; }
|
||||||
.header { padding: 24px 20px 8px; }
|
.header { padding: 24px 20px 8px; }
|
||||||
.back-btn { background: none; border: none; color: #6366f1; font-size: 15px; font-weight: 600; cursor: pointer; padding: 8px 4px; margin-bottom: 12px; }
|
.back-btn { background: none; border: none; color: #6366f1; font-size: 15px; font-weight: 600; cursor: pointer; padding: 8px 4px; margin-bottom: 12px; }
|
||||||
@ -464,25 +473,22 @@
|
|||||||
.summary-itogo { font-size: 30px; font-weight: 700; color: #8b93a1; }
|
.summary-itogo { font-size: 30px; font-weight: 700; color: #8b93a1; }
|
||||||
.summary-price { font-size: 38px; font-weight: 800; color: #fff; }
|
.summary-price { font-size: 38px; font-weight: 800; color: #fff; }
|
||||||
|
|
||||||
/* Обертка оверлея */
|
|
||||||
.overlay-backdrop {
|
.overlay-backdrop {
|
||||||
position: fixed; inset: 0; z-index: 1000;
|
position: fixed; inset: 0; z-index: 1000;
|
||||||
background: rgba(10, 12, 18, 0.92); backdrop-filter: blur(12px);
|
background: rgba(10, 12, 18, 0.92); backdrop-filter: blur(12px);
|
||||||
display: flex; align-items: center; justify-content: center; padding: 0;
|
display: flex; align-items: center; justify-content: center; padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-window-wrapper {
|
.overlay-window-wrapper {
|
||||||
width: 100%; height: 100%;
|
width: 100%; height: 100%;
|
||||||
display: flex; flex-direction: column;
|
display: flex; flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.overlay-window-wrapper {
|
.overlay-window-wrapper {
|
||||||
max-width: 800px; max-height: 90vh;
|
max-width: 800px; max-height: 90vh;
|
||||||
border-radius: 20px; height: auto;
|
border-radius: 20px; height: auto;
|
||||||
border: 1px solid rgba(255,255,255,0.1);
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
background: #13151b; /* Fallback bg for wrapper on desktop */
|
background: #13151b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user