diff --git a/desktop-app/resources/js/preview-worker.js b/desktop-app/resources/js/preview-worker.js
index ca19372..5f412d0 100644
--- a/desktop-app/resources/js/preview-worker.js
+++ b/desktop-app/resources/js/preview-worker.js
@@ -343,7 +343,13 @@ function configureMarked() {
function ensureLibraries(urls) {
if (!librariesLoaded) {
- importScripts(urls.marked, urls.highlight);
+ const scripts = [];
+ if (urls.marked) scripts.push(urls.marked);
+ if (urls.highlight) scripts.push(urls.highlight);
+ if (urls.purify) scripts.push(urls.purify);
+ if (scripts.length > 0) {
+ importScripts(...scripts);
+ }
librariesLoaded = true;
}
configureMarked();
@@ -461,6 +467,34 @@ function renderSegmentedMarkdown(markdown, options) {
self.onmessage = function(event) {
const data = event.data || {};
+ if (data.type === "render-full") {
+ try {
+ const options = data.options || {};
+ ensureLibraries(options.libraryUrls || {});
+ mermaidIdCounter = 0;
+ const html = marked.parse(data.markdown || "");
+ let sanitized = html;
+ if (typeof DOMPurify !== "undefined") {
+ sanitized = DOMPurify.sanitize(html, {
+ ADD_TAGS: ['mjx-container', 'svg', 'path', 'g', 'marker', 'defs', 'pattern', 'clipPath', 'input'],
+ ADD_ATTR: ['id', 'class', 'style', 'align', 'viewBox', 'd', 'fill', 'stroke', 'transform', 'marker-end', 'marker-start', 'type', 'checked', 'disabled', 'data-original-code']
+ });
+ }
+ self.postMessage({
+ type: "render-full-result",
+ requestId: data.requestId,
+ html: sanitized
+ });
+ } catch (error) {
+ self.postMessage({
+ type: "render-full-error",
+ requestId: data.requestId,
+ error: error && error.message ? error.message : "Full worker render failed."
+ });
+ }
+ return;
+ }
+
if (data.type !== "render") return;
try {
diff --git a/desktop-app/resources/js/script.js b/desktop-app/resources/js/script.js
index ee7fc2c..2908e65 100644
--- a/desktop-app/resources/js/script.js
+++ b/desktop-app/resources/js/script.js
@@ -551,6 +551,11 @@ document.addEventListener("DOMContentLoaded", function () {
if (data.type === "render-result") {
previewWorkerFailureCount = 0;
pending.resolve(data.result);
+ } else if (data.type === "render-full-result") {
+ previewWorkerFailureCount = 0;
+ pending.resolve(data.html);
+ } else if (data.type === "render-full-error") {
+ pending.reject(new Error(data.error || "Preview worker render-full failed."));
} else {
recordPreviewWorkerRenderFailure();
pending.reject(new Error(data.error || "Preview worker render failed."));
@@ -594,6 +599,54 @@ document.addEventListener("DOMContentLoaded", function () {
});
}
+ function requestFullWorkerRender(rawVal) {
+ const worker = getPreviewWorker();
+ if (!worker) return Promise.reject(new Error("Worker unavailable"));
+
+ const requestId = ++previewWorkerRequestCounter;
+ return new Promise(function(resolve, reject) {
+ const timeoutId = setTimeout(function() {
+ previewWorkerRequests.delete(requestId);
+ reject(new Error("Full render worker timeout."));
+ }, 30000); // 30s timeout for full document render
+
+ previewWorkerRequests.set(requestId, { resolve, reject, timeoutId });
+
+ try {
+ worker.postMessage({
+ type: "render-full",
+ requestId: requestId,
+ markdown: rawVal,
+ options: {
+ libraryUrls: getPreviewWorkerLibraryUrls()
+ },
+ });
+ } catch (e) {
+ previewWorkerRequests.delete(requestId);
+ clearTimeout(timeoutId);
+ reject(e);
+ }
+ });
+ }
+
+ async function parseMarkdownFull(markdown) {
+ try {
+ const html = await requestFullWorkerRender(markdown);
+ return html;
+ } catch (e) {
+ console.warn("Full worker render failed, falling back to main thread:", e);
+ const { frontmatter, body } = parseFrontmatter(markdown);
+ const tableHtml = frontmatter ? renderFrontmatterTable(frontmatter) : '';
+ const referenceData = extractReferenceDefinitions(body);
+ const parsedHtml = tableHtml + marked.parse(referenceData.cleanedMarkdown);
+ const sanitizedHtml = DOMPurify.sanitize(parsedHtml, {
+ ADD_TAGS: ['mjx-container', 'svg', 'path', 'g', 'marker', 'defs', 'pattern', 'clipPath', 'input'],
+ ADD_ATTR: ['id', 'class', 'style', 'align', 'viewBox', 'd', 'fill', 'stroke', 'transform', 'marker-end', 'marker-start', 'type', 'checked', 'disabled', 'data-original-code']
+ });
+ return sanitizedHtml;
+ }
+ }
+
function parseInlineWithoutFootnotes(text) {
suppressFootnotePreprocess = true;
try {
@@ -7458,195 +7511,411 @@ document.addEventListener("DOMContentLoaded", function () {
// End Oversized Graphics Scaling Functions
// ============================================
- exportPdf.addEventListener("click", async function (event) {
- event.preventDefault();
- if (activePdfExport) return;
+ // ============================================
+ // New Modular PDF Export Engine
+ // ============================================
+ const PdfExportEngine = {
+ ExportDocumentBuilder: {
+ build: async function(markdown, state) {
+ updatePdfProgress(state, 15, "Parsing markdown");
+ await waitForPdfFrame(state);
+
+ const sanitizedHtml = await parseMarkdownFull(markdown);
+ throwIfPdfExportAborted(state.signal);
- const progressState = createPdfProgressState();
- activePdfExport = progressState;
- setPdfExportTriggersBusy(progressState, true);
- document.body.appendChild(progressState.overlay);
- updatePdfProgress(progressState, 3, "Starting");
- progressState.overlay.querySelector(".pdf-progress-cancel")?.focus();
+ updatePdfProgress(state, 24, "Preparing document");
+ await waitForPdfFrame(state);
+
+ const tempElement = document.createElement("div");
+ state.tempElement = tempElement;
+ tempElement.className = "markdown-body pdf-export";
+ tempElement.innerHTML = sanitizedHtml;
+ enhanceGitHubAlerts(tempElement);
+ tempElement.style.padding = "20px";
+ tempElement.style.width = "210mm";
+ tempElement.style.margin = "0 auto";
+ tempElement.style.fontSize = "14px";
+ tempElement.style.position = "fixed";
+ tempElement.style.left = "-9999px";
+ tempElement.style.top = "0";
+
+ const currentTheme = document.documentElement.getAttribute("data-theme");
+ tempElement.style.backgroundColor = currentTheme === "dark" ? "#0d1117" : "#ffffff";
+ tempElement.style.color = currentTheme === "dark" ? "#c9d1d9" : "#24292e";
+
+ document.body.appendChild(tempElement);
+ await waitForPdfFrame(state);
+
+ await PdfExportEngine.AssetReadinessGate.awaitReady(tempElement, markdown, state);
+ throwIfPdfExportAborted(state.signal);
+
+ const pageHeightPx = tempElement.offsetWidth * (PAGE_CONFIG.contentHeight / PAGE_CONFIG.contentWidth);
+ tempElement.querySelectorAll("pre").forEach(pre => {
+ if (pre.offsetHeight > pageHeightPx) {
+ pre.classList.add("oversized");
+ }
+ });
- try {
- // PERF-002: Lazy-load PDF libraries on first export
- if (typeof jspdf === 'undefined' || typeof html2canvas === 'undefined') {
- updatePdfProgress(progressState, 8, "Loading PDF libraries");
- await runPdfAbortable(progressState, Promise.all([loadScript(CDN.jspdf), loadScript(CDN.html2canvas)]));
- throwIfPdfExportAborted(progressState.signal);
+ const parentStyles = Array.from(document.querySelectorAll('link[rel="stylesheet"], style'))
+ .map(el => el.outerHTML)
+ .join("\n");
+
+ const fullHtml = `
+
+
+
+
Export Document
+ ${parentStyles}
+
+
+
+ ${tempElement.innerHTML}
+
+
+`;
+
+ return { fullHtml, tempElement };
}
+ },
- updatePdfProgress(progressState, 15, "Parsing markdown");
- await waitForPdfFrame(progressState);
- const markdown = markdownEditor.value;
- const html = marked.parse(markdown);
- const sanitizedHtml = DOMPurify.sanitize(html, {
- ADD_TAGS: ['mjx-container', 'svg', 'path', 'g', 'marker', 'defs', 'pattern', 'clipPath', 'input'],
- ADD_ATTR: ['id', 'class', 'style', 'align', 'viewBox', 'd', 'fill', 'stroke', 'transform', 'marker-end', 'marker-start', 'type', 'checked', 'disabled', 'data-original-code']
- });
- throwIfPdfExportAborted(progressState.signal);
-
- updatePdfProgress(progressState, 24, "Preparing document");
- await waitForPdfFrame(progressState);
- const tempElement = document.createElement("div");
- progressState.tempElement = tempElement;
- tempElement.className = "markdown-body pdf-export";
- tempElement.innerHTML = sanitizedHtml;
- enhanceGitHubAlerts(tempElement);
- tempElement.style.padding = "20px";
- tempElement.style.width = "210mm";
- tempElement.style.margin = "0 auto";
- tempElement.style.fontSize = "14px";
- tempElement.style.position = "fixed";
- tempElement.style.left = "-9999px";
- tempElement.style.top = "0";
-
- const currentTheme = document.documentElement.getAttribute("data-theme");
- tempElement.style.backgroundColor = currentTheme === "dark" ? "#0d1117" : "#ffffff";
- tempElement.style.color = currentTheme === "dark" ? "#c9d1d9" : "#24292e";
-
- document.body.appendChild(tempElement);
- await waitForPdfFrame(progressState);
-
- const mermaidNodes = tempElement.querySelectorAll('.mermaid');
- if (mermaidNodes.length > 0) {
- updatePdfProgress(progressState, 34, "Rendering diagrams");
- try {
- if (typeof mermaid === 'undefined') {
- await runPdfAbortable(progressState, loadScript(CDN.mermaid));
+ AssetReadinessGate: {
+ awaitReady: async function(tempElement, markdown, state) {
+ if (window.MathJax && markdownLikelyContainsMath(markdown)) {
+ updatePdfProgress(state, 30, "Rendering math");
+ try {
+ await runPdfAbortable(state, MathJax.typesetPromise([tempElement]));
+ } catch (err) {
+ console.warn("MathJax rendering issue:", err);
}
- throwIfPdfExportAborted(progressState.signal);
- initMermaid(true);
- await runPdfAbortable(progressState, mermaid.init(undefined, mermaidNodes));
- tempElement.querySelectorAll('.mermaid-container.is-loading').forEach(container => {
- container.classList.remove('is-loading');
- });
- } catch (mermaidError) {
- if (mermaidError instanceof PdfExportCancelledError) throw mermaidError;
- console.warn("Mermaid rendering issue:", mermaidError);
- tempElement.querySelectorAll('.mermaid-container.is-loading').forEach(container => {
- container.classList.remove('is-loading');
- });
+ throwIfPdfExportAborted(state.signal);
+
+ tempElement.querySelectorAll('mjx-assistive-mml').forEach(el => el.remove());
+ tempElement.querySelectorAll('script[type*="math"], script[type*="tex"]').forEach(el => el.remove());
}
- throwIfPdfExportAborted(progressState.signal);
- await waitForPdfFrame(progressState);
+
+ const mermaidNodes = tempElement.querySelectorAll('.mermaid');
+ if (mermaidNodes.length > 0) {
+ updatePdfProgress(state, 40, "Rendering diagrams");
+ try {
+ if (typeof mermaid === 'undefined') {
+ await runPdfAbortable(state, loadScript(CDN.mermaid));
+ }
+ throwIfPdfExportAborted(state.signal);
+ initMermaid(true);
+ await runPdfAbortable(state, mermaid.init(undefined, mermaidNodes));
+ tempElement.querySelectorAll('.mermaid-container.is-loading').forEach(container => {
+ container.classList.remove('is-loading');
+ });
+ } catch (err) {
+ console.warn("Mermaid rendering issue:", err);
+ tempElement.querySelectorAll('.mermaid-container.is-loading').forEach(container => {
+ container.classList.remove('is-loading');
+ });
+ }
+ throwIfPdfExportAborted(state.signal);
+ }
+
+ const images = Array.from(tempElement.querySelectorAll("img"));
+ if (images.length > 0) {
+ updatePdfProgress(state, 50, "Loading images");
+ await Promise.all(images.map(img => {
+ if (img.complete) return Promise.resolve();
+ return new Promise(resolve => {
+ img.addEventListener("load", resolve, { once: true });
+ img.addEventListener("error", resolve, { once: true });
+ });
+ }));
+ await Promise.all(images.map(img => {
+ if (typeof img.decode === "function") {
+ return img.decode().catch(() => {});
+ }
+ return Promise.resolve();
+ }));
+ throwIfPdfExportAborted(state.signal);
+ }
+
+ if (document.fonts && typeof document.fonts.ready === "object") {
+ updatePdfProgress(state, 55, "Loading fonts");
+ await document.fonts.ready;
+ throwIfPdfExportAborted(state.signal);
+ }
+
+ updatePdfProgress(state, 60, "Stabilizing layout");
+ await waitForPdfFrame(state);
+ await waitForPdfFrame(state);
}
+ },
- if (window.MathJax && markdownLikelyContainsMath(markdown)) {
- updatePdfProgress(progressState, 44, "Rendering math");
+ WebPrintBackend: {
+ print: function(fullHtml, state) {
+ return new Promise((resolve, reject) => {
+ try {
+ throwIfPdfExportAborted(state.signal);
+
+ const iframe = document.createElement("iframe");
+ iframe.style.position = "fixed";
+ iframe.style.left = "-9999px";
+ iframe.style.top = "0";
+ iframe.style.width = "100%";
+ iframe.style.height = "100%";
+ document.body.appendChild(iframe);
+
+ const doc = iframe.contentDocument || iframe.contentWindow.document;
+ doc.open();
+ doc.write(fullHtml);
+ doc.close();
+
+ const cleanup = () => {
+ if (iframe.parentNode) {
+ iframe.parentNode.removeChild(iframe);
+ }
+ };
+
+ iframe.contentWindow.focus();
+
+ iframe.contentWindow.addEventListener("afterprint", () => {
+ cleanup();
+ resolve();
+ }, { once: true });
+
+ iframe.contentWindow.print();
+
+ setTimeout(() => {
+ cleanup();
+ resolve();
+ }, 5000);
+ } catch (err) {
+ reject(err);
+ }
+ });
+ }
+ },
+
+ DesktopChromiumSidecarBackend: {
+ print: async function(fullHtml, state) {
+ throwIfPdfExportAborted(state.signal);
+
+ const outputPath = await Neutralino.os.showSaveDialog("Save PDF Document", {
+ filters: [{ name: "PDF files", extensions: ["pdf"] }]
+ });
+
+ if (!outputPath) {
+ throw new Error("Export cancelled by user.");
+ }
+
+ throwIfPdfExportAborted(state.signal);
+
+ let port;
try {
- await runPdfAbortable(progressState, MathJax.typesetPromise([tempElement]));
- } catch (mathJaxError) {
- if (mathJaxError instanceof PdfExportCancelledError) throw mathJaxError;
- console.warn("MathJax rendering issue:", mathJaxError);
+ port = await Neutralino.filesystem.readFile(".pdf_exporter_port");
+ port = port.trim();
+ } catch (err) {
+ throw new Error("Local PDF exporter extension is not running. Please make sure the desktop application is running properly.");
}
- throwIfPdfExportAborted(progressState.signal);
-
- // Hide MathJax assistive elements that cause duplicate text in PDF
- // These are screen reader elements that html2canvas captures as visible
- // Use multiple CSS properties to ensure html2canvas doesn't render them
- const assistiveElements = tempElement.querySelectorAll('mjx-assistive-mml');
- assistiveElements.forEach(el => {
- el.style.display = 'none';
- el.style.visibility = 'hidden';
- el.style.position = 'absolute';
- el.style.width = '0';
- el.style.height = '0';
- el.style.overflow = 'hidden';
- el.remove(); // Remove entirely from DOM
+
+ throwIfPdfExportAborted(state.signal);
+
+ const response = await fetch(`http://127.0.0.1:${port}/export`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ html: fullHtml,
+ outputPath: outputPath
+ }),
+ signal: state.signal
});
- // Also hide any MathJax script elements that might contain source
- const mathScripts = tempElement.querySelectorAll('script[type*="math"], script[type*="tex"]');
- mathScripts.forEach(el => el.remove());
+ if (!response.ok) {
+ const errData = await response.json().catch(() => ({}));
+ throw new Error(errData.error || `HTTP error ${response.status}`);
+ }
+
+ return outputPath;
}
+ },
- await waitForPdfFrame(progressState);
- fitExportElementToContent(tempElement);
- await waitForPdfFrame(progressState);
+ LegacyRasterBackend: {
+ print: async function(tempElement, state) {
+ throwIfPdfExportAborted(state.signal);
- // Analyze and apply page-breaks for graphics (Story 1.1 + 1.2)
- updatePdfProgress(progressState, 55, "Optimizing page breaks");
- const pageBreakAnalysis = applyPageBreaksWithCascade(tempElement, PAGE_CONFIG, 10, progressState.signal);
- throwIfPdfExportAborted(progressState.signal);
+ if (typeof jspdf === 'undefined' || typeof html2canvas === 'undefined') {
+ updatePdfProgress(state, 62, "Loading PDF libraries");
+ await runPdfAbortable(state, Promise.all([loadScript(CDN.jspdf), loadScript(CDN.html2canvas)]));
+ throwIfPdfExportAborted(state.signal);
+ }
- // Scale oversized graphics that can't fit on a single page (Story 1.3)
- if (pageBreakAnalysis.oversizedElements && pageBreakAnalysis.pageHeightPx) {
- handleOversizedElements(pageBreakAnalysis.oversizedElements, pageBreakAnalysis.pageHeightPx, progressState.signal);
- }
- await waitForPdfFrame(progressState);
+ updatePdfProgress(state, 65, "Calculating legacy page breaks");
+ const pageBreakAnalysis = applyPageBreaksWithCascade(tempElement, PAGE_CONFIG, 10, state.signal);
+ throwIfPdfExportAborted(state.signal);
- const pdfOptions = {
- orientation: 'portrait',
- unit: 'mm',
- format: 'a4',
- compress: true,
- hotfixes: ["px_scaling"]
- };
+ if (pageBreakAnalysis.oversizedElements && pageBreakAnalysis.pageHeightPx) {
+ handleOversizedElements(pageBreakAnalysis.oversizedElements, pageBreakAnalysis.pageHeightPx, state.signal);
+ }
+ await waitForPdfFrame(state);
+
+ const pdfOptions = {
+ orientation: 'portrait',
+ unit: 'mm',
+ format: 'a4',
+ compress: true,
+ hotfixes: ["px_scaling"]
+ };
- const pdf = new jspdf.jsPDF(pdfOptions);
- const pageWidth = pdf.internal.pageSize.getWidth();
- const pageHeight = pdf.internal.pageSize.getHeight();
- const margin = 15;
- const contentWidth = pageWidth - (margin * 2);
- const captureScale = choosePdfCanvasScale(tempElement);
-
- updatePdfProgress(progressState, 65, "Capturing document");
- const canvas = await runPdfAbortable(progressState, html2canvas(tempElement, {
- scale: captureScale,
- useCORS: true,
- allowTaint: false,
- logging: false,
- windowWidth: Math.max(PAGE_CONFIG.windowWidth, Math.ceil(tempElement.getBoundingClientRect().width)),
- windowHeight: tempElement.scrollHeight
- }));
- await waitForPdfFrame(progressState);
- throwIfPdfExportAborted(progressState.signal);
-
- const scaleFactor = canvas.width / contentWidth;
- const imgHeight = canvas.height / scaleFactor;
- const pagesCount = Math.ceil(imgHeight / (pageHeight - margin * 2));
-
- updatePdfProgress(progressState, 76, "Rendering pages");
- for (let page = 0; page < pagesCount; page++) {
- throwIfPdfExportAborted(progressState.signal);
- const pageProgress = 76 + ((page + 1) / pagesCount) * 18;
- updatePdfProgress(progressState, pageProgress, `Rendering page ${page + 1} of ${pagesCount}`);
-
- if (page > 0) pdf.addPage();
-
- const sourceY = page * (pageHeight - margin * 2) * scaleFactor;
- const sourceHeight = Math.min(canvas.height - sourceY, (pageHeight - margin * 2) * scaleFactor);
- const destHeight = sourceHeight / scaleFactor;
-
- const pageCanvas = document.createElement('canvas');
- pageCanvas.width = canvas.width;
- pageCanvas.height = sourceHeight;
-
- const ctx = pageCanvas.getContext('2d');
- ctx.drawImage(canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight);
-
- const imgData = pageCanvas.toDataURL('image/png');
- pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, destHeight);
- await waitForPdfFrame(progressState);
- }
-
- throwIfPdfExportAborted(progressState.signal);
- updatePdfProgress(progressState, 98, "Preparing download");
- pdf.save("document.pdf");
- updatePdfProgress(progressState, 100, "Complete");
+ const pdf = new jspdf.jsPDF(pdfOptions);
+ const pageWidth = pdf.internal.pageSize.getWidth();
+ const pageHeight = pdf.internal.pageSize.getHeight();
+ const margin = 15;
+ const contentWidth = pageWidth - (margin * 2);
+ const captureScale = choosePdfCanvasScale(tempElement);
+
+ updatePdfProgress(state, 75, "Capturing document");
+ const canvas = await runPdfAbortable(state, html2canvas(tempElement, {
+ scale: captureScale,
+ useCORS: true,
+ allowTaint: false,
+ logging: false,
+ windowWidth: Math.max(PAGE_CONFIG.windowWidth, Math.ceil(tempElement.getBoundingClientRect().width)),
+ windowHeight: tempElement.scrollHeight
+ }));
+ await waitForPdfFrame(state);
+ throwIfPdfExportAborted(state.signal);
- } catch (error) {
- if (error instanceof PdfExportCancelledError || progressState.signal.aborted) {
- console.info("PDF export cancelled");
- } else {
- console.error("PDF export failed:", error);
- alert("PDF export failed: " + error.message);
+ const scaleFactor = canvas.width / contentWidth;
+ const imgHeight = canvas.height / scaleFactor;
+ const pagesCount = Math.ceil(imgHeight / (pageHeight - margin * 2));
+
+ updatePdfProgress(state, 80, "Rendering pages");
+ for (let page = 0; page < pagesCount; page++) {
+ throwIfPdfExportAborted(state.signal);
+ const pageProgress = 80 + ((page + 1) / pagesCount) * 18;
+ updatePdfProgress(state, pageProgress, `Rendering page ${page + 1} of ${pagesCount}`);
+
+ if (page > 0) pdf.addPage();
+
+ const sourceY = page * (pageHeight - margin * 2) * scaleFactor;
+ const sourceHeight = Math.min(canvas.height - sourceY, (pageHeight - margin * 2) * scaleFactor);
+ const destHeight = sourceHeight / scaleFactor;
+
+ const pageCanvas = document.createElement('canvas');
+ pageCanvas.width = canvas.width;
+ pageCanvas.height = sourceHeight;
+
+ const ctx = pageCanvas.getContext('2d');
+ ctx.drawImage(canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight);
+
+ const imgData = pageCanvas.toDataURL('image/png');
+ pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, destHeight);
+ await waitForPdfFrame(state);
+ }
+
+ throwIfPdfExportAborted(state.signal);
+ updatePdfProgress(state, 98, "Saving document");
+ pdf.save("document.pdf");
}
- } finally {
- cleanupPdfExport(progressState);
}
+ };
+
+ // PDF Export Modal Elements
+ const pdfExportModal = document.getElementById("pdf-export-modal");
+ const pdfExportConfirmBtn = document.getElementById("pdf-export-modal-confirm");
+ const pdfExportCancelBtn = document.getElementById("pdf-export-modal-close");
+ const pdfExportCloseIcon = document.getElementById("pdf-export-modal-close-icon");
+ const pdfCardPrint = document.getElementById("pdf-card-print");
+ const pdfCardLegacy = document.getElementById("pdf-card-legacy");
+ const pdfEnginePrintInput = document.getElementById("pdf-engine-print");
+ const pdfEngineLegacyInput = document.getElementById("pdf-engine-legacy");
+
+ function syncPdfCardStyles() {
+ if (pdfEnginePrintInput.checked) {
+ pdfCardPrint.classList.add("is-selected");
+ pdfCardLegacy.classList.remove("is-selected");
+ } else {
+ pdfCardLegacy.classList.add("is-selected");
+ pdfCardPrint.classList.remove("is-selected");
+ }
+ }
+
+ if (pdfEnginePrintInput && pdfEngineLegacyInput) {
+ pdfEnginePrintInput.addEventListener("change", syncPdfCardStyles);
+ pdfEngineLegacyInput.addEventListener("change", syncPdfCardStyles);
+ }
+
+ function openPdfExportModal() {
+ pdfEnginePrintInput.checked = true;
+ syncPdfCardStyles();
+ pdfExportModal.style.display = "";
+ requestAnimationFrame(() => {
+ pdfExportModal.classList.add("is-visible");
+ pdfExportModal.setAttribute("aria-hidden", "false");
+ });
+ }
+
+ function closePdfExportModal() {
+ pdfExportModal.classList.remove("is-visible");
+ pdfExportModal.setAttribute("aria-hidden", "true");
+ pdfExportModal.addEventListener("transitionend", function handler() {
+ pdfExportModal.style.display = "none";
+ pdfExportModal.removeEventListener("transitionend", handler);
+ });
+ }
+
+ if (pdfExportCancelBtn) pdfExportCancelBtn.addEventListener("click", closePdfExportModal);
+ if (pdfExportCloseIcon) pdfExportCloseIcon.addEventListener("click", closePdfExportModal);
+ if (pdfExportModal) {
+ pdfExportModal.addEventListener("click", function (e) {
+ if (e.target === pdfExportModal) closePdfExportModal();
+ });
+ }
+
+ if (pdfExportConfirmBtn) {
+ pdfExportConfirmBtn.addEventListener("click", async function() {
+ closePdfExportModal();
+
+ if (activePdfExport) return;
+
+ const progressState = createPdfProgressState();
+ activePdfExport = progressState;
+ setPdfExportTriggersBusy(progressState, true);
+ document.body.appendChild(progressState.overlay);
+ updatePdfProgress(progressState, 3, "Starting");
+ progressState.overlay.querySelector(".pdf-progress-cancel")?.focus();
+
+ try {
+ const isLegacy = pdfEngineLegacyInput.checked;
+ const markdown = markdownEditor.value;
+
+ const { fullHtml, tempElement } = await PdfExportEngine.ExportDocumentBuilder.build(markdown, progressState);
+
+ if (isLegacy) {
+ await PdfExportEngine.LegacyRasterBackend.print(tempElement, progressState);
+ } else {
+ updatePdfProgress(progressState, 75, "Generating PDF");
+ if (typeof Neutralino !== 'undefined') {
+ await PdfExportEngine.DesktopChromiumSidecarBackend.print(fullHtml, progressState);
+ } else {
+ await PdfExportEngine.WebPrintBackend.print(fullHtml, progressState);
+ }
+ }
+
+ updatePdfProgress(progressState, 100, "Complete");
+ } catch (error) {
+ if (error instanceof PdfExportCancelledError || progressState.signal.aborted) {
+ console.info("PDF export cancelled");
+ } else {
+ console.error("PDF export failed:", error);
+ alert("PDF export failed: " + error.message);
+ }
+ } finally {
+ cleanupPdfExport(progressState);
+ }
+ });
+ }
+
+ exportPdf.addEventListener("click", async function (event) {
+ event.preventDefault();
+ openPdfExportModal();
});
copyMarkdownButton.addEventListener("click", function () {
diff --git a/desktop-app/resources/styles.css b/desktop-app/resources/styles.css
index f080608..34c6d77 100644
--- a/desktop-app/resources/styles.css
+++ b/desktop-app/resources/styles.css
@@ -1,3848 +1,3962 @@
-:root {
- --bg-color: #ffffff;
- --editor-bg: #f6f8fa;
- --preview-bg: #ffffff; /* Preview background for light mode */
- --text-color: #24292e;
- --text-secondary: #57606a;
- --font-mono: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
- --color-danger-fg: #d73a49;
- --preview-text-color: #24292e; /* Text color for preview in light mode */
- --border-color: #e1e4e8;
- --header-bg: #f6f8fa;
- --button-bg: #f6f8fa;
- --button-hover: #e1e4e8;
- --button-active: #d1d5da;
- --scrollbar-thumb: #c1c1c1;
- --scrollbar-track: #f1f1f1;
- --accent-color: #0366d6;
- --table-bg: #ffffff; /* Table background for light mode */
- --code-bg: #f6f8fa; /* Code block background for light mode */
- --skeleton-bg: #e2e8f0;
- --skeleton-glow: rgba(255, 255, 255, 0.65);
-
- /* Find & Replace Panel custom properties (PERF-010 consolidated) */
- --fr-bg: rgba(255, 255, 255, 0.95);
- --fr-border: #d0d7de;
- --fr-shadow: 0 8px 24px rgba(140, 149, 159, 0.2);
- --fr-btn-active: #0969da;
- --fr-btn-active-bg: #ddf4ff;
- --fr-match-highlight: #ffdf5d;
- --fr-match-active: #ff9b30;
- --fr-error-bg: #ffebe9;
- --fr-error-border: #ff8577;
- --fr-text-danger: #cf222e;
-}
-
-[data-theme="dark"] {
- --bg-color: #0d1117;
- --editor-bg: #161b22;
- --preview-bg: #0d1117; /* Preview background for dark mode */
- --text-color: #c9d1d9;
- --text-secondary: #8b949e;
- --color-danger-fg: #f85149;
- --preview-text-color: #c9d1d9; /* Text color for preview in dark mode */
- --border-color: #30363d;
- --header-bg: #161b22;
- --button-bg: #21262d;
- --button-hover: #30363d;
- --button-active: #3b434b;
- --scrollbar-thumb: #484f58;
- --scrollbar-track: #21262d;
- --accent-color: #58a6ff;
- --table-bg: #161b22; /* Table background for dark mode */
- --code-bg: #161b22; /* Code block background for dark mode */
- --skeleton-bg: #2d3139;
- --skeleton-glow: rgba(255, 255, 255, 0.08);
-
- /* Find & Replace Panel custom properties for dark mode */
- --fr-bg: rgba(28, 33, 40, 0.98);
- --fr-border: #444c56;
- --fr-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
- --fr-btn-active: #2f81f7;
- --fr-btn-active-bg: rgba(56, 139, 253, 0.15);
- --fr-match-highlight: rgba(187, 128, 9, 0.4);
- --fr-match-active: #f1e05a;
- --fr-error-bg: rgba(248, 81, 73, 0.1);
- --fr-error-border: rgba(248, 81, 73, 0.4);
- --fr-text-danger: #ff7b72;
-}
-
-* {
- box-sizing: border-box;
- margin: 0;
- padding: 0;
-}
-
-@media (min-width: 768px) {
- html,
- body {
- height: 100%;
- overflow: hidden;
- }
-}
-
-body {
- background-color: var(--bg-color);
- color: var(--text-color);
- /* PERF-021: Removed background-color transition to avoid full-viewport repaint on theme toggle */
- transition: color 0.15s ease;
- min-height: 100vh;
- font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Hiragino Kaku Gothic ProN", Meiryo, "Malgun Gothic", "Apple SD Gothic Neo", "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
-}
-
-.app-header {
- background-color: var(--header-bg);
- border-bottom: 1px solid var(--border-color);
- padding: 0.35rem 0.75rem;
- transition: background-color 0.3s ease;
- position: relative;
- z-index: 100;
- flex-shrink: 0;
-}
-
-.app-container {
- height: 100vh;
- display: flex;
- flex-direction: column;
- overflow: hidden;
-}
-
-.content-container {
- display: flex;
- flex: 1;
- overflow: hidden;
-}
-
-.editor-pane, .preview-pane {
- flex: 1;
- padding: 20px;
- overflow-y: auto;
- position: relative;
- /* PERF-025: Shortened transition and scoped to background-color only */
- transition: background-color 0.15s ease;
-}
-
-.editor-pane {
- background-color: var(--editor-bg);
- border-right: 1px solid var(--border-color);
- padding-right: 0px;
- --line-number-gutter: 0px;
-}
-
-.preview-pane {
- background-color: var(--preview-bg); /* Using the new variable for preview background */
-}
-
-/* Custom scrollbar */
-.editor-pane::-webkit-scrollbar,
-.preview-pane::-webkit-scrollbar,
-#markdown-editor::-webkit-scrollbar {
- width: 8px;
- height: 8px;
-}
-
-.editor-pane::-webkit-scrollbar-track,
-.preview-pane::-webkit-scrollbar-track,
-#markdown-editor::-webkit-scrollbar-track {
- background: var(--scrollbar-track);
-}
-
-.editor-pane::-webkit-scrollbar-thumb,
-.preview-pane::-webkit-scrollbar-thumb,
-#markdown-editor::-webkit-scrollbar-thumb {
- background: var(--scrollbar-thumb);
- border-radius: 4px;
-}
-
-.editor-pane::-webkit-scrollbar-thumb:hover,
-.preview-pane::-webkit-scrollbar-thumb:hover,
-#markdown-editor::-webkit-scrollbar-thumb:hover {
- background: var(--button-active);
-}
-
-#markdown-editor {
- width: 100%;
- height: 100%;
- border: none;
- background-color: transparent;
- color: var(--text-color);
- resize: none;
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
- font-size: 14px;
- line-height: 1.5;
- padding: 10px;
- padding-left: calc(10px + var(--line-number-gutter));
- transition: background-color 0.3s ease, color 0.3s ease;
- overflow-y: auto;
- position: relative;
- z-index: 3;
-}
-
-#markdown-editor:focus {
- outline: none;
-}
-
-.preview-pane {
- padding: 20px;
-}
-
-.markdown-body {
- padding: 20px;
- width: 100%;
- background-color: var(--preview-bg); /* Ensuring the markdown content matches preview background */
- color: var(--preview-text-color); /* Using specific text color for preview content */
-}
-
-.markdown-body a.reference-link {
- font-size: 0.75em;
- letter-spacing: -0.02em;
- line-height: 1;
- vertical-align: super;
- position: relative;
- top: 0.08em;
-}
-
-/* Style tables in light mode */
-.markdown-body table {
- background-color: var(--table-bg);
- border-color: var(--border-color);
-}
-
-.markdown-body table tr {
- background-color: var(--table-bg);
- border-top: 1px solid var(--border-color);
-}
-
-.markdown-body table tr:nth-child(2n) {
- background-color: var(--bg-color);
-}
-
-/* Style code blocks in light mode */
-.markdown-body pre {
- background-color: var(--code-bg);
- border-radius: 6px;
-}
-
-.markdown-body code {
- background-color: var(--code-bg);
- border-radius: 3px;
- padding: 0.2em 0.4em;
-}
-
-.markdown-body img.emoji-inline {
- width: 1em;
- height: 1em;
- vertical-align: -0.1em;
-}
-
-.markdown-body ul,
-.markdown-body ol {
- padding-left: 2em;
- margin: 0.4em 0;
-}
-
-.markdown-body ul ul,
-.markdown-body ul ol,
-.markdown-body ol ul,
-.markdown-body ol ol {
- margin-top: 0.2em;
- margin-bottom: 0.2em;
-}
-
-.markdown-body ul.contains-task-list,
-.markdown-body li.task-list-item {
- list-style: none;
-}
-
-.markdown-body ul.contains-task-list {
- padding-left: 2em;
-}
-
-.markdown-body li.task-list-item input[type="checkbox"] {
- margin: 0 0.5em 0.2em 0;
- vertical-align: middle;
- pointer-events: none;
-}
-
-.markdown-body li.task-list-item::marker {
- content: "";
-}
-
-.markdown-body li:has(> input[type="checkbox"]) {
- list-style: none;
-}
-
-.markdown-body li:has(> input[type="checkbox"])::marker {
- content: "";
-}
-
-.markdown-body ul:has(> li > input[type="checkbox"]) {
- list-style: none;
- padding-left: 2em;
-}
-
-.markdown-body .footnotes {
- margin-top: 1.5rem;
- font-size: 0.9em;
-}
-
-.markdown-body .footnotes ol {
- padding-left: 1.5em;
-}
-
-.markdown-body .footnotes ol > li::marker {
- content: "[" counter(list-item) "] ";
- font-weight: 600;
-}
-
-.markdown-body .footnotes li > p {
- margin: 0.2em 0;
-}
-
-.markdown-body .footnote-ref a,
-.markdown-body .footnote-backref {
- text-decoration: none;
-}
-
-.markdown-body .footnote-backref {
- margin-left: 0.4em;
-}
-
-.markdown-body .markdown-alert {
- padding: 0.5rem 1rem;
- margin-bottom: 16px;
- border-left: 0.25em solid;
- border-radius: 0.375rem;
-}
-
-.markdown-body .markdown-alert > :last-child {
- margin-bottom: 0;
-}
-
-.markdown-body .markdown-alert-title {
- margin: 0 0 8px;
- font-weight: 600;
- line-height: 1.25;
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.markdown-body .markdown-alert-icon {
- display: inline-flex;
- width: 16px;
- height: 16px;
-}
-
-.markdown-body .markdown-alert-icon svg {
- width: 16px;
- height: 16px;
- fill: currentColor;
-}
-
-.markdown-body .markdown-alert-note {
- color: #0969da;
- border-left-color: #0969da;
- background-color: #ddf4ff;
-}
-
-.markdown-body .markdown-alert-tip {
- color: #1a7f37;
- border-left-color: #1a7f37;
- background-color: #dafbe1;
-}
-
-.markdown-body .markdown-alert-important {
- color: #8250df;
- border-left-color: #8250df;
- background-color: #fbefff;
-}
-
-.markdown-body .markdown-alert-warning {
- color: #9a6700;
- border-left-color: #9a6700;
- background-color: #fff8c5;
-}
-
-.markdown-body .markdown-alert-caution {
- color: #cf222e;
- border-left-color: #cf222e;
- background-color: #ffebe9;
-}
-
-.markdown-body .markdown-alert > *:not(.markdown-alert-title) {
- color: var(--preview-text-color);
-}
-
-[data-theme="dark"] .markdown-body .markdown-alert-note {
- color: #4493f8;
- background-color: rgba(31, 111, 235, 0.15);
- border-left-color: #4493f8;
-}
-
-[data-theme="dark"] .markdown-body .markdown-alert-tip {
- color: #3fb950;
- background-color: rgba(35, 134, 54, 0.15);
- border-left-color: #3fb950;
-}
-
-[data-theme="dark"] .markdown-body .markdown-alert-important {
- color: #ab7df8;
- background-color: rgba(137, 87, 229, 0.15);
- border-left-color: #ab7df8;
-}
-
-[data-theme="dark"] .markdown-body .markdown-alert-warning {
- color: #d29922;
- background-color: rgba(210, 153, 34, 0.18);
- border-left-color: #d29922;
-}
-
-[data-theme="dark"] .markdown-body .markdown-alert-caution {
- color: #f85149;
- background-color: rgba(248, 81, 73, 0.18);
- border-left-color: #f85149;
-}
-
-.toolbar {
- display: flex;
- gap: 8px;
- align-items: center;
-}
-
-.toolbar-group {
- display: inline-flex;
- align-items: center;
- gap: 6px;
-}
-
-.toolbar-divider {
- width: 1px;
- height: 20px;
- background-color: var(--border-color);
- opacity: 0.7;
-}
-
-.tool-button {
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- color: var(--text-color);
- border-radius: 5px;
- padding: 4px 8px;
- font-size: 13px;
- cursor: pointer;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 4px;
- /* PERF-016: Specific transition properties instead of 'all' to avoid animating layout-triggering properties */
- transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
-}
-
-.tool-button:hover {
- background-color: var(--button-hover);
-}
-
-.tool-button:active {
- background-color: var(--button-active);
-}
-
-.tool-button:disabled,
-.tool-button[aria-disabled="true"] {
- cursor: not-allowed;
- opacity: 0.5;
-}
-
-.tool-button i {
- font-size: 15px;
-}
-
-.tool-button.is-active,
-.tool-button.is-active:hover {
- border-color: var(--accent-color);
- color: var(--accent-color);
- background-color: rgba(3, 102, 214, 0.08);
-}
-
-.btn-text {
- display: none;
-}
-
-.toolbar .tool-button {
- height: 28px;
- min-width: 28px;
-}
-
-.toolbar .tool-button.sync-active {
- border-color: var(--accent-color);
- color: var(--accent-color);
-}
-
-.file-input {
- display: none;
-}
-
-/* Drag overlay: full-screen drop target shown when user drags a file over the window */
-.drag-overlay {
- display: none;
- position: fixed;
- inset: 0;
- z-index: 9999;
- background-color: rgba(0, 0, 0, 0.45);
- pointer-events: none;
- align-items: center;
- justify-content: center;
-}
-
-.drag-overlay.active {
- display: flex;
- pointer-events: auto;
-}
-
-.drag-overlay-inner {
- border: 3px dashed var(--accent-color);
- border-radius: 12px;
- padding: 48px 64px;
- text-align: center;
- color: #ffffff;
- background-color: rgba(3, 102, 214, 0.15);
- animation: overlayPulse 1.4s ease-in-out infinite;
-}
-
-.drag-overlay-icon {
- display: block;
- font-size: 3rem;
- margin-bottom: 12px;
- color: var(--accent-color);
-}
-
-.drag-overlay-text {
- font-size: 1.4rem;
- font-weight: 600;
- margin-bottom: 4px;
-}
-
-.drag-overlay-sub {
- font-size: 0.85rem;
- opacity: 0.75;
- margin-bottom: 0;
-}
-
-@keyframes overlayPulse {
- 0%, 100% { transform: scale(1); }
- 50% { transform: scale(1.015); }
-}
-
-/* Editor drop hint: subtle text at bottom of editor pane, shown only when empty */
-.drop-hint {
- position: absolute;
- bottom: 14px;
- left: 0;
- right: 0;
- text-align: center;
- font-size: 0.75rem;
- color: var(--text-color);
- opacity: 0.35;
- pointer-events: none;
- user-select: none;
- z-index: 3;
-}
-
-.editor-pane:has(#markdown-editor:not(:placeholder-shown)) .drop-hint {
- display: none;
-}
-
-.line-numbers {
- position: absolute;
- top: 20px;
- bottom: 20px;
- left: 20px;
- width: var(--line-number-gutter);
- padding: 10px 8px 10px 0;
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
- font-size: 14px;
- line-height: 1.5;
- text-align: right;
- color: var(--text-secondary);
- background-color: var(--editor-bg);
- border-right: 1px solid var(--border-color);
- box-sizing: border-box;
- overflow: hidden;
- pointer-events: none;
- user-select: none;
- z-index: 2;
- font-variant-numeric: tabular-nums;
-}
-
-.line-numbers .line-number {
- display: block;
- height: auto;
-}
-
-.editor-highlight-layer {
- position: absolute;
- inset: 20px 0 20px calc(20px + var(--line-number-gutter));
- padding: 10px;
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
- font-size: 14px;
- line-height: 1.5;
- white-space: pre-wrap;
- word-wrap: break-word;
- color: transparent;
- pointer-events: none;
- overflow: auto;
- background-color: var(--editor-bg);
- border-radius: 4px;
- z-index: 1;
-}
-
-.editor-highlight-layer::-webkit-scrollbar {
- width: 8px;
- height: 8px;
-}
-
-.editor-highlight-layer::-webkit-scrollbar-thumb {
- background: transparent;
-}
-
-.editor-highlight-layer::-webkit-scrollbar-track {
- background: transparent;
-}
-
-.find-highlight {
- background-color: var(--fr-match-highlight, rgba(255, 223, 93, 0.4)) !important;
- border-radius: 2px;
- color: transparent !important;
-}
-
-.find-highlight.active {
- background-color: var(--fr-match-active, #ff9b30) !important;
- color: transparent !important;
-}
-
-/* Dropdown improvements */
-.dropdown-menu {
- background-color: var(--bg-color);
- border-color: var(--border-color);
-}
-
-.dropdown-item {
- color: var(--text-color);
-}
-
-.dropdown-item:hover, .dropdown-item:focus {
- background-color: var(--button-hover);
- color: var(--text-color);
-}
-
-/* Markdown formatting toolbar */
-.markdown-format-toolbar {
- display: flex;
- align-items: center;
- height: 34px;
- padding: 0 6px;
- background-color: var(--header-bg);
- border-bottom: 1px solid var(--border-color);
- overflow-x: auto;
- overflow-y: hidden;
- flex-shrink: 0;
- scrollbar-width: none;
- -ms-overflow-style: none;
-}
-
-.markdown-format-toolbar::-webkit-scrollbar {
- display: none;
-}
-
-.markdown-toolbar-group {
- display: flex;
- align-items: center;
- gap: 2px;
- height: 100%;
- padding: 0 6px;
- border-right: 1px solid var(--border-color);
- flex-shrink: 0;
-}
-
-.markdown-toolbar-group:first-child {
- padding-left: 0;
-}
-
-.markdown-toolbar-group:last-child {
- border-right: none;
- padding-right: 0;
-}
-
-.markdown-tool-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 26px;
- height: 26px;
- border: 1px solid transparent;
- border-radius: 4px;
- background: transparent;
- color: var(--text-color);
- cursor: pointer;
- font-size: 14px;
- line-height: 1;
- padding: 0;
- transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
-}
-
-.markdown-tool-btn:hover,
-.markdown-tool-btn:focus-visible {
- background-color: var(--button-hover);
- border-color: var(--border-color);
- color: var(--accent-color);
-}
-
-.markdown-tool-btn:active {
- background-color: var(--button-active);
-}
-
-.markdown-tool-btn:disabled,
-.markdown-tool-btn.disabled {
- opacity: 0.4;
- cursor: not-allowed;
- pointer-events: none;
-}
-
-.markdown-tool-btn i {
- font-size: 15px;
-}
-
-.markdown-tool-btn[data-md-action="reference"] i::before {
- content: "[ ]";
- font-style: normal;
- font-size: 12px;
- letter-spacing: -0.12em;
-}
-
-.markdown-tool-btn.text-tool {
- width: auto;
- min-width: 26px;
- padding: 0 5px;
- font-weight: 600;
- font-family: Georgia, "Times New Roman", serif;
-}
-
-.heading-group .markdown-tool-btn {
- min-width: 30px;
-}
-
-
-
-/* Loading indicators */
-.loading {
- opacity: 0.6;
- pointer-events: none;
-}
-
-/* Focus outline for accessibility */
-button:focus,
-a:focus {
- outline: 2px solid var(--accent-color);
- outline-offset: 2px;
-}
-
-/* Animation for copied message */
-@keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
-}
-
-/* Tooltip styles */
-.tooltip {
- position: absolute;
- background: var(--button-bg);
- border: 1px solid var(--border-color);
- padding: 5px 8px;
- border-radius: 4px;
- font-size: 12px;
- z-index: 1000;
- animation: fadeIn 0.2s ease;
-}
-
-/* Styles for GitHub markdown preview light mode */
-.markdown-body {
- color-scheme: light;
- --color-prettylights-syntax-comment: #6a737d;
- --color-prettylights-syntax-constant: #005cc5;
- --color-prettylights-syntax-entity: #6f42c1;
- --color-prettylights-syntax-storage-modifier-import: #24292e;
- --color-prettylights-syntax-entity-tag: #22863a;
- --color-prettylights-syntax-keyword: #cf222e;
- --color-prettylights-syntax-string: #032f62;
- --color-prettylights-syntax-variable: #e36209;
- --color-prettylights-syntax-brackethighlighter-unmatched: #b31d28;
- --color-prettylights-syntax-invalid-illegal-text: #fafbfc;
- --color-prettylights-syntax-invalid-illegal-bg: #b31d28;
- --color-prettylights-syntax-carriage-return-text: #fafbfc;
- --color-prettylights-syntax-carriage-return-bg: #d73a49;
- --color-prettylights-syntax-string-regexp: #22863a;
- --color-prettylights-syntax-markup-list: #735c0f;
- --color-prettylights-syntax-markup-heading: #005cc5;
- --color-prettylights-syntax-markup-italic: #24292e;
- --color-prettylights-syntax-markup-bold: #24292e;
- --color-prettylights-syntax-markup-deleted-text: #b31d28;
- --color-prettylights-syntax-markup-deleted-bg: #ffeef0;
- --color-prettylights-syntax-markup-inserted-text: #22863a;
- --color-prettylights-syntax-markup-inserted-bg: #f0fff4;
- --color-prettylights-syntax-markup-changed-text: #e36209;
- --color-prettylights-syntax-markup-changed-bg: #ffebda;
- --color-prettylights-syntax-markup-ignored-text: #f6f8fa;
- --color-prettylights-syntax-markup-ignored-bg: #005cc5;
- --color-prettylights-syntax-meta-diff-range: #6f42c1;
- --color-prettylights-syntax-brackethighlighter-angle: #586069;
- --color-prettylights-syntax-sublimelinter-gutter-mark: #e1e4e8;
- --color-prettylights-syntax-constant-other-reference-link: #032f62;
- --color-fg-default: #24292e;
- --color-fg-muted: #586069;
- --color-fg-subtle: #6a737d;
- --color-canvas-default: #ffffff;
- --color-canvas-subtle: #f6f8fa;
- --color-border-default: #e1e4e8;
- --color-border-muted: #eaecef;
- --color-neutral-muted: rgba(175,184,193,0.2);
- --color-accent-fg: #0366d6;
- --color-accent-emphasis: #0366d6;
- --color-attention-subtle: #fff5b1;
- --color-danger-fg: #d73a49;
-}
-
-/* Styles for GitHub markdown preview dark mode */
-[data-theme="dark"] .markdown-body {
- color-scheme: dark;
- --color-prettylights-syntax-comment: #8b949e;
- --color-prettylights-syntax-constant: #79c0ff;
- --color-prettylights-syntax-entity: #d2a8ff;
- --color-prettylights-syntax-storage-modifier-import: #c9d1d9;
- --color-prettylights-syntax-entity-tag: #7ee787;
- --color-prettylights-syntax-keyword: #ff7b72;
- --color-prettylights-syntax-string: #a5d6ff;
- --color-prettylights-syntax-variable: #ffa657;
- --color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
- --color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
- --color-prettylights-syntax-invalid-illegal-bg: #8e1519;
- --color-prettylights-syntax-carriage-return-text: #f0f6fc;
- --color-prettylights-syntax-carriage-return-bg: #b62324;
- --color-prettylights-syntax-string-regexp: #7ee787;
- --color-prettylights-syntax-markup-list: #f2cc60;
- --color-prettylights-syntax-markup-heading: #1f6feb;
- --color-prettylights-syntax-markup-italic: #c9d1d9;
- --color-prettylights-syntax-markup-bold: #c9d1d9;
- --color-prettylights-syntax-markup-deleted-text: #ffdcd7;
- --color-prettylights-syntax-markup-deleted-bg: #67060c;
- --color-prettylights-syntax-markup-inserted-text: #aff5b4;
- --color-prettylights-syntax-markup-inserted-bg: #033a16;
- --color-prettylights-syntax-markup-changed-text: #ffdfb6;
- --color-prettylights-syntax-markup-changed-bg: #5a1e02;
- --color-prettylights-syntax-markup-ignored-text: #c9d1d9;
- --color-prettylights-syntax-markup-ignored-bg: #1158c7;
- --color-prettylights-syntax-meta-diff-range: #d2a8ff;
- --color-prettylights-syntax-brackethighlighter-angle: #8b949e;
- --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
- --color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
- --color-fg-default: #c9d1d9;
- --color-fg-muted: #8b949e;
- --color-fg-subtle: #484f58;
- --color-canvas-default: #0d1117;
- --color-canvas-subtle: #161b22;
- --color-border-default: #30363d;
- --color-border-muted: #21262d;
- --color-neutral-muted: rgba(110,118,129,0.4);
- --color-accent-fg: #58a6ff;
- --color-accent-emphasis: #1f6feb;
- --color-attention-subtle: rgba(187,128,9,0.15);
- --color-danger-fg: #f85149;
-}
-
-/* Override specific styles for dark mode tables and code */
-[data-theme="dark"] .markdown-body table tr {
- background-color: var(--table-bg);
-}
-
-[data-theme="dark"] .markdown-body table tr:nth-child(2n) {
- background-color: #1c2128; /* Slightly lighter than base dark background */
-}
-
-[data-theme="dark"] .markdown-body pre {
- background-color: var(--code-bg);
-}
-
-[data-theme="dark"] .markdown-body code {
- background-color: var(--code-bg);
-}
-
-/* Syntax Highlighting Mapping to GitHub Variables */
-.hljs {
- color: var(--color-fg-default);
-}
-.hljs-doctag,
-.hljs-keyword,
-.hljs-meta .hljs-keyword,
-.hljs-template-tag,
-.hljs-template-variable,
-.hljs-type,
-.hljs-variable.language_ {
- color: var(--color-prettylights-syntax-keyword);
-}
-.hljs-title,
-.hljs-title.class_,
-.hljs-title.class_.inherited__,
-.hljs-title.function_ {
- color: var(--color-prettylights-syntax-entity);
-}
-.hljs-attr,
-.hljs-attribute,
-.hljs-literal,
-.hljs-meta,
-.hljs-number,
-.hljs-operator,
-.hljs-variable,
-.hljs-selector-attr,
-.hljs-selector-class,
-.hljs-selector-id {
- color: var(--color-prettylights-syntax-constant);
-}
-.hljs-regexp,
-.hljs-string,
-.hljs-meta .hljs-string {
- color: var(--color-prettylights-syntax-string);
-}
-.hljs-built_in,
-.hljs-symbol {
- color: var(--color-prettylights-syntax-variable);
-}
-.hljs-comment,
-.hljs-code,
-.hljs-formula {
- color: var(--color-prettylights-syntax-comment);
-}
-.hljs-name,
-.hljs-quote,
-.hljs-selector-tag,
-.hljs-selector-pseudo {
- color: var(--color-prettylights-syntax-entity-tag);
-}
-.hljs-subst {
- color: var(--color-fg-default);
-}
-.hljs-section {
- color: var(--color-prettylights-syntax-markup-heading);
- font-weight: bold;
-}
-.hljs-bullet {
- color: var(--color-prettylights-syntax-constant);
-}
-.hljs-emphasis {
- color: var(--color-fg-default);
- font-style: italic;
-}
-.hljs-strong {
- color: var(--color-fg-default);
- font-weight: bold;
-}
-.hljs-addition {
- color: var(--color-prettylights-syntax-markup-inserted-text);
- background-color: var(--color-prettylights-syntax-markup-inserted-bg);
-}
-.hljs-deletion {
- color: var(--color-prettylights-syntax-markup-deleted-text);
- background-color: var(--color-prettylights-syntax-markup-deleted-bg);
-}
-
-.stats-container {
- font-size: 0.8rem;
- color: var(--text-color);
-}
-
-.stat-item {
- align-items: center;
-}
-
-.stat-item i {
- font-size: 0.9rem;
- opacity: 0.8;
-}
-
-#importDropdown,
-#exportDropdown,
-#languageDropdown {
- font-size: 0.8rem;
-}
-
-#importDropdown i,
-#exportDropdown i,
-#languageDropdown i {
- font-size: 0.9rem;
-}
-
-/* Ensure desktop dropdown menu options match the stats-container font size */
-[aria-labelledby="importDropdown"] .dropdown-item,
-[aria-labelledby="exportDropdown"] .dropdown-item,
-[aria-labelledby="languageDropdown"] .dropdown-item {
- font-size: 0.8rem;
-}
-
-/* Ensure mobile menu import, export, and language dropdown triggers/options match the mobile stats-container font size */
-#mobile-import-button,
-#mobile-import-github-button,
-#mobile-export-md,
-#mobile-export-html,
-#mobile-export-pdf,
-#mobileLanguageDropdown {
- font-size: 0.9rem !important;
-}
-
-[aria-labelledby="mobileLanguageDropdown"] .dropdown-item {
- font-size: 0.9rem;
-}
-
-.editor-pane {
- overflow: hidden;
-}
-
-/* Mobile Menu Styles */
-.mobile-menu {
- display: none;
- position: relative;
- z-index: 1001;
-}
-
-
-
-/* slideāin panel */
-.mobile-menu-panel {
- position: fixed;
- top: 0;
- right: -300px;
- width: 280px;
- height: 100vh;
- background-color: var(--bg-color);
- box-shadow: -2px 0 10px rgba(0, 0, 0, 0.2);
- transition: right 0.3s ease;
- overflow-y: auto;
- padding: 1rem;
- display: flex;
- flex-direction: column;
- z-index: 1002;
-}
-
-.mobile-menu-panel.active {
- right: 0;
-}
-
-/* translucent overlay behind panel */
-.mobile-menu-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100vh;
- background-color: rgba(0, 0, 0, 0.5);
- opacity: 0;
- visibility: hidden;
- pointer-events: none;
- transition: opacity 0.3s ease, visibility 0.3s ease;
- z-index: 1000;
-}
-
-.mobile-menu-overlay.active {
- opacity: 1;
- visibility: visible;
- pointer-events: auto;
-}
-
-/* header inside mobile menu */
-.mobile-menu-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 1rem;
-}
-
-.mobile-menu-header h5 {
- margin: 0;
- font-size: 1.25rem;
- color: var(--text-color);
-}
-
-/* stats section in mobile menu */
-.mobile-stats-container {
- border-bottom: 1px solid var(--border-color);
- padding-bottom: 0.75rem;
- margin-bottom: 1rem;
-}
-
-.mobile-stats-container .stat-item {
- font-size: 0.9rem;
- color: var(--text-color);
- display: flex;
- align-items: center;
-}
-
-.mobile-stats-container .stat-item i {
- margin-right: 0.5em;
- opacity: 0.8;
-}
-
-/* menu buttons list */
-.mobile-menu-items {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- flex-grow: 1;
-}
-
-/* each menu item */
-.mobile-menu-item {
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- color: var(--text-color);
- border-radius: 6px;
- padding: 0.6rem 1rem;
- font-size: 1rem;
- text-align: left;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- transition: background-color 0.2s ease;
- cursor: pointer;
-}
-
-.mobile-menu-item:hover {
- background-color: var(--button-hover);
-}
-
-.mobile-menu-item:active {
- background-color: var(--button-active);
-}
-
-/* close button override */
-#close-mobile-menu.tool-button {
- padding: 0.25rem 0.5rem;
- font-size: 1rem;
-}
-
-/* Mobile document tabs section */
-.mobile-tabs-section {
- border-bottom: 1px solid var(--border-color);
- padding-bottom: 0.75rem;
-}
-
-.mobile-tabs-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 0.5rem;
-}
-
-.mobile-tabs-label {
- font-size: 0.85rem;
- font-weight: 600;
- color: var(--text-color);
- opacity: 0.8;
- text-transform: uppercase;
- letter-spacing: 0.04em;
-}
-
-.mobile-new-tab-btn {
- background: none;
- border: 1px solid var(--border-color);
- border-radius: 4px;
- color: var(--text-color);
- padding: 2px 7px;
- font-size: 0.9rem;
- cursor: pointer;
- display: flex;
- align-items: center;
- transition: background-color 0.15s ease;
-}
-
-.mobile-new-tab-btn:hover {
- background-color: var(--button-hover);
-}
-
-.mobile-tab-list {
- display: flex;
- flex-direction: column;
- gap: 4px;
- max-height: 180px;
- overflow-y: auto;
-}
-
-.mobile-tab-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- border-radius: 6px;
- padding: 0.45rem 0.75rem;
- font-size: 0.9rem;
- color: var(--text-color);
- cursor: pointer;
- transition: background-color 0.15s ease;
- gap: 0.5rem;
-}
-
-.mobile-tab-item:hover {
- background-color: var(--button-hover);
-}
-
-.mobile-tab-item.active {
- border-color: var(--accent-color);
- color: var(--accent-color);
- background-color: var(--bg-color);
-}
-
-.mobile-tab-title {
- flex: 1;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- min-width: 0;
-}
-
-.mobile-tab-item .tab-menu-btn {
- opacity: 0.6;
-}
-
-.mobile-tab-item:hover .tab-menu-btn,
-.mobile-tab-item.active .tab-menu-btn {
- opacity: 0.8;
-}
-
-#mobile-tab-reset-btn {
- margin-left: 0;
- height: auto;
- padding: 0.45rem 0.75rem;
- justify-content: center;
- font-size: 0.9rem;
-}
-
-/* ==========================================
- NAVBAR RESPONSIVE BREAKPOINTS
- >= 1080px : full desktop navbar
- < 1080px : mobile hamburger + stacked panes
- ========================================== */
-
-/* Mobile / tablet (< 1080px): switch to hamburger, stack panes */
-@media (max-width: 1079px) {
- /* Override Bootstrap d-md-flex / d-md-none so the breakpoint is 1080px */
- .stats-container,
- .toolbar {
- display: none !important;
- }
-
- /* Expand touch target sizes to meet WCAG mobile guidelines */
- .markdown-tool-btn {
- width: 36px !important;
- height: 36px !important;
- font-size: 16px !important;
- }
- .markdown-format-toolbar {
- height: 44px !important;
- }
- .tab-close-btn {
- width: 28px !important;
- height: 28px !important;
- font-size: 14px !important;
- }
- .tab-menu-btn {
- width: 28px !important;
- height: 28px !important;
- font-size: 14px !important;
- }
-
- .mobile-menu {
- display: block !important;
- }
-
- /* Stack editor and preview vertically */
- .content-container {
- flex-direction: column;
- }
-
- .editor-pane,
- .preview-pane {
- flex: none;
- height: 50%;
- border-right: none;
- }
-
- .editor-pane {
- border-bottom: 1px solid var(--border-color);
- }
-
- /* Hide drag-resize divider (touch devices don't use it) */
- .resize-divider {
- display: none;
- }
-
- /* Single-pane view modes: occupy full height */
- .content-container.view-editor-only .editor-pane,
- .content-container.view-preview-only .preview-pane {
- height: 100%;
- }
-
- .content-container.view-split .editor-pane,
- .content-container.view-split .preview-pane {
- height: 50%;
- }
-}
-
-.github-link {
- color: var(--text-color);
- text-decoration: none;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: transform 0.2s ease, color 0.2s ease;
- margin-right: 2rem;
-}
-
-.github-link:hover {
- color: var(--accent-color);
- transform: scale(1.1);
-}
-
-.github-link i {
- font-size: 1.25rem;
-}
-
-/* ========================================
- HEADER LAYOUT
- ======================================== */
-.header-container {
- position: relative;
- min-height: 30px;
-}
-
-.app-header h1 {
- font-size: 1.05rem;
- line-height: 1.1;
-}
-
-.header-left {
- flex: 1 0 auto;
- justify-content: flex-start;
- white-space: nowrap;
-}
-
-.header-right {
- flex: 1 0 auto;
- justify-content: flex-end;
- white-space: nowrap;
-}
-
-/* Pane View States */
-.content-container.view-editor-only .preview-pane {
- display: none;
-}
-
-.content-container.view-editor-only .editor-pane {
- flex: 1;
- border-right: none;
-}
-
-.content-container.view-preview-only .editor-pane {
- display: none;
-}
-
-.content-container.view-preview-only .preview-pane {
- flex: 1;
-}
-
-.content-container.view-split .editor-pane,
-.content-container.view-split .preview-pane {
- flex: 1;
-}
-
-/* Compact desktop (< 1280px): compact toolbar */
-@media (max-width: 1280px) {
- /* Compact toolbar at medium widths */
- .toolbar {
- gap: 4px;
- }
-}
-
-
-
-/* ========================================
- RESIZE DIVIDER - Story 1.3
- ======================================== */
-
-.resize-divider {
- width: 8px;
- background-color: transparent;
- cursor: col-resize;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- position: relative;
- z-index: 10;
- transition: background-color 0.2s ease;
-}
-
-.resize-divider:hover {
- background-color: var(--button-hover);
-}
-
-.resize-divider.dragging {
- background-color: var(--accent-color);
-}
-
-.resize-divider-handle {
- width: 2px;
- height: 40px;
- background-color: var(--border-color);
- border-radius: 2px;
- transition: background-color 0.2s ease, width 0.2s ease;
-}
-
-.resize-divider:hover .resize-divider-handle,
-.resize-divider.dragging .resize-divider-handle {
- background-color: var(--accent-color);
- width: 3px;
-}
-
-/* Hide divider in single-pane modes */
-.content-container.view-editor-only .resize-divider,
-.content-container.view-preview-only .resize-divider {
- display: none;
-}
-
-
-
-/* Prevent text selection during drag */
-.resizing {
- user-select: none;
- cursor: col-resize !important;
-}
-
-.resizing * {
- cursor: col-resize !important;
-}
-
-.resizing #markdown-preview,
-.resizing #markdown-editor,
-.resizing .line-numbers {
- pointer-events: none !important;
-}
-
-/* ========================================
- MOBILE VIEW MODE CONTROLS - Story 1.4
- ======================================== */
-
-.mobile-view-mode-group {
- display: flex;
- gap: 0;
- border-bottom: 1px solid var(--border-color);
- padding-bottom: 0.75rem;
-}
-
-.mobile-view-mode-btn {
- flex: 1;
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- color: var(--text-color);
- padding: 8px 12px;
- font-size: 14px;
- cursor: pointer;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 4px;
- transition: all 0.2s ease;
-}
-
-.mobile-view-mode-btn:first-child {
- border-radius: 6px 0 0 6px;
-}
-
-.mobile-view-mode-btn:last-child {
- border-radius: 0 6px 6px 0;
-}
-
-.mobile-view-mode-btn:not(:last-child) {
- border-right: none;
-}
-
-.mobile-view-mode-btn:hover,
-.mobile-view-mode-btn:active {
- background-color: var(--button-hover);
-}
-
-.mobile-view-mode-btn.active {
- background-color: var(--button-bg);
- border-color: var(--accent-color);
- color: var(--accent-color);
- border-width: 2px;
- padding: 7px 11px;
-}
-
-.mobile-view-mode-btn.active:not(:last-child) {
- border-right: 2px solid var(--accent-color);
-}
-
-.mobile-view-mode-btn i {
- font-size: 18px;
-}
-
-.mobile-view-mode-btn span {
- font-size: 12px;
-}
-
-/* ========================================
- RESPONSIVE VIEW MODE FIXES - Story 1.5
- ======================================== */
-
-
-
-/* ========================================
- PDF EXPORT TABLE FIX - Rowspan/Colspan
- ======================================== */
-
-/* Fix for html2canvas not properly rendering rowspan/colspan cells.
- Apply backgrounds to cells instead of rows to prevent row backgrounds
- from painting over rowspan cells during canvas capture. */
-.pdf-export table tr {
- background-color: transparent !important;
-}
-
-.pdf-export table th,
-.pdf-export table td {
- background-color: var(--table-bg, #ffffff);
- position: relative;
-}
-
-.pdf-export table tr:nth-child(2n) th,
-.pdf-export table tr:nth-child(2n) td {
- background-color: var(--bg-color, #f6f8fa);
-}
-
-/* Ensure rowspan cells render correctly */
-.pdf-export table th[rowspan],
-.pdf-export table td[rowspan] {
- vertical-align: middle;
- background-color: var(--table-bg, #ffffff) !important;
-}
-
-/* Ensure colspan cells render correctly */
-.pdf-export table th[colspan],
-.pdf-export table td[colspan] {
- text-align: center;
-}
-
-/* Dark mode PDF export table fix */
-[data-theme="dark"] .pdf-export table th,
-[data-theme="dark"] .pdf-export table td {
- background-color: var(--table-bg, #161b22);
-}
-
-[data-theme="dark"] .pdf-export table tr:nth-child(2n) th,
-[data-theme="dark"] .pdf-export table tr:nth-child(2n) td {
- background-color: #1c2128;
-}
-
-[data-theme="dark"] .pdf-export table th[rowspan],
-[data-theme="dark"] .pdf-export table td[rowspan] {
- background-color: var(--table-bg, #161b22) !important;
-}
-
-/* ========================================
- MERMAID DIAGRAM TOOLBAR
- ======================================== */
-
-.mermaid-container {
- position: relative;
-}
-
-.mermaid-toolbar {
- position: absolute;
- top: 8px;
- right: 8px;
- display: flex;
- gap: 4px;
- opacity: 0;
- transition: opacity 0.2s ease;
- z-index: 10;
-}
-
-.mermaid-container:hover .mermaid-toolbar {
- opacity: 1;
-}
-
-.mermaid-toolbar-btn {
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- color: var(--text-color);
- border-radius: 4px;
- padding: 4px 7px;
- font-size: 13px;
- cursor: pointer;
- display: flex;
- align-items: center;
- gap: 3px;
- transition: background-color 0.2s ease, color 0.2s ease;
- white-space: nowrap;
-}
-
-.mermaid-toolbar-btn:hover {
- background-color: var(--button-hover);
- color: var(--accent-color);
-}
-
-.mermaid-toolbar-btn:active {
- background-color: var(--button-active);
-}
-
-.mermaid-toolbar-btn i {
- font-size: 14px;
-}
-
-/* ========================================
- MERMAID ZOOM MODAL
- ======================================== */
-
-#mermaid-zoom-modal {
- display: none;
- position: fixed;
- inset: 0;
- z-index: 2000;
- background-color: rgba(0, 0, 0, 0.75);
- align-items: center;
- justify-content: center;
-}
-
-#mermaid-zoom-modal.active {
- display: flex;
-}
-
-.mermaid-modal-content {
- background-color: var(--bg-color);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- padding: 16px;
- width: 85vw;
- height: 85vh;
- max-width: 85vw;
- max-height: 85vh;
- display: flex;
- flex-direction: column;
- gap: 12px;
-}
-
-@media (max-width: 576px) {
- .mermaid-modal-content {
- width: 95vw;
- height: 90vh;
- max-width: 95vw;
- max-height: 90vh;
- padding: 10px;
- }
-}
-
-.mermaid-modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.mermaid-modal-header span {
- font-weight: 600;
- font-size: 15px;
- color: var(--text-color);
-}
-
-.mermaid-modal-close {
- background: none;
- border: none;
- color: var(--text-color);
- font-size: 1.2rem;
- cursor: pointer;
- padding: 2px 6px;
- border-radius: 4px;
- display: flex;
- align-items: center;
- transition: background-color 0.2s ease;
-}
-
-.mermaid-modal-close:hover {
- background-color: var(--button-hover);
-}
-
-.mermaid-modal-diagram {
- overflow: auto;
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 200px;
- cursor: grab;
-}
-
-.mermaid-modal-diagram.dragging {
- cursor: grabbing;
-}
-
-.mermaid-modal-diagram svg {
- transform-origin: center;
- transition: transform 0.1s ease;
- max-width: none;
-}
-
-.mermaid-modal-controls {
- display: flex;
- justify-content: center;
- gap: 8px;
- flex-wrap: wrap;
-}
-
-.mermaid-modal-controls .mermaid-toolbar-btn {
- opacity: 1;
-}
-
-/* ========================================
- DOCUMENT TABS & SESSION MANAGEMENT
- ======================================== */
-
-.tab-bar {
- display: flex;
- align-items: center;
- background-color: var(--header-bg);
- border-bottom: 1px solid var(--border-color);
- height: 32px;
- overflow: visible; /* ā was: overflow: hidden */
- flex-shrink: 0;
- padding: 0 4px;
- gap: 0;
- user-select: none;
- position: relative;
- z-index: 10;
-}
-
-.tab-list {
- display: flex;
- align-items: flex-end;
- overflow-x: auto;
- overflow-y: visible; /* ā was: overflow-y: hidden */
- flex: 1;
- height: 100%;
- scrollbar-width: none;
- -ms-overflow-style: none;
-}
-
-.tab-list::-webkit-scrollbar {
- display: none;
-}
-
-.tab-item {
- display: flex;
- align-items: center;
- gap: 6px;
- height: 32px;
- padding: 0 8px 0 10px;
- min-width: 100px;
- max-width: 180px;
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- border-bottom: 1px solid transparent;
- border-radius: 6px 6px 0 0;
- cursor: pointer;
- font-size: 13px;
- color: var(--text-color);
- white-space: nowrap;
- /* overflow: hidden; <-- REMOVE THIS */
- position: relative;
- transition: background-color 0.15s ease, color 0.15s ease;
- flex-shrink: 0;
- margin-right: 2px;
- opacity: 0.7;
-}
-
-.tab-item:hover {
- background-color: var(--button-hover);
- opacity: 0.9;
-}
-
-.tab-item.active {
- background-color: var(--bg-color);
- border-color: var(--border-color);
- color: var(--accent-color);
- border-bottom: 1px solid var(--bg-color);
- opacity: 1;
- z-index: 2;
-}
-
-.tab-item.unsaved::after {
- content: '';
- display: inline-block;
- width: 6px;
- height: 6px;
- background-color: var(--accent-color);
- border-radius: 50%;
- flex-shrink: 0;
- margin-left: 2px;
-}
-
-.tab-title {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- flex: 1;
- min-width: 0;
-}
-
-.tab-close-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 16px;
- height: 16px;
- border-radius: 3px;
- background: none;
- border: none;
- color: var(--text-color);
- cursor: pointer;
- padding: 0;
- font-size: 11px;
- opacity: 0;
- flex-shrink: 0;
- transition: background-color 0.15s ease, opacity 0.15s ease;
-}
-
-.tab-item:hover .tab-close-btn,
-.tab-item.active .tab-close-btn {
- opacity: 0.6;
-}
-
-.tab-close-btn:hover {
- background-color: var(--button-active);
- opacity: 1 !important;
- color: var(--color-danger-fg, #d73a49);
-}
-
-.tab-new-btn {
- display: flex;
- align-items: center;
- gap: 4px;
- height: 24px;
- padding: 0 8px;
- border-radius: 5px;
- background: none;
- border: 1px solid var(--border-color);
- color: var(--text-color);
- cursor: pointer;
- font-size: 12px;
- flex-shrink: 0;
- margin-left: 6px;
- align-self: center;
- transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
-}
-
-.tab-new-btn:hover {
- background-color: rgba(46, 160, 67, 0.1);
- border-color: var(--accent-color, #2ea043);
- color: var(--accent-color, #2ea043);
-}
-
-.tab-new-btn:active {
- background-color: rgba(46, 160, 67, 0.2);
-}
-
-/* Drag-and-drop visual feedback */
-.tab-item.dragging {
- opacity: 0.4;
-}
-
-.tab-item.drag-over {
- border-left: 2px solid var(--accent-color);
-}
-
-/* Tab enter animation */
-@keyframes tabSlideIn {
- from { opacity: 0; transform: translateY(4px); }
- to { opacity: 0.7; transform: translateY(0); }
-}
-
-.tab-item {
- animation: tabSlideIn 0.12s ease forwards;
-}
-
-.tab-item.active {
- animation: none;
-}
-
-/* Hide tab bar on very small screens ā single-file use */
-@media (max-width: 480px) {
- .tab-bar {
- display: none;
- }
-}
-
-/* ========================================
- TAB OVERFLOW ā Scroll Buttons & Fade Indicators
- ======================================== */
-
-.tab-scroll-btn {
- display: none;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- border-radius: 4px;
- background: none;
- border: 1px solid transparent;
- color: var(--text-color);
- cursor: pointer;
- font-size: 14px;
- flex-shrink: 0;
- padding: 0;
- transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
- z-index: 2;
- opacity: 0.6;
-}
-
-.tab-scroll-btn:hover {
- background-color: var(--button-hover);
- border-color: var(--border-color);
- opacity: 1;
-}
-
-.tab-scroll-btn:active {
- background-color: var(--button-active);
-}
-
-/* Show scroll buttons only when overflow exists */
-.tab-bar.has-overflow-left .tab-scroll-left,
-.tab-bar.has-overflow-right .tab-scroll-right {
- display: flex;
-}
-
-/* Overflow fade indicators ā subtle gradient at clipped edges */
-.tab-list::before,
-.tab-list::after {
- content: '';
- position: sticky;
- top: 0;
- bottom: 0;
- width: 0;
- flex-shrink: 0;
- pointer-events: none;
- z-index: 3;
- transition: box-shadow 0.2s ease;
-}
-
-.tab-list::before {
- left: 0;
-}
-
-.tab-list::after {
- right: 0;
-}
-
-.tab-bar.has-overflow-left .tab-list::before {
- box-shadow: 8px 0 12px -4px rgba(0, 0, 0, 0.12);
-}
-
-.tab-bar.has-overflow-right .tab-list::after {
- box-shadow: -8px 0 12px -4px rgba(0, 0, 0, 0.12);
-}
-
-[data-theme="dark"] .tab-bar.has-overflow-left .tab-list::before {
- box-shadow: 8px 0 12px -4px rgba(0, 0, 0, 0.35);
-}
-
-[data-theme="dark"] .tab-bar.has-overflow-right .tab-list::after {
- box-shadow: -8px 0 12px -4px rgba(0, 0, 0, 0.35);
-}
-
-/* ========================================
- THREE-DOT TAB MENU
- ======================================== */
-
-.tab-menu-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 22px;
- height: 22px;
- border-radius: 3px;
- background: none;
- border: none;
- color: var(--text-color);
- cursor: pointer;
- padding: 0;
- font-size: 14px;
- font-weight: bold;
- letter-spacing: 1px;
- opacity: 0.65;
- flex-shrink: 0;
- transition: background-color 0.15s ease, opacity 0.15s ease;
- position: relative;
-}
-
-/* Touch Hitbox Expansion for Tab Menu Button */
-.tab-menu-btn::before {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 48px;
- height: 48px;
-}
-
-/* Touch-optimized styling for coarser pointers (e.g., smartphones & tablets) */
-@media (pointer: coarse) {
- .tab-bar {
- height: 40px !important;
- }
- .tab-item {
- height: 40px !important;
- font-size: 14px !important;
- padding: 0 10px 0 12px !important;
- gap: 8px !important;
- }
- .tab-new-btn,
- .tab-reset-btn {
- height: 32px !important;
- font-size: 14px !important;
- padding: 0 12px !important;
- }
- .tab-scroll-btn {
- width: 32px !important;
- height: 32px !important;
- font-size: 18px !important;
- }
- .tab-menu-btn {
- width: 30px !important;
- height: 30px !important;
- font-size: 18px !important;
- }
- .tab-close-btn {
- width: 20px !important;
- height: 20px !important;
- font-size: 13px !important;
- opacity: 0.8 !important;
- }
-}
-
-.tab-item:hover .tab-menu-btn,
-.tab-item.active .tab-menu-btn {
- opacity: 0.65;
-}
-
-.tab-menu-btn:hover {
- background-color: var(--button-active);
- opacity: 1 !important;
-}
-
-.tab-menu-dropdown {
- display: none;
- position: fixed;
- min-width: 130px;
- background-color: var(--header-bg);
- border: 1px solid var(--border-color);
- border-radius: 6px;
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
- z-index: 99999;
- overflow: hidden;
- flex-direction: column;
-}
-
-.tab-menu-dropdown.open {
- display: flex;
-}
-
-.tab-menu-item {
- display: flex;
- align-items: center;
- gap: 7px;
- padding: 7px 12px;
- background: none;
- border: none;
- color: var(--text-color);
- font-size: 12px;
- cursor: pointer;
- text-align: left;
- transition: background-color 0.12s ease;
- white-space: nowrap;
-}
-
-.tab-menu-item:hover {
- background-color: var(--button-hover);
-}
-
-.tab-menu-item-danger {
- color: var(--color-danger-fg, #d73a49);
-}
-
-.tab-menu-item-danger:hover {
- background-color: rgba(215, 58, 73, 0.1);
-}
-
-/* ========================================
- RESET BUTTON
- ======================================== */
-
-.tab-reset-btn {
- display: flex;
- align-items: center;
- gap: 4px;
- height: 24px;
- padding: 0 8px;
- border-radius: 5px;
- background: none;
- border: 1px solid var(--border-color);
- color: var(--text-color);
- cursor: pointer;
- font-size: 12px;
- flex-shrink: 0;
- margin-left: 6px;
- transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
-}
-
-.tab-reset-btn:hover {
- background-color: rgba(215, 58, 73, 0.1);
- border-color: var(--color-danger-fg, #d73a49);
- color: var(--color-danger-fg, #d73a49);
-}
-
-/* ========================================
- RESET & RENAME CONFIRMATION MODALS
- ======================================== */
-
-.reset-modal-overlay {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.45);
- z-index: 2000;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.reset-modal-overlay.modal-overlay {
- opacity: 0;
- visibility: hidden;
- transition: opacity 0.2s ease, visibility 0.2s ease;
-}
-
-.reset-modal-overlay.modal-overlay.is-visible {
- opacity: 1;
- visibility: visible;
-}
-
-.reset-modal-box {
- background: var(--header-bg);
- border: 1px solid var(--border-color);
- border-radius: 10px;
- padding: 24px 28px;
- min-width: 280px;
- max-width: 360px;
- box-shadow: 0 8px 32px rgba(0,0,0,0.25);
- display: flex;
- flex-direction: column;
- gap: 16px;
-}
-
-.modal-box {
- max-height: min(85vh, 760px);
- opacity: 0;
- transform: translateY(8px);
- transition: transform 0.2s ease, opacity 0.2s ease;
-}
-
-.reset-modal-overlay.modal-overlay.is-visible .modal-box {
- opacity: 1;
- transform: translateY(0);
-}
-
-.modal-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
-}
-
-.modal-header .reset-modal-message {
- text-align: left;
- flex: 1;
-}
-
-.modal-close-btn {
- border: 1px solid var(--border-color);
- background: var(--button-bg);
- color: var(--text-color);
- border-radius: 6px;
- width: 28px;
- height: 28px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- transition: background-color 0.15s ease;
-}
-
-.modal-close-btn:hover {
- background-color: var(--button-hover);
-}
-
-.modal-body {
- display: flex;
- flex-direction: column;
- gap: 16px;
- max-height: min(60vh, 520px);
- overflow: auto;
- padding-right: 4px;
-}
-
-.modal-section {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.modal-section-title {
- margin: 0;
- font-size: 0.95rem;
- font-weight: 600;
-}
-
-.modal-list {
- margin: 0;
- padding-left: 1.1rem;
- display: flex;
- flex-direction: column;
- gap: 6px;
- font-size: 0.85rem;
-}
-
-.modal-list a {
- color: var(--accent-color);
- text-decoration: none;
-}
-
-.modal-list a:hover {
- text-decoration: underline;
-}
-
-.modal-subtext {
- margin: 0;
- font-size: 12px;
- color: var(--text-secondary, #57606a);
- line-height: 1.4;
-}
-
-.find-replace-meta {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
-}
-
-.find-match-count {
- font-size: 12px;
- color: var(--text-secondary, #57606a);
-}
-
-.find-replace-nav {
- display: inline-flex;
- gap: 6px;
-}
-
-.find-nav-btn {
- width: 28px;
- height: 28px;
- padding: 0;
-}
-
-.about-header {
- display: flex;
- align-items: center;
- gap: 16px;
- flex-wrap: wrap;
-}
-
-.about-logo {
- width: 64px;
- height: 64px;
- border-radius: 12px;
- border: 1px solid var(--border-color);
- object-fit: cover;
-}
-
-.about-details {
- display: flex;
- flex-direction: column;
- gap: 6px;
-}
-
-.about-title {
- margin: 0;
- font-size: 1.05rem;
- font-weight: 600;
-}
-
-.about-description {
- margin: 0;
- font-size: 0.85rem;
- color: var(--text-secondary, #57606a);
-}
-
-.about-meta {
- margin: 0;
- font-size: 0.78rem;
- color: var(--text-secondary, #57606a);
-}
-
-.modal-body kbd {
- padding: 2px 6px;
- border-radius: 4px;
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- font-size: 0.75rem;
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
-}
-
-.reset-modal-box--wide {
- width: min(92vw, 640px);
- max-width: 640px;
-}
-
-.reset-modal-message {
- margin: 0;
- font-size: 14px;
- color: var(--text-color);
- font-weight: 500;
- text-align: center;
-}
-
-.reset-modal-actions {
- display: flex;
- gap: 10px;
- justify-content: flex-end;
-}
-
-.reset-modal-btn {
- padding: 6px 16px;
- border-radius: 6px;
- border: 1px solid var(--border-color);
- background: var(--button-bg);
- color: var(--text-color);
- font-size: 13px;
- cursor: pointer;
- transition: background-color 0.15s ease;
-}
-
-.reset-modal-btn:hover {
- background-color: var(--button-hover);
-}
-
-.reset-modal-confirm {
- background-color: var(--color-danger-fg, #d73a49);
- border-color: var(--color-danger-fg, #d73a49);
- color: #fff;
-}
-
-.reset-modal-confirm:hover {
- background-color: #b02a37;
- border-color: #b02a37;
-}
-
+:root {
+ --bg-color: #ffffff;
+ --editor-bg: #f6f8fa;
+ --preview-bg: #ffffff; /* Preview background for light mode */
+ --text-color: #24292e;
+ --text-secondary: #57606a;
+ --font-mono: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
+ --color-danger-fg: #d73a49;
+ --preview-text-color: #24292e; /* Text color for preview in light mode */
+ --border-color: #e1e4e8;
+ --header-bg: #f6f8fa;
+ --button-bg: #f6f8fa;
+ --button-hover: #e1e4e8;
+ --button-active: #d1d5da;
+ --scrollbar-thumb: #c1c1c1;
+ --scrollbar-track: #f1f1f1;
+ --accent-color: #0366d6;
+ --table-bg: #ffffff; /* Table background for light mode */
+ --code-bg: #f6f8fa; /* Code block background for light mode */
+ --skeleton-bg: #e2e8f0;
+ --skeleton-glow: rgba(255, 255, 255, 0.65);
+
+ /* Find & Replace Panel custom properties (PERF-010 consolidated) */
+ --fr-bg: rgba(255, 255, 255, 0.95);
+ --fr-border: #d0d7de;
+ --fr-shadow: 0 8px 24px rgba(140, 149, 159, 0.2);
+ --fr-btn-active: #0969da;
+ --fr-btn-active-bg: #ddf4ff;
+ --fr-match-highlight: #ffdf5d;
+ --fr-match-active: #ff9b30;
+ --fr-error-bg: #ffebe9;
+ --fr-error-border: #ff8577;
+ --fr-text-danger: #cf222e;
+}
+
+[data-theme="dark"] {
+ --bg-color: #0d1117;
+ --editor-bg: #161b22;
+ --preview-bg: #0d1117; /* Preview background for dark mode */
+ --text-color: #c9d1d9;
+ --text-secondary: #8b949e;
+ --color-danger-fg: #f85149;
+ --preview-text-color: #c9d1d9; /* Text color for preview in dark mode */
+ --border-color: #30363d;
+ --header-bg: #161b22;
+ --button-bg: #21262d;
+ --button-hover: #30363d;
+ --button-active: #3b434b;
+ --scrollbar-thumb: #484f58;
+ --scrollbar-track: #21262d;
+ --accent-color: #58a6ff;
+ --table-bg: #161b22; /* Table background for dark mode */
+ --code-bg: #161b22; /* Code block background for dark mode */
+ --skeleton-bg: #2d3139;
+ --skeleton-glow: rgba(255, 255, 255, 0.08);
+
+ /* Find & Replace Panel custom properties for dark mode */
+ --fr-bg: rgba(28, 33, 40, 0.98);
+ --fr-border: #444c56;
+ --fr-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
+ --fr-btn-active: #2f81f7;
+ --fr-btn-active-bg: rgba(56, 139, 253, 0.15);
+ --fr-match-highlight: rgba(187, 128, 9, 0.4);
+ --fr-match-active: #f1e05a;
+ --fr-error-bg: rgba(248, 81, 73, 0.1);
+ --fr-error-border: rgba(248, 81, 73, 0.4);
+ --fr-text-danger: #ff7b72;
+}
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+@media (min-width: 768px) {
+ html,
+ body {
+ height: 100%;
+ overflow: hidden;
+ }
+}
+
+body {
+ background-color: var(--bg-color);
+ color: var(--text-color);
+ /* PERF-021: Removed background-color transition to avoid full-viewport repaint on theme toggle */
+ transition: color 0.15s ease;
+ min-height: 100vh;
+ font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Hiragino Kaku Gothic ProN", Meiryo, "Malgun Gothic", "Apple SD Gothic Neo", "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
+}
+
+.app-header {
+ background-color: var(--header-bg);
+ border-bottom: 1px solid var(--border-color);
+ padding: 0.35rem 0.75rem;
+ transition: background-color 0.3s ease;
+ position: relative;
+ z-index: 100;
+ flex-shrink: 0;
+}
+
+.app-container {
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.content-container {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+}
+
+.editor-pane, .preview-pane {
+ flex: 1;
+ padding: 20px;
+ overflow-y: auto;
+ position: relative;
+ /* PERF-025: Shortened transition and scoped to background-color only */
+ transition: background-color 0.15s ease;
+}
+
+.editor-pane {
+ background-color: var(--editor-bg);
+ border-right: 1px solid var(--border-color);
+ padding-right: 0px;
+ --line-number-gutter: 0px;
+}
+
+.preview-pane {
+ background-color: var(--preview-bg); /* Using the new variable for preview background */
+}
+
+/* Custom scrollbar */
+.editor-pane::-webkit-scrollbar,
+.preview-pane::-webkit-scrollbar,
+#markdown-editor::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+.editor-pane::-webkit-scrollbar-track,
+.preview-pane::-webkit-scrollbar-track,
+#markdown-editor::-webkit-scrollbar-track {
+ background: var(--scrollbar-track);
+}
+
+.editor-pane::-webkit-scrollbar-thumb,
+.preview-pane::-webkit-scrollbar-thumb,
+#markdown-editor::-webkit-scrollbar-thumb {
+ background: var(--scrollbar-thumb);
+ border-radius: 4px;
+}
+
+.editor-pane::-webkit-scrollbar-thumb:hover,
+.preview-pane::-webkit-scrollbar-thumb:hover,
+#markdown-editor::-webkit-scrollbar-thumb:hover {
+ background: var(--button-active);
+}
+
+#markdown-editor {
+ width: 100%;
+ height: 100%;
+ border: none;
+ background-color: transparent;
+ color: var(--text-color);
+ resize: none;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+ font-size: 14px;
+ line-height: 1.5;
+ padding: 10px;
+ padding-left: calc(10px + var(--line-number-gutter));
+ transition: background-color 0.3s ease, color 0.3s ease;
+ overflow-y: auto;
+ position: relative;
+ z-index: 3;
+}
+
+#markdown-editor:focus {
+ outline: none;
+}
+
+.preview-pane {
+ padding: 20px;
+}
+
+.markdown-body {
+ padding: 20px;
+ width: 100%;
+ background-color: var(--preview-bg); /* Ensuring the markdown content matches preview background */
+ color: var(--preview-text-color); /* Using specific text color for preview content */
+}
+
+.markdown-body a.reference-link {
+ font-size: 0.75em;
+ letter-spacing: -0.02em;
+ line-height: 1;
+ vertical-align: super;
+ position: relative;
+ top: 0.08em;
+}
+
+/* Style tables in light mode */
+.markdown-body table {
+ background-color: var(--table-bg);
+ border-color: var(--border-color);
+}
+
+.markdown-body table tr {
+ background-color: var(--table-bg);
+ border-top: 1px solid var(--border-color);
+}
+
+.markdown-body table tr:nth-child(2n) {
+ background-color: var(--bg-color);
+}
+
+/* Style code blocks in light mode */
+.markdown-body pre {
+ background-color: var(--code-bg);
+ border-radius: 6px;
+}
+
+.markdown-body code {
+ background-color: var(--code-bg);
+ border-radius: 3px;
+ padding: 0.2em 0.4em;
+}
+
+.markdown-body img.emoji-inline {
+ width: 1em;
+ height: 1em;
+ vertical-align: -0.1em;
+}
+
+.markdown-body ul,
+.markdown-body ol {
+ padding-left: 2em;
+ margin: 0.4em 0;
+}
+
+.markdown-body ul ul,
+.markdown-body ul ol,
+.markdown-body ol ul,
+.markdown-body ol ol {
+ margin-top: 0.2em;
+ margin-bottom: 0.2em;
+}
+
+.markdown-body ul.contains-task-list,
+.markdown-body li.task-list-item {
+ list-style: none;
+}
+
+.markdown-body ul.contains-task-list {
+ padding-left: 2em;
+}
+
+.markdown-body li.task-list-item input[type="checkbox"] {
+ margin: 0 0.5em 0.2em 0;
+ vertical-align: middle;
+ pointer-events: none;
+}
+
+.markdown-body li.task-list-item::marker {
+ content: "";
+}
+
+.markdown-body li:has(> input[type="checkbox"]) {
+ list-style: none;
+}
+
+.markdown-body li:has(> input[type="checkbox"])::marker {
+ content: "";
+}
+
+.markdown-body ul:has(> li > input[type="checkbox"]) {
+ list-style: none;
+ padding-left: 2em;
+}
+
+.markdown-body .footnotes {
+ margin-top: 1.5rem;
+ font-size: 0.9em;
+}
+
+.markdown-body .footnotes ol {
+ padding-left: 1.5em;
+}
+
+.markdown-body .footnotes ol > li::marker {
+ content: "[" counter(list-item) "] ";
+ font-weight: 600;
+}
+
+.markdown-body .footnotes li > p {
+ margin: 0.2em 0;
+}
+
+.markdown-body .footnote-ref a,
+.markdown-body .footnote-backref {
+ text-decoration: none;
+}
+
+.markdown-body .footnote-backref {
+ margin-left: 0.4em;
+}
+
+.markdown-body .markdown-alert {
+ padding: 0.5rem 1rem;
+ margin-bottom: 16px;
+ border-left: 0.25em solid;
+ border-radius: 0.375rem;
+}
+
+.markdown-body .markdown-alert > :last-child {
+ margin-bottom: 0;
+}
+
+.markdown-body .markdown-alert-title {
+ margin: 0 0 8px;
+ font-weight: 600;
+ line-height: 1.25;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.markdown-body .markdown-alert-icon {
+ display: inline-flex;
+ width: 16px;
+ height: 16px;
+}
+
+.markdown-body .markdown-alert-icon svg {
+ width: 16px;
+ height: 16px;
+ fill: currentColor;
+}
+
+.markdown-body .markdown-alert-note {
+ color: #0969da;
+ border-left-color: #0969da;
+ background-color: #ddf4ff;
+}
+
+.markdown-body .markdown-alert-tip {
+ color: #1a7f37;
+ border-left-color: #1a7f37;
+ background-color: #dafbe1;
+}
+
+.markdown-body .markdown-alert-important {
+ color: #8250df;
+ border-left-color: #8250df;
+ background-color: #fbefff;
+}
+
+.markdown-body .markdown-alert-warning {
+ color: #9a6700;
+ border-left-color: #9a6700;
+ background-color: #fff8c5;
+}
+
+.markdown-body .markdown-alert-caution {
+ color: #cf222e;
+ border-left-color: #cf222e;
+ background-color: #ffebe9;
+}
+
+.markdown-body .markdown-alert > *:not(.markdown-alert-title) {
+ color: var(--preview-text-color);
+}
+
+[data-theme="dark"] .markdown-body .markdown-alert-note {
+ color: #4493f8;
+ background-color: rgba(31, 111, 235, 0.15);
+ border-left-color: #4493f8;
+}
+
+[data-theme="dark"] .markdown-body .markdown-alert-tip {
+ color: #3fb950;
+ background-color: rgba(35, 134, 54, 0.15);
+ border-left-color: #3fb950;
+}
+
+[data-theme="dark"] .markdown-body .markdown-alert-important {
+ color: #ab7df8;
+ background-color: rgba(137, 87, 229, 0.15);
+ border-left-color: #ab7df8;
+}
+
+[data-theme="dark"] .markdown-body .markdown-alert-warning {
+ color: #d29922;
+ background-color: rgba(210, 153, 34, 0.18);
+ border-left-color: #d29922;
+}
+
+[data-theme="dark"] .markdown-body .markdown-alert-caution {
+ color: #f85149;
+ background-color: rgba(248, 81, 73, 0.18);
+ border-left-color: #f85149;
+}
+
+.toolbar {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.toolbar-group {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.toolbar-divider {
+ width: 1px;
+ height: 20px;
+ background-color: var(--border-color);
+ opacity: 0.7;
+}
+
+.tool-button {
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ color: var(--text-color);
+ border-radius: 5px;
+ padding: 4px 8px;
+ font-size: 13px;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ /* PERF-016: Specific transition properties instead of 'all' to avoid animating layout-triggering properties */
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
+}
+
+.tool-button:hover {
+ background-color: var(--button-hover);
+}
+
+.tool-button:active {
+ background-color: var(--button-active);
+}
+
+.tool-button:disabled,
+.tool-button[aria-disabled="true"] {
+ cursor: not-allowed;
+ opacity: 0.5;
+}
+
+.tool-button i {
+ font-size: 15px;
+}
+
+.tool-button.is-active,
+.tool-button.is-active:hover {
+ border-color: var(--accent-color);
+ color: var(--accent-color);
+ background-color: rgba(3, 102, 214, 0.08);
+}
+
+.btn-text {
+ display: none;
+}
+
+.toolbar .tool-button {
+ height: 28px;
+ min-width: 28px;
+}
+
+.toolbar .tool-button.sync-active {
+ border-color: var(--accent-color);
+ color: var(--accent-color);
+}
+
+.file-input {
+ display: none;
+}
+
+/* Drag overlay: full-screen drop target shown when user drags a file over the window */
+.drag-overlay {
+ display: none;
+ position: fixed;
+ inset: 0;
+ z-index: 9999;
+ background-color: rgba(0, 0, 0, 0.45);
+ pointer-events: none;
+ align-items: center;
+ justify-content: center;
+}
+
+.drag-overlay.active {
+ display: flex;
+ pointer-events: auto;
+}
+
+.drag-overlay-inner {
+ border: 3px dashed var(--accent-color);
+ border-radius: 12px;
+ padding: 48px 64px;
+ text-align: center;
+ color: #ffffff;
+ background-color: rgba(3, 102, 214, 0.15);
+ animation: overlayPulse 1.4s ease-in-out infinite;
+}
+
+.drag-overlay-icon {
+ display: block;
+ font-size: 3rem;
+ margin-bottom: 12px;
+ color: var(--accent-color);
+}
+
+.drag-overlay-text {
+ font-size: 1.4rem;
+ font-weight: 600;
+ margin-bottom: 4px;
+}
+
+.drag-overlay-sub {
+ font-size: 0.85rem;
+ opacity: 0.75;
+ margin-bottom: 0;
+}
+
+@keyframes overlayPulse {
+ 0%, 100% { transform: scale(1); }
+ 50% { transform: scale(1.015); }
+}
+
+/* Editor drop hint: subtle text at bottom of editor pane, shown only when empty */
+.drop-hint {
+ position: absolute;
+ bottom: 14px;
+ left: 0;
+ right: 0;
+ text-align: center;
+ font-size: 0.75rem;
+ color: var(--text-color);
+ opacity: 0.35;
+ pointer-events: none;
+ user-select: none;
+ z-index: 3;
+}
+
+.editor-pane:has(#markdown-editor:not(:placeholder-shown)) .drop-hint {
+ display: none;
+}
+
+.line-numbers {
+ position: absolute;
+ top: 20px;
+ bottom: 20px;
+ left: 20px;
+ width: var(--line-number-gutter);
+ padding: 10px 8px 10px 0;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+ font-size: 14px;
+ line-height: 1.5;
+ text-align: right;
+ color: var(--text-secondary);
+ background-color: var(--editor-bg);
+ border-right: 1px solid var(--border-color);
+ box-sizing: border-box;
+ overflow: hidden;
+ pointer-events: none;
+ user-select: none;
+ z-index: 2;
+ font-variant-numeric: tabular-nums;
+}
+
+.line-numbers .line-number {
+ display: block;
+ height: auto;
+}
+
+.editor-highlight-layer {
+ position: absolute;
+ inset: 20px 0 20px calc(20px + var(--line-number-gutter));
+ padding: 10px;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+ font-size: 14px;
+ line-height: 1.5;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ color: transparent;
+ pointer-events: none;
+ overflow: auto;
+ background-color: var(--editor-bg);
+ border-radius: 4px;
+ z-index: 1;
+}
+
+.editor-highlight-layer::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+.editor-highlight-layer::-webkit-scrollbar-thumb {
+ background: transparent;
+}
+
+.editor-highlight-layer::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.find-highlight {
+ background-color: var(--fr-match-highlight, rgba(255, 223, 93, 0.4)) !important;
+ border-radius: 2px;
+ color: transparent !important;
+}
+
+.find-highlight.active {
+ background-color: var(--fr-match-active, #ff9b30) !important;
+ color: transparent !important;
+}
+
+/* Dropdown improvements */
+.dropdown-menu {
+ background-color: var(--bg-color);
+ border-color: var(--border-color);
+}
+
+.dropdown-item {
+ color: var(--text-color);
+}
+
+.dropdown-item:hover, .dropdown-item:focus {
+ background-color: var(--button-hover);
+ color: var(--text-color);
+}
+
+/* Markdown formatting toolbar */
+.markdown-format-toolbar {
+ display: flex;
+ align-items: center;
+ height: 34px;
+ padding: 0 6px;
+ background-color: var(--header-bg);
+ border-bottom: 1px solid var(--border-color);
+ overflow-x: auto;
+ overflow-y: hidden;
+ flex-shrink: 0;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+
+.markdown-format-toolbar::-webkit-scrollbar {
+ display: none;
+}
+
+.markdown-toolbar-group {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ height: 100%;
+ padding: 0 6px;
+ border-right: 1px solid var(--border-color);
+ flex-shrink: 0;
+}
+
+.markdown-toolbar-group:first-child {
+ padding-left: 0;
+}
+
+.markdown-toolbar-group:last-child {
+ border-right: none;
+ padding-right: 0;
+}
+
+.markdown-tool-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 26px;
+ height: 26px;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ background: transparent;
+ color: var(--text-color);
+ cursor: pointer;
+ font-size: 14px;
+ line-height: 1;
+ padding: 0;
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
+}
+
+.markdown-tool-btn:hover,
+.markdown-tool-btn:focus-visible {
+ background-color: var(--button-hover);
+ border-color: var(--border-color);
+ color: var(--accent-color);
+}
+
+.markdown-tool-btn:active {
+ background-color: var(--button-active);
+}
+
+.markdown-tool-btn:disabled,
+.markdown-tool-btn.disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+ pointer-events: none;
+}
+
+.markdown-tool-btn i {
+ font-size: 15px;
+}
+
+.markdown-tool-btn[data-md-action="reference"] i::before {
+ content: "[ ]";
+ font-style: normal;
+ font-size: 12px;
+ letter-spacing: -0.12em;
+}
+
+.markdown-tool-btn.text-tool {
+ width: auto;
+ min-width: 26px;
+ padding: 0 5px;
+ font-weight: 600;
+ font-family: Georgia, "Times New Roman", serif;
+}
+
+.heading-group .markdown-tool-btn {
+ min-width: 30px;
+}
+
+
+
+/* Loading indicators */
+.loading {
+ opacity: 0.6;
+ pointer-events: none;
+}
+
+/* Focus outline for accessibility */
+button:focus,
+a:focus {
+ outline: 2px solid var(--accent-color);
+ outline-offset: 2px;
+}
+
+/* Animation for copied message */
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+/* Tooltip styles */
+.tooltip {
+ position: absolute;
+ background: var(--button-bg);
+ border: 1px solid var(--border-color);
+ padding: 5px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ z-index: 1000;
+ animation: fadeIn 0.2s ease;
+}
+
+/* Styles for GitHub markdown preview light mode */
+.markdown-body {
+ color-scheme: light;
+ --color-prettylights-syntax-comment: #6a737d;
+ --color-prettylights-syntax-constant: #005cc5;
+ --color-prettylights-syntax-entity: #6f42c1;
+ --color-prettylights-syntax-storage-modifier-import: #24292e;
+ --color-prettylights-syntax-entity-tag: #22863a;
+ --color-prettylights-syntax-keyword: #cf222e;
+ --color-prettylights-syntax-string: #032f62;
+ --color-prettylights-syntax-variable: #e36209;
+ --color-prettylights-syntax-brackethighlighter-unmatched: #b31d28;
+ --color-prettylights-syntax-invalid-illegal-text: #fafbfc;
+ --color-prettylights-syntax-invalid-illegal-bg: #b31d28;
+ --color-prettylights-syntax-carriage-return-text: #fafbfc;
+ --color-prettylights-syntax-carriage-return-bg: #d73a49;
+ --color-prettylights-syntax-string-regexp: #22863a;
+ --color-prettylights-syntax-markup-list: #735c0f;
+ --color-prettylights-syntax-markup-heading: #005cc5;
+ --color-prettylights-syntax-markup-italic: #24292e;
+ --color-prettylights-syntax-markup-bold: #24292e;
+ --color-prettylights-syntax-markup-deleted-text: #b31d28;
+ --color-prettylights-syntax-markup-deleted-bg: #ffeef0;
+ --color-prettylights-syntax-markup-inserted-text: #22863a;
+ --color-prettylights-syntax-markup-inserted-bg: #f0fff4;
+ --color-prettylights-syntax-markup-changed-text: #e36209;
+ --color-prettylights-syntax-markup-changed-bg: #ffebda;
+ --color-prettylights-syntax-markup-ignored-text: #f6f8fa;
+ --color-prettylights-syntax-markup-ignored-bg: #005cc5;
+ --color-prettylights-syntax-meta-diff-range: #6f42c1;
+ --color-prettylights-syntax-brackethighlighter-angle: #586069;
+ --color-prettylights-syntax-sublimelinter-gutter-mark: #e1e4e8;
+ --color-prettylights-syntax-constant-other-reference-link: #032f62;
+ --color-fg-default: #24292e;
+ --color-fg-muted: #586069;
+ --color-fg-subtle: #6a737d;
+ --color-canvas-default: #ffffff;
+ --color-canvas-subtle: #f6f8fa;
+ --color-border-default: #e1e4e8;
+ --color-border-muted: #eaecef;
+ --color-neutral-muted: rgba(175,184,193,0.2);
+ --color-accent-fg: #0366d6;
+ --color-accent-emphasis: #0366d6;
+ --color-attention-subtle: #fff5b1;
+ --color-danger-fg: #d73a49;
+}
+
+/* Styles for GitHub markdown preview dark mode */
+[data-theme="dark"] .markdown-body {
+ color-scheme: dark;
+ --color-prettylights-syntax-comment: #8b949e;
+ --color-prettylights-syntax-constant: #79c0ff;
+ --color-prettylights-syntax-entity: #d2a8ff;
+ --color-prettylights-syntax-storage-modifier-import: #c9d1d9;
+ --color-prettylights-syntax-entity-tag: #7ee787;
+ --color-prettylights-syntax-keyword: #ff7b72;
+ --color-prettylights-syntax-string: #a5d6ff;
+ --color-prettylights-syntax-variable: #ffa657;
+ --color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
+ --color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
+ --color-prettylights-syntax-invalid-illegal-bg: #8e1519;
+ --color-prettylights-syntax-carriage-return-text: #f0f6fc;
+ --color-prettylights-syntax-carriage-return-bg: #b62324;
+ --color-prettylights-syntax-string-regexp: #7ee787;
+ --color-prettylights-syntax-markup-list: #f2cc60;
+ --color-prettylights-syntax-markup-heading: #1f6feb;
+ --color-prettylights-syntax-markup-italic: #c9d1d9;
+ --color-prettylights-syntax-markup-bold: #c9d1d9;
+ --color-prettylights-syntax-markup-deleted-text: #ffdcd7;
+ --color-prettylights-syntax-markup-deleted-bg: #67060c;
+ --color-prettylights-syntax-markup-inserted-text: #aff5b4;
+ --color-prettylights-syntax-markup-inserted-bg: #033a16;
+ --color-prettylights-syntax-markup-changed-text: #ffdfb6;
+ --color-prettylights-syntax-markup-changed-bg: #5a1e02;
+ --color-prettylights-syntax-markup-ignored-text: #c9d1d9;
+ --color-prettylights-syntax-markup-ignored-bg: #1158c7;
+ --color-prettylights-syntax-meta-diff-range: #d2a8ff;
+ --color-prettylights-syntax-brackethighlighter-angle: #8b949e;
+ --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
+ --color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
+ --color-fg-default: #c9d1d9;
+ --color-fg-muted: #8b949e;
+ --color-fg-subtle: #484f58;
+ --color-canvas-default: #0d1117;
+ --color-canvas-subtle: #161b22;
+ --color-border-default: #30363d;
+ --color-border-muted: #21262d;
+ --color-neutral-muted: rgba(110,118,129,0.4);
+ --color-accent-fg: #58a6ff;
+ --color-accent-emphasis: #1f6feb;
+ --color-attention-subtle: rgba(187,128,9,0.15);
+ --color-danger-fg: #f85149;
+}
+
+/* Override specific styles for dark mode tables and code */
+[data-theme="dark"] .markdown-body table tr {
+ background-color: var(--table-bg);
+}
+
+[data-theme="dark"] .markdown-body table tr:nth-child(2n) {
+ background-color: #1c2128; /* Slightly lighter than base dark background */
+}
+
+[data-theme="dark"] .markdown-body pre {
+ background-color: var(--code-bg);
+}
+
+[data-theme="dark"] .markdown-body code {
+ background-color: var(--code-bg);
+}
+
+/* Syntax Highlighting Mapping to GitHub Variables */
+.hljs {
+ color: var(--color-fg-default);
+}
+.hljs-doctag,
+.hljs-keyword,
+.hljs-meta .hljs-keyword,
+.hljs-template-tag,
+.hljs-template-variable,
+.hljs-type,
+.hljs-variable.language_ {
+ color: var(--color-prettylights-syntax-keyword);
+}
+.hljs-title,
+.hljs-title.class_,
+.hljs-title.class_.inherited__,
+.hljs-title.function_ {
+ color: var(--color-prettylights-syntax-entity);
+}
+.hljs-attr,
+.hljs-attribute,
+.hljs-literal,
+.hljs-meta,
+.hljs-number,
+.hljs-operator,
+.hljs-variable,
+.hljs-selector-attr,
+.hljs-selector-class,
+.hljs-selector-id {
+ color: var(--color-prettylights-syntax-constant);
+}
+.hljs-regexp,
+.hljs-string,
+.hljs-meta .hljs-string {
+ color: var(--color-prettylights-syntax-string);
+}
+.hljs-built_in,
+.hljs-symbol {
+ color: var(--color-prettylights-syntax-variable);
+}
+.hljs-comment,
+.hljs-code,
+.hljs-formula {
+ color: var(--color-prettylights-syntax-comment);
+}
+.hljs-name,
+.hljs-quote,
+.hljs-selector-tag,
+.hljs-selector-pseudo {
+ color: var(--color-prettylights-syntax-entity-tag);
+}
+.hljs-subst {
+ color: var(--color-fg-default);
+}
+.hljs-section {
+ color: var(--color-prettylights-syntax-markup-heading);
+ font-weight: bold;
+}
+.hljs-bullet {
+ color: var(--color-prettylights-syntax-constant);
+}
+.hljs-emphasis {
+ color: var(--color-fg-default);
+ font-style: italic;
+}
+.hljs-strong {
+ color: var(--color-fg-default);
+ font-weight: bold;
+}
+.hljs-addition {
+ color: var(--color-prettylights-syntax-markup-inserted-text);
+ background-color: var(--color-prettylights-syntax-markup-inserted-bg);
+}
+.hljs-deletion {
+ color: var(--color-prettylights-syntax-markup-deleted-text);
+ background-color: var(--color-prettylights-syntax-markup-deleted-bg);
+}
+
+.stats-container {
+ font-size: 0.8rem;
+ color: var(--text-color);
+}
+
+.stat-item {
+ align-items: center;
+}
+
+.stat-item i {
+ font-size: 0.9rem;
+ opacity: 0.8;
+}
+
+#importDropdown,
+#exportDropdown,
+#languageDropdown {
+ font-size: 0.8rem;
+}
+
+#importDropdown i,
+#exportDropdown i,
+#languageDropdown i {
+ font-size: 0.9rem;
+}
+
+/* Ensure desktop dropdown menu options match the stats-container font size */
+[aria-labelledby="importDropdown"] .dropdown-item,
+[aria-labelledby="exportDropdown"] .dropdown-item,
+[aria-labelledby="languageDropdown"] .dropdown-item {
+ font-size: 0.8rem;
+}
+
+/* Ensure mobile menu import, export, and language dropdown triggers/options match the mobile stats-container font size */
+#mobile-import-button,
+#mobile-import-github-button,
+#mobile-export-md,
+#mobile-export-html,
+#mobile-export-pdf,
+#mobileLanguageDropdown {
+ font-size: 0.9rem !important;
+}
+
+[aria-labelledby="mobileLanguageDropdown"] .dropdown-item {
+ font-size: 0.9rem;
+}
+
+.editor-pane {
+ overflow: hidden;
+}
+
+/* Mobile Menu Styles */
+.mobile-menu {
+ display: none;
+ position: relative;
+ z-index: 1001;
+}
+
+
+
+/* slideāin panel */
+.mobile-menu-panel {
+ position: fixed;
+ top: 0;
+ right: -300px;
+ width: 280px;
+ height: 100vh;
+ background-color: var(--bg-color);
+ box-shadow: -2px 0 10px rgba(0, 0, 0, 0.2);
+ transition: right 0.3s ease;
+ overflow-y: auto;
+ padding: 1rem;
+ display: flex;
+ flex-direction: column;
+ z-index: 1002;
+}
+
+.mobile-menu-panel.active {
+ right: 0;
+}
+
+/* translucent overlay behind panel */
+.mobile-menu-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100vh;
+ background-color: rgba(0, 0, 0, 0.5);
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity 0.3s ease, visibility 0.3s ease;
+ z-index: 1000;
+}
+
+.mobile-menu-overlay.active {
+ opacity: 1;
+ visibility: visible;
+ pointer-events: auto;
+}
+
+/* header inside mobile menu */
+.mobile-menu-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1rem;
+}
+
+.mobile-menu-header h5 {
+ margin: 0;
+ font-size: 1.25rem;
+ color: var(--text-color);
+}
+
+/* stats section in mobile menu */
+.mobile-stats-container {
+ border-bottom: 1px solid var(--border-color);
+ padding-bottom: 0.75rem;
+ margin-bottom: 1rem;
+}
+
+.mobile-stats-container .stat-item {
+ font-size: 0.9rem;
+ color: var(--text-color);
+ display: flex;
+ align-items: center;
+}
+
+.mobile-stats-container .stat-item i {
+ margin-right: 0.5em;
+ opacity: 0.8;
+}
+
+/* menu buttons list */
+.mobile-menu-items {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ flex-grow: 1;
+}
+
+/* each menu item */
+.mobile-menu-item {
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ color: var(--text-color);
+ border-radius: 6px;
+ padding: 0.6rem 1rem;
+ font-size: 1rem;
+ text-align: left;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ transition: background-color 0.2s ease;
+ cursor: pointer;
+}
+
+.mobile-menu-item:hover {
+ background-color: var(--button-hover);
+}
+
+.mobile-menu-item:active {
+ background-color: var(--button-active);
+}
+
+/* close button override */
+#close-mobile-menu.tool-button {
+ padding: 0.25rem 0.5rem;
+ font-size: 1rem;
+}
+
+/* Mobile document tabs section */
+.mobile-tabs-section {
+ border-bottom: 1px solid var(--border-color);
+ padding-bottom: 0.75rem;
+}
+
+.mobile-tabs-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 0.5rem;
+}
+
+.mobile-tabs-label {
+ font-size: 0.85rem;
+ font-weight: 600;
+ color: var(--text-color);
+ opacity: 0.8;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+}
+
+.mobile-new-tab-btn {
+ background: none;
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ color: var(--text-color);
+ padding: 2px 7px;
+ font-size: 0.9rem;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ transition: background-color 0.15s ease;
+}
+
+.mobile-new-tab-btn:hover {
+ background-color: var(--button-hover);
+}
+
+.mobile-tab-list {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ max-height: 180px;
+ overflow-y: auto;
+}
+
+.mobile-tab-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ padding: 0.45rem 0.75rem;
+ font-size: 0.9rem;
+ color: var(--text-color);
+ cursor: pointer;
+ transition: background-color 0.15s ease;
+ gap: 0.5rem;
+}
+
+.mobile-tab-item:hover {
+ background-color: var(--button-hover);
+}
+
+.mobile-tab-item.active {
+ border-color: var(--accent-color);
+ color: var(--accent-color);
+ background-color: var(--bg-color);
+}
+
+.mobile-tab-title {
+ flex: 1;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 0;
+}
+
+.mobile-tab-item .tab-menu-btn {
+ opacity: 0.6;
+}
+
+.mobile-tab-item:hover .tab-menu-btn,
+.mobile-tab-item.active .tab-menu-btn {
+ opacity: 0.8;
+}
+
+#mobile-tab-reset-btn {
+ margin-left: 0;
+ height: auto;
+ padding: 0.45rem 0.75rem;
+ justify-content: center;
+ font-size: 0.9rem;
+}
+
+/* ==========================================
+ NAVBAR RESPONSIVE BREAKPOINTS
+ >= 1080px : full desktop navbar
+ < 1080px : mobile hamburger + stacked panes
+ ========================================== */
+
+/* Mobile / tablet (< 1080px): switch to hamburger, stack panes */
+@media (max-width: 1079px) {
+ /* Override Bootstrap d-md-flex / d-md-none so the breakpoint is 1080px */
+ .stats-container,
+ .toolbar {
+ display: none !important;
+ }
+
+ /* Expand touch target sizes to meet WCAG mobile guidelines */
+ .markdown-tool-btn {
+ width: 36px !important;
+ height: 36px !important;
+ font-size: 16px !important;
+ }
+ .markdown-format-toolbar {
+ height: 44px !important;
+ }
+ .tab-close-btn {
+ width: 28px !important;
+ height: 28px !important;
+ font-size: 14px !important;
+ }
+ .tab-menu-btn {
+ width: 28px !important;
+ height: 28px !important;
+ font-size: 14px !important;
+ }
+
+ .mobile-menu {
+ display: block !important;
+ }
+
+ /* Stack editor and preview vertically */
+ .content-container {
+ flex-direction: column;
+ }
+
+ .editor-pane,
+ .preview-pane {
+ flex: none;
+ height: 50%;
+ border-right: none;
+ }
+
+ .editor-pane {
+ border-bottom: 1px solid var(--border-color);
+ }
+
+ /* Hide drag-resize divider (touch devices don't use it) */
+ .resize-divider {
+ display: none;
+ }
+
+ /* Single-pane view modes: occupy full height */
+ .content-container.view-editor-only .editor-pane,
+ .content-container.view-preview-only .preview-pane {
+ height: 100%;
+ }
+
+ .content-container.view-split .editor-pane,
+ .content-container.view-split .preview-pane {
+ height: 50%;
+ }
+}
+
+.github-link {
+ color: var(--text-color);
+ text-decoration: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: transform 0.2s ease, color 0.2s ease;
+ margin-right: 2rem;
+}
+
+.github-link:hover {
+ color: var(--accent-color);
+ transform: scale(1.1);
+}
+
+.github-link i {
+ font-size: 1.25rem;
+}
+
+/* ========================================
+ HEADER LAYOUT
+ ======================================== */
+.header-container {
+ position: relative;
+ min-height: 30px;
+}
+
+.app-header h1 {
+ font-size: 1.05rem;
+ line-height: 1.1;
+}
+
+.header-left {
+ flex: 1 0 auto;
+ justify-content: flex-start;
+ white-space: nowrap;
+}
+
+.header-right {
+ flex: 1 0 auto;
+ justify-content: flex-end;
+ white-space: nowrap;
+}
+
+/* Pane View States */
+.content-container.view-editor-only .preview-pane {
+ display: none;
+}
+
+.content-container.view-editor-only .editor-pane {
+ flex: 1;
+ border-right: none;
+}
+
+.content-container.view-preview-only .editor-pane {
+ display: none;
+}
+
+.content-container.view-preview-only .preview-pane {
+ flex: 1;
+}
+
+.content-container.view-split .editor-pane,
+.content-container.view-split .preview-pane {
+ flex: 1;
+}
+
+/* Compact desktop (< 1280px): compact toolbar */
+@media (max-width: 1280px) {
+ /* Compact toolbar at medium widths */
+ .toolbar {
+ gap: 4px;
+ }
+}
+
+
+
+/* ========================================
+ RESIZE DIVIDER - Story 1.3
+ ======================================== */
+
+.resize-divider {
+ width: 8px;
+ background-color: transparent;
+ cursor: col-resize;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ position: relative;
+ z-index: 10;
+ transition: background-color 0.2s ease;
+}
+
+.resize-divider:hover {
+ background-color: var(--button-hover);
+}
+
+.resize-divider.dragging {
+ background-color: var(--accent-color);
+}
+
+.resize-divider-handle {
+ width: 2px;
+ height: 40px;
+ background-color: var(--border-color);
+ border-radius: 2px;
+ transition: background-color 0.2s ease, width 0.2s ease;
+}
+
+.resize-divider:hover .resize-divider-handle,
+.resize-divider.dragging .resize-divider-handle {
+ background-color: var(--accent-color);
+ width: 3px;
+}
+
+/* Hide divider in single-pane modes */
+.content-container.view-editor-only .resize-divider,
+.content-container.view-preview-only .resize-divider {
+ display: none;
+}
+
+
+
+/* Prevent text selection during drag */
+.resizing {
+ user-select: none;
+ cursor: col-resize !important;
+}
+
+.resizing * {
+ cursor: col-resize !important;
+}
+
+.resizing #markdown-preview,
+.resizing #markdown-editor,
+.resizing .line-numbers {
+ pointer-events: none !important;
+}
+
+/* ========================================
+ MOBILE VIEW MODE CONTROLS - Story 1.4
+ ======================================== */
+
+.mobile-view-mode-group {
+ display: flex;
+ gap: 0;
+ border-bottom: 1px solid var(--border-color);
+ padding-bottom: 0.75rem;
+}
+
+.mobile-view-mode-btn {
+ flex: 1;
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ color: var(--text-color);
+ padding: 8px 12px;
+ font-size: 14px;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ transition: all 0.2s ease;
+}
+
+.mobile-view-mode-btn:first-child {
+ border-radius: 6px 0 0 6px;
+}
+
+.mobile-view-mode-btn:last-child {
+ border-radius: 0 6px 6px 0;
+}
+
+.mobile-view-mode-btn:not(:last-child) {
+ border-right: none;
+}
+
+.mobile-view-mode-btn:hover,
+.mobile-view-mode-btn:active {
+ background-color: var(--button-hover);
+}
+
+.mobile-view-mode-btn.active {
+ background-color: var(--button-bg);
+ border-color: var(--accent-color);
+ color: var(--accent-color);
+ border-width: 2px;
+ padding: 7px 11px;
+}
+
+.mobile-view-mode-btn.active:not(:last-child) {
+ border-right: 2px solid var(--accent-color);
+}
+
+.mobile-view-mode-btn i {
+ font-size: 18px;
+}
+
+.mobile-view-mode-btn span {
+ font-size: 12px;
+}
+
+/* ========================================
+ RESPONSIVE VIEW MODE FIXES - Story 1.5
+ ======================================== */
+
+
+
+/* ========================================
+ PDF EXPORT TABLE FIX - Rowspan/Colspan
+ ======================================== */
+
+/* Fix for html2canvas not properly rendering rowspan/colspan cells.
+ Apply backgrounds to cells instead of rows to prevent row backgrounds
+ from painting over rowspan cells during canvas capture. */
+.pdf-export table tr {
+ background-color: transparent !important;
+}
+
+.pdf-export table th,
+.pdf-export table td {
+ background-color: var(--table-bg, #ffffff);
+ position: relative;
+}
+
+.pdf-export table tr:nth-child(2n) th,
+.pdf-export table tr:nth-child(2n) td {
+ background-color: var(--bg-color, #f6f8fa);
+}
+
+/* Ensure rowspan cells render correctly */
+.pdf-export table th[rowspan],
+.pdf-export table td[rowspan] {
+ vertical-align: middle;
+ background-color: var(--table-bg, #ffffff) !important;
+}
+
+/* Ensure colspan cells render correctly */
+.pdf-export table th[colspan],
+.pdf-export table td[colspan] {
+ text-align: center;
+}
+
+/* Dark mode PDF export table fix */
+[data-theme="dark"] .pdf-export table th,
+[data-theme="dark"] .pdf-export table td {
+ background-color: var(--table-bg, #161b22);
+}
+
+[data-theme="dark"] .pdf-export table tr:nth-child(2n) th,
+[data-theme="dark"] .pdf-export table tr:nth-child(2n) td {
+ background-color: #1c2128;
+}
+
+[data-theme="dark"] .pdf-export table th[rowspan],
+[data-theme="dark"] .pdf-export table td[rowspan] {
+ background-color: var(--table-bg, #161b22) !important;
+}
+
+/* ========================================
+ MERMAID DIAGRAM TOOLBAR
+ ======================================== */
+
+.mermaid-container {
+ position: relative;
+}
+
+.mermaid-toolbar {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ display: flex;
+ gap: 4px;
+ opacity: 0;
+ transition: opacity 0.2s ease;
+ z-index: 10;
+}
+
+.mermaid-container:hover .mermaid-toolbar {
+ opacity: 1;
+}
+
+.mermaid-toolbar-btn {
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ color: var(--text-color);
+ border-radius: 4px;
+ padding: 4px 7px;
+ font-size: 13px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 3px;
+ transition: background-color 0.2s ease, color 0.2s ease;
+ white-space: nowrap;
+}
+
+.mermaid-toolbar-btn:hover {
+ background-color: var(--button-hover);
+ color: var(--accent-color);
+}
+
+.mermaid-toolbar-btn:active {
+ background-color: var(--button-active);
+}
+
+.mermaid-toolbar-btn i {
+ font-size: 14px;
+}
+
+/* ========================================
+ MERMAID ZOOM MODAL
+ ======================================== */
+
+#mermaid-zoom-modal {
+ display: none;
+ position: fixed;
+ inset: 0;
+ z-index: 2000;
+ background-color: rgba(0, 0, 0, 0.75);
+ align-items: center;
+ justify-content: center;
+}
+
+#mermaid-zoom-modal.active {
+ display: flex;
+}
+
+.mermaid-modal-content {
+ background-color: var(--bg-color);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 16px;
+ width: 85vw;
+ height: 85vh;
+ max-width: 85vw;
+ max-height: 85vh;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+@media (max-width: 576px) {
+ .mermaid-modal-content {
+ width: 95vw;
+ height: 90vh;
+ max-width: 95vw;
+ max-height: 90vh;
+ padding: 10px;
+ }
+}
+
+.mermaid-modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.mermaid-modal-header span {
+ font-weight: 600;
+ font-size: 15px;
+ color: var(--text-color);
+}
+
+.mermaid-modal-close {
+ background: none;
+ border: none;
+ color: var(--text-color);
+ font-size: 1.2rem;
+ cursor: pointer;
+ padding: 2px 6px;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ transition: background-color 0.2s ease;
+}
+
+.mermaid-modal-close:hover {
+ background-color: var(--button-hover);
+}
+
+.mermaid-modal-diagram {
+ overflow: auto;
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 200px;
+ cursor: grab;
+}
+
+.mermaid-modal-diagram.dragging {
+ cursor: grabbing;
+}
+
+.mermaid-modal-diagram svg {
+ transform-origin: center;
+ transition: transform 0.1s ease;
+ max-width: none;
+}
+
+.mermaid-modal-controls {
+ display: flex;
+ justify-content: center;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.mermaid-modal-controls .mermaid-toolbar-btn {
+ opacity: 1;
+}
+
+/* ========================================
+ DOCUMENT TABS & SESSION MANAGEMENT
+ ======================================== */
+
+.tab-bar {
+ display: flex;
+ align-items: center;
+ background-color: var(--header-bg);
+ border-bottom: 1px solid var(--border-color);
+ height: 32px;
+ overflow: visible; /* ā was: overflow: hidden */
+ flex-shrink: 0;
+ padding: 0 4px;
+ gap: 0;
+ user-select: none;
+ position: relative;
+ z-index: 10;
+}
+
+.tab-list {
+ display: flex;
+ align-items: flex-end;
+ overflow-x: auto;
+ overflow-y: visible; /* ā was: overflow-y: hidden */
+ flex: 1;
+ height: 100%;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+
+.tab-list::-webkit-scrollbar {
+ display: none;
+}
+
+.tab-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ height: 32px;
+ padding: 0 8px 0 10px;
+ min-width: 100px;
+ max-width: 180px;
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ border-bottom: 1px solid transparent;
+ border-radius: 6px 6px 0 0;
+ cursor: pointer;
+ font-size: 13px;
+ color: var(--text-color);
+ white-space: nowrap;
+ /* overflow: hidden; <-- REMOVE THIS */
+ position: relative;
+ transition: background-color 0.15s ease, color 0.15s ease;
+ flex-shrink: 0;
+ margin-right: 2px;
+ opacity: 0.7;
+}
+
+.tab-item:hover {
+ background-color: var(--button-hover);
+ opacity: 0.9;
+}
+
+.tab-item.active {
+ background-color: var(--bg-color);
+ border-color: var(--border-color);
+ color: var(--accent-color);
+ border-bottom: 1px solid var(--bg-color);
+ opacity: 1;
+ z-index: 2;
+}
+
+.tab-item.unsaved::after {
+ content: '';
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ background-color: var(--accent-color);
+ border-radius: 50%;
+ flex-shrink: 0;
+ margin-left: 2px;
+}
+
+.tab-title {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ flex: 1;
+ min-width: 0;
+}
+
+.tab-close-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 16px;
+ height: 16px;
+ border-radius: 3px;
+ background: none;
+ border: none;
+ color: var(--text-color);
+ cursor: pointer;
+ padding: 0;
+ font-size: 11px;
+ opacity: 0;
+ flex-shrink: 0;
+ transition: background-color 0.15s ease, opacity 0.15s ease;
+}
+
+.tab-item:hover .tab-close-btn,
+.tab-item.active .tab-close-btn {
+ opacity: 0.6;
+}
+
+.tab-close-btn:hover {
+ background-color: var(--button-active);
+ opacity: 1 !important;
+ color: var(--color-danger-fg, #d73a49);
+}
+
+.tab-new-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ height: 24px;
+ padding: 0 8px;
+ border-radius: 5px;
+ background: none;
+ border: 1px solid var(--border-color);
+ color: var(--text-color);
+ cursor: pointer;
+ font-size: 12px;
+ flex-shrink: 0;
+ margin-left: 6px;
+ align-self: center;
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
+}
+
+.tab-new-btn:hover {
+ background-color: rgba(46, 160, 67, 0.1);
+ border-color: var(--accent-color, #2ea043);
+ color: var(--accent-color, #2ea043);
+}
+
+.tab-new-btn:active {
+ background-color: rgba(46, 160, 67, 0.2);
+}
+
+/* Drag-and-drop visual feedback */
+.tab-item.dragging {
+ opacity: 0.4;
+}
+
+.tab-item.drag-over {
+ border-left: 2px solid var(--accent-color);
+}
+
+/* Tab enter animation */
+@keyframes tabSlideIn {
+ from { opacity: 0; transform: translateY(4px); }
+ to { opacity: 0.7; transform: translateY(0); }
+}
+
+.tab-item {
+ animation: tabSlideIn 0.12s ease forwards;
+}
+
+.tab-item.active {
+ animation: none;
+}
+
+/* Hide tab bar on very small screens ā single-file use */
+@media (max-width: 480px) {
+ .tab-bar {
+ display: none;
+ }
+}
+
+/* ========================================
+ TAB OVERFLOW ā Scroll Buttons & Fade Indicators
+ ======================================== */
+
+.tab-scroll-btn {
+ display: none;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ border-radius: 4px;
+ background: none;
+ border: 1px solid transparent;
+ color: var(--text-color);
+ cursor: pointer;
+ font-size: 14px;
+ flex-shrink: 0;
+ padding: 0;
+ transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
+ z-index: 2;
+ opacity: 0.6;
+}
+
+.tab-scroll-btn:hover {
+ background-color: var(--button-hover);
+ border-color: var(--border-color);
+ opacity: 1;
+}
+
+.tab-scroll-btn:active {
+ background-color: var(--button-active);
+}
+
+/* Show scroll buttons only when overflow exists */
+.tab-bar.has-overflow-left .tab-scroll-left,
+.tab-bar.has-overflow-right .tab-scroll-right {
+ display: flex;
+}
+
+/* Overflow fade indicators ā subtle gradient at clipped edges */
+.tab-list::before,
+.tab-list::after {
+ content: '';
+ position: sticky;
+ top: 0;
+ bottom: 0;
+ width: 0;
+ flex-shrink: 0;
+ pointer-events: none;
+ z-index: 3;
+ transition: box-shadow 0.2s ease;
+}
+
+.tab-list::before {
+ left: 0;
+}
+
+.tab-list::after {
+ right: 0;
+}
+
+.tab-bar.has-overflow-left .tab-list::before {
+ box-shadow: 8px 0 12px -4px rgba(0, 0, 0, 0.12);
+}
+
+.tab-bar.has-overflow-right .tab-list::after {
+ box-shadow: -8px 0 12px -4px rgba(0, 0, 0, 0.12);
+}
+
+[data-theme="dark"] .tab-bar.has-overflow-left .tab-list::before {
+ box-shadow: 8px 0 12px -4px rgba(0, 0, 0, 0.35);
+}
+
+[data-theme="dark"] .tab-bar.has-overflow-right .tab-list::after {
+ box-shadow: -8px 0 12px -4px rgba(0, 0, 0, 0.35);
+}
+
+/* ========================================
+ THREE-DOT TAB MENU
+ ======================================== */
+
+.tab-menu-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 22px;
+ height: 22px;
+ border-radius: 3px;
+ background: none;
+ border: none;
+ color: var(--text-color);
+ cursor: pointer;
+ padding: 0;
+ font-size: 14px;
+ font-weight: bold;
+ letter-spacing: 1px;
+ opacity: 0.65;
+ flex-shrink: 0;
+ transition: background-color 0.15s ease, opacity 0.15s ease;
+ position: relative;
+}
+
+/* Touch Hitbox Expansion for Tab Menu Button */
+.tab-menu-btn::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 48px;
+ height: 48px;
+}
+
+/* Touch-optimized styling for coarser pointers (e.g., smartphones & tablets) */
+@media (pointer: coarse) {
+ .tab-bar {
+ height: 40px !important;
+ }
+ .tab-item {
+ height: 40px !important;
+ font-size: 14px !important;
+ padding: 0 10px 0 12px !important;
+ gap: 8px !important;
+ }
+ .tab-new-btn,
+ .tab-reset-btn {
+ height: 32px !important;
+ font-size: 14px !important;
+ padding: 0 12px !important;
+ }
+ .tab-scroll-btn {
+ width: 32px !important;
+ height: 32px !important;
+ font-size: 18px !important;
+ }
+ .tab-menu-btn {
+ width: 30px !important;
+ height: 30px !important;
+ font-size: 18px !important;
+ }
+ .tab-close-btn {
+ width: 20px !important;
+ height: 20px !important;
+ font-size: 13px !important;
+ opacity: 0.8 !important;
+ }
+}
+
+.tab-item:hover .tab-menu-btn,
+.tab-item.active .tab-menu-btn {
+ opacity: 0.65;
+}
+
+.tab-menu-btn:hover {
+ background-color: var(--button-active);
+ opacity: 1 !important;
+}
+
+.tab-menu-dropdown {
+ display: none;
+ position: fixed;
+ min-width: 130px;
+ background-color: var(--header-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+ z-index: 99999;
+ overflow: hidden;
+ flex-direction: column;
+}
+
+.tab-menu-dropdown.open {
+ display: flex;
+}
+
+.tab-menu-item {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ padding: 7px 12px;
+ background: none;
+ border: none;
+ color: var(--text-color);
+ font-size: 12px;
+ cursor: pointer;
+ text-align: left;
+ transition: background-color 0.12s ease;
+ white-space: nowrap;
+}
+
+.tab-menu-item:hover {
+ background-color: var(--button-hover);
+}
+
+.tab-menu-item-danger {
+ color: var(--color-danger-fg, #d73a49);
+}
+
+.tab-menu-item-danger:hover {
+ background-color: rgba(215, 58, 73, 0.1);
+}
+
+/* ========================================
+ RESET BUTTON
+ ======================================== */
+
+.tab-reset-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ height: 24px;
+ padding: 0 8px;
+ border-radius: 5px;
+ background: none;
+ border: 1px solid var(--border-color);
+ color: var(--text-color);
+ cursor: pointer;
+ font-size: 12px;
+ flex-shrink: 0;
+ margin-left: 6px;
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
+}
+
+.tab-reset-btn:hover {
+ background-color: rgba(215, 58, 73, 0.1);
+ border-color: var(--color-danger-fg, #d73a49);
+ color: var(--color-danger-fg, #d73a49);
+}
+
+/* ========================================
+ RESET & RENAME CONFIRMATION MODALS
+ ======================================== */
+
+.reset-modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.45);
+ z-index: 2000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.reset-modal-overlay.modal-overlay {
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity 0.2s ease, visibility 0.2s ease;
+}
+
+.reset-modal-overlay.modal-overlay.is-visible {
+ opacity: 1;
+ visibility: visible;
+}
+
+.reset-modal-box {
+ background: var(--header-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 10px;
+ padding: 24px 28px;
+ min-width: 280px;
+ max-width: 360px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.25);
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.modal-box {
+ max-height: min(85vh, 760px);
+ opacity: 0;
+ transform: translateY(8px);
+ transition: transform 0.2s ease, opacity 0.2s ease;
+}
+
+.reset-modal-overlay.modal-overlay.is-visible .modal-box {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.modal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.modal-header .reset-modal-message {
+ text-align: left;
+ flex: 1;
+}
+
+.modal-close-btn {
+ border: 1px solid var(--border-color);
+ background: var(--button-bg);
+ color: var(--text-color);
+ border-radius: 6px;
+ width: 28px;
+ height: 28px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ transition: background-color 0.15s ease;
+}
+
+.modal-close-btn:hover {
+ background-color: var(--button-hover);
+}
+
+.modal-body {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ max-height: min(60vh, 520px);
+ overflow: auto;
+ padding-right: 4px;
+}
+
+.modal-section {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.modal-section-title {
+ margin: 0;
+ font-size: 0.95rem;
+ font-weight: 600;
+}
+
+.modal-list {
+ margin: 0;
+ padding-left: 1.1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ font-size: 0.85rem;
+}
+
+.modal-list a {
+ color: var(--accent-color);
+ text-decoration: none;
+}
+
+.modal-list a:hover {
+ text-decoration: underline;
+}
+
+.modal-subtext {
+ margin: 0;
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+ line-height: 1.4;
+}
+
+.find-replace-meta {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.find-match-count {
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+}
+
+.find-replace-nav {
+ display: inline-flex;
+ gap: 6px;
+}
+
+.find-nav-btn {
+ width: 28px;
+ height: 28px;
+ padding: 0;
+}
+
+.about-header {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ flex-wrap: wrap;
+}
+
+.about-logo {
+ width: 64px;
+ height: 64px;
+ border-radius: 12px;
+ border: 1px solid var(--border-color);
+ object-fit: cover;
+}
+
+.about-details {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.about-title {
+ margin: 0;
+ font-size: 1.05rem;
+ font-weight: 600;
+}
+
+.about-description {
+ margin: 0;
+ font-size: 0.85rem;
+ color: var(--text-secondary, #57606a);
+}
+
+.about-meta {
+ margin: 0;
+ font-size: 0.78rem;
+ color: var(--text-secondary, #57606a);
+}
+
+.modal-body kbd {
+ padding: 2px 6px;
+ border-radius: 4px;
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ font-size: 0.75rem;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+}
+
+.reset-modal-box--wide {
+ width: min(92vw, 640px);
+ max-width: 640px;
+}
+
+.reset-modal-message {
+ margin: 0;
+ font-size: 14px;
+ color: var(--text-color);
+ font-weight: 500;
+ text-align: center;
+}
+
+.reset-modal-actions {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+}
+
+.reset-modal-btn {
+ padding: 6px 16px;
+ border-radius: 6px;
+ border: 1px solid var(--border-color);
+ background: var(--button-bg);
+ color: var(--text-color);
+ font-size: 13px;
+ cursor: pointer;
+ transition: background-color 0.15s ease;
+}
+
+.reset-modal-btn:hover {
+ background-color: var(--button-hover);
+}
+
+.reset-modal-confirm {
+ background-color: var(--color-danger-fg, #d73a49);
+ border-color: var(--color-danger-fg, #d73a49);
+ color: #fff;
+}
+
+.reset-modal-confirm:hover {
+ background-color: #b02a37;
+ border-color: #b02a37;
+}
+
+/* ========================================
+ PDF EXPORT PROGRESS MODAL
+ ======================================== */
+
+.pdf-progress-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 2600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.45);
+ padding: 20px;
+}
+
+.pdf-progress-modal {
+ width: min(92vw, 420px);
+ background: var(--header-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.28);
+ color: var(--text-color);
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ padding: 20px;
+}
+
+.pdf-progress-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.pdf-progress-title {
+ margin: 0;
+ font-size: 15px;
+ font-weight: 600;
+}
+
+.pdf-progress-percent {
+ font-size: 28px;
+ font-weight: 600;
+ line-height: 1;
+}
+
+.pdf-progress-track {
+ width: 100%;
+ height: 10px;
+ background: var(--button-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 999px;
+ overflow: hidden;
+}
+
+.pdf-progress-fill {
+ width: 0%;
+ height: 100%;
+ background: var(--accent-color);
+ border-radius: inherit;
+ transition: width 0.18s ease;
+}
+
+.pdf-progress-details {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ font-size: 12px;
+ color: var(--text-secondary);
+}
+
+.pdf-progress-detail {
+ display: flex;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.pdf-progress-detail strong {
+ color: var(--text-color);
+ font-weight: 600;
+}
+
+.pdf-progress-actions {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.tool-button.pdf-export-loading,
+.mobile-menu-item.pdf-export-loading {
+ pointer-events: none;
+}
+/* ========================================
+ RESET MODAL FORM FIELDS
+ ======================================== */
+
+.reset-modal-field {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ text-align: left;
+}
+
+.reset-modal-field-group {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.reset-modal-label {
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+ font-weight: 600;
+}
+
+.reset-modal-toggle-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.reset-modal-option {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 13px;
+ color: var(--text-color);
+}
+
+.reset-modal-option input {
+ margin: 0;
+}
+
+/* ========================================
+ RENAME MODAL INPUT
+ ======================================== */
+
+.rename-modal-input {
+ width: 100%;
+ padding: 7px 10px;
+ border-radius: 6px;
+ border: 1px solid var(--border-color);
+ background: var(--bg-color);
+ color: var(--text-color);
+ font-size: 13px;
+ outline: none;
+ box-sizing: border-box;
+}
+
+.rename-modal-input:focus {
+ border-color: var(--accent-color);
+}
+
+/* ========================================
+ TOOLBAR POPUP PANELS
+ ======================================== */
+
+.reset-modal-box--xl {
+ width: min(94vw, 980px);
+ max-width: 980px;
+}
+
+.modal-empty {
+ margin: 0;
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+ text-align: center;
+}
+
+.emoji-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+ gap: 12px;
+ max-height: min(52vh, 440px);
+ overflow: auto;
+ padding: 4px;
+}
+
+.symbol-grid {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ max-height: min(52vh, 440px);
+ overflow: auto;
+ padding: 4px 2px;
+}
+
+.symbol-section-title {
+ margin: 0;
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--text-secondary, #57606a);
+}
+
+.symbol-section-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ gap: 12px;
+}
+
+.alert-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+ gap: 12px;
+ max-height: min(45vh, 360px);
+ overflow: auto;
+ padding: 2px;
+}
+
+.emoji-item,
+.symbol-item,
+.alert-option {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ align-items: center;
+ border: 1px solid var(--border-color);
+ border-radius: 10px;
+ padding: 10px;
+ background: var(--bg-color);
+ color: var(--text-color);
+ cursor: pointer;
+ transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
+}
+
+.emoji-item:focus-visible,
+.symbol-item:focus-visible,
+.alert-option:focus-visible {
+ outline: 2px solid var(--accent-color);
+ outline-offset: 2px;
+}
+
+.emoji-item.is-selected,
+.symbol-item.is-selected,
+.alert-option.is-selected {
+ border-color: var(--accent-color);
+ box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2);
+ background-color: rgba(88, 166, 255, 0.08);
+}
+
+.emoji-preview {
+ width: 36px;
+ height: 36px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.emoji-preview img {
+ width: 32px;
+ height: 32px;
+}
+
+.emoji-shortcode {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+ text-align: center;
+}
+
+.emoji-copy-btn,
+.symbol-copy-btn {
+ border: none;
+ background: transparent;
+ color: var(--text-secondary, #57606a);
+ cursor: pointer;
+ padding: 2px;
+ border-radius: 4px;
+}
+
+.emoji-copy-btn:hover,
+.symbol-copy-btn:hover {
+ color: var(--text-color);
+ background: var(--button-hover);
+}
+
+.emoji-copy-btn.is-copied,
+.symbol-copy-btn.is-copied {
+ color: var(--accent-color);
+}
+
+.symbol-preview {
+ font-size: 28px;
+ line-height: 1;
+}
+
+.symbol-code {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+}
+
+.alert-option {
+ align-items: stretch;
+ text-align: left;
+ padding: 12px;
+}
+
+.alert-preview {
+ margin: 0;
+}
+
+.alert-preview .markdown-alert {
+ padding: 0.5rem 0.9rem;
+ border-left: 0.25em solid;
+ border-radius: 0.375rem;
+}
+
+.alert-preview .markdown-alert-title {
+ margin: 0 0 6px;
+ font-weight: 600;
+ line-height: 1.25;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.alert-preview .markdown-alert-icon {
+ display: inline-flex;
+ width: 16px;
+ height: 16px;
+}
+
+.alert-preview .markdown-alert-icon svg {
+ width: 16px;
+ height: 16px;
+ fill: currentColor;
+}
+
+.alert-preview .markdown-alert > *:not(.markdown-alert-title) {
+ color: var(--text-color);
+}
+
+.alert-preview .markdown-alert-note {
+ color: #0969da;
+ border-left-color: #0969da;
+ background-color: #ddf4ff;
+}
+
+.alert-preview .markdown-alert-tip {
+ color: #1a7f37;
+ border-left-color: #1a7f37;
+ background-color: #dafbe1;
+}
+
+.alert-preview .markdown-alert-important {
+ color: #8250df;
+ border-left-color: #8250df;
+ background-color: #fbefff;
+}
+
+.alert-preview .markdown-alert-warning {
+ color: #9a6700;
+ border-left-color: #9a6700;
+ background-color: #fff8c5;
+}
+
+.alert-preview .markdown-alert-caution {
+ color: #cf222e;
+ border-left-color: #cf222e;
+ background-color: #ffebe9;
+}
+
+[data-theme="dark"] .alert-preview .markdown-alert-note {
+ color: #4493f8;
+ border-left-color: #4493f8;
+ background-color: rgba(31, 111, 235, 0.15);
+}
+
+[data-theme="dark"] .alert-preview .markdown-alert-tip {
+ color: #3fb950;
+ border-left-color: #3fb950;
+ background-color: rgba(35, 134, 54, 0.15);
+}
+
+[data-theme="dark"] .alert-preview .markdown-alert-important {
+ color: #ab7df8;
+ border-left-color: #ab7df8;
+ background-color: rgba(137, 87, 229, 0.15);
+}
+
+[data-theme="dark"] .alert-preview .markdown-alert-warning {
+ color: #d29922;
+ border-left-color: #d29922;
+ background-color: rgba(210, 153, 34, 0.18);
+}
+
+[data-theme="dark"] .alert-preview .markdown-alert-caution {
+ color: #f85149;
+ border-left-color: #f85149;
+ background-color: rgba(248, 81, 73, 0.18);
+}
+
+.github-import-error {
+ margin: 0;
+ font-size: 12px;
+ color: var(--color-danger-fg, #d73a49);
+ text-align: left;
+ line-height: 1.5;
+}
+
+.github-import-error.is-info {
+ color: var(--text-secondary, #57606a);
+}
+
+#github-import-modal .reset-modal-box {
+ width: 60vw;
+ max-width: 60vw;
+ min-width: 340px;
+ padding: 30px 34px;
+ gap: 16px;
+ box-shadow: 0 20px 48px rgba(0, 0, 0, 0.22);
+}
+
+#github-import-modal .reset-modal-message {
+ font-size: 18px;
+ line-height: 1.35;
+ text-align: left;
+}
+
+#github-import-url,
+#github-import-file-select {
+ min-height: 46px;
+ padding: 10px 12px;
+ font-size: 15px;
+}
+
+#github-import-file-select {
+ min-height: 180px;
+}
+
+.github-import-tree {
+ max-height: 420px;
+ overflow: auto;
+ border: 1px solid var(--border-color);
+ border-radius: 10px;
+ padding: 12px;
+ background: var(--bg-color);
+}
+
+.github-import-selection-toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 10px 12px;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ background: var(--button-bg);
+}
+
+.github-import-selected-count {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--text-color);
+}
+
+.github-import-tree ul {
+ list-style: none;
+ margin: 0;
+ padding-left: 18px;
+}
+
+.github-import-tree > ul {
+ padding-left: 4px;
+}
+
+.github-import-tree li {
+ margin: 2px 0;
+}
+
+.github-tree-folder-label {
+ display: inline-block;
+ font-size: 14px;
+ color: var(--text-secondary, #57606a);
+ margin-bottom: 4px;
+}
+
+.github-tree-file-btn {
+ border: 0;
+ background: transparent;
+ color: var(--text-color);
+ cursor: pointer;
+ padding: 6px 8px;
+ border-radius: 6px;
+ text-align: left;
+ width: 100%;
+ font-size: 14px;
+}
+
+.github-tree-file-btn:hover,
+.github-tree-file-btn:focus-visible {
+ background: var(--button-hover);
+ outline: none;
+}
+
+.github-tree-file-btn.is-selected {
+ background: rgba(56, 139, 253, 0.14);
+ color: var(--accent-color);
+}
+
+#github-import-modal .reset-modal-actions {
+ gap: 12px;
+}
+
+#github-import-modal .reset-modal-btn {
+ min-height: 42px;
+ padding: 9px 18px;
+ font-size: 14px;
+}
+
+@media (max-width: 576px) {
+ #github-import-modal .reset-modal-box {
+ width: 95vw;
+ max-width: 95vw;
+ min-width: 0;
+ padding: 20px;
+ gap: 14px;
+ }
+
+ .github-import-selection-toolbar {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ #github-import-modal .reset-modal-message {
+ font-size: 16px;
+ }
+
+ #github-import-modal .reset-modal-actions {
+ flex-direction: column-reverse;
+ }
+
+ #github-import-modal .reset-modal-btn {
+ width: 100%;
+ }
+}
+
+.frontmatter-table {
+ border-collapse: collapse;
+ margin-bottom: 1.5em;
+ font-size: 0.9em;
+ width: auto;
+ max-width: 100%;
+}
+
+.frontmatter-table th,
+.frontmatter-table td {
+ border: 1px solid var(--border-color);
+ padding: 6px 13px;
+ vertical-align: top;
+ color: var(--text-color);
+}
+
+.frontmatter-table tr:nth-child(odd) th,
+.frontmatter-table tr:nth-child(odd) td {
+ background-color: var(--table-bg);
+}
+
+.frontmatter-table tr:nth-child(even) th,
+.frontmatter-table tr:nth-child(even) td {
+ background-color: var(--editor-bg);
+}
+
+.frontmatter-table th {
+ font-weight: 600;
+ text-align: right;
+ white-space: nowrap;
+ vertical-align: middle;
+}
+
+.frontmatter-table td {
+ text-align: left;
+}
+
+.fm-complex {
+ margin: 0;
+ padding: 4px 6px;
+ font-size: 0.8em;
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
+ white-space: pre-wrap;
+ word-break: break-word;
+ background: transparent;
+ border: none;
+ color: var(--text-color);
+}
+
+.fm-tag {
+ display: inline-block;
+ padding: 2px 8px;
+ margin: 2px 3px 2px 0;
+ border: 1px solid var(--border-color);
+ border-radius: 2em;
+ font-size: 0.8em;
+ font-weight: 500;
+ color: var(--accent-color);
+ background-color: var(--button-bg);
+ white-space: nowrap;
+}
+
/* ========================================
- PDF EXPORT PROGRESS MODAL
+ RTL SUPPORT
+ ======================================== */
+
+[dir="rtl"] body {
+ direction: rtl;
+}
+
+[dir="rtl"] .editor-pane {
+ padding-left: 0px;
+ padding-right: 20px;
+ border-right: none;
+ border-left: 1px solid var(--border-color);
+}
+
+[dir="rtl"] #markdown-editor,
+[dir="rtl"] .markdown-body {
+ direction: rtl;
+ text-align: right;
+}
+
+[dir="rtl"] .markdown-body pre,
+[dir="rtl"] .markdown-body code,
+[dir="rtl"] .fm-complex {
+ direction: ltr;
+ text-align: left;
+}
+
+[dir="rtl"] .line-numbers {
+ left: auto;
+ right: 20px;
+ padding: 10px 0 10px 8px;
+ text-align: left;
+ border-right: none;
+ border-left: 1px solid var(--border-color);
+}
+
+[dir="rtl"] #markdown-editor {
+ padding-left: 10px;
+ padding-right: calc(10px + var(--line-number-gutter));
+}
+
+[dir="rtl"] .editor-highlight-layer {
+ inset: 20px calc(20px + var(--line-number-gutter)) 20px 0;
+}
+
+[dir="rtl"] .mobile-menu-item,
+[dir="rtl"] .tab-menu-item,
+[dir="rtl"] .modal-header .reset-modal-message,
+[dir="rtl"] .reset-modal-field,
+[dir="rtl"] .alert-option,
+[dir="rtl"] .github-import-error,
+[dir="rtl"] #github-import-modal .reset-modal-message,
+[dir="rtl"] .github-tree-file-btn,
+[dir="rtl"] .frontmatter-table td {
+ text-align: right;
+}
+
+[dir="rtl"] .github-import-tree ul {
+ padding-left: 0;
+ padding-right: 18px;
+}
+
+[dir="rtl"] .github-import-tree > ul {
+ padding-right: 4px;
+}
+
+[dir="rtl"] .markdown-body .markdown-alert,
+[dir="rtl"] .alert-preview .markdown-alert {
+ border-left: 0;
+ border-right: 0.25em solid currentColor;
+}
+
+/* ============================================
+ SHARE MODAL
+ ============================================ */
+
+.share-modal-description {
+ font-size: 13px;
+ color: var(--text-secondary, #57606a);
+ margin: 0;
+}
+
+.share-mode-cards {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.share-mode-card {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 14px;
+ border-radius: 8px;
+ border: 1px solid var(--border-color);
+ background: var(--bg-color);
+ cursor: pointer;
+ transition: border-color 0.15s ease, background-color 0.15s ease;
+ user-select: none;
+}
+
+.share-mode-card:hover {
+ border-color: var(--accent-color);
+ background: var(--button-hover);
+}
+
+.share-mode-card.is-selected {
+ border-color: var(--accent-color);
+ background: color-mix(in srgb, var(--accent-color) 8%, transparent);
+}
+
+.share-mode-card input[type="radio"] {
+ display: none;
+}
+
+.share-card-icon {
+ font-size: 18px;
+ width: 28px;
+ text-align: center;
+ color: var(--text-secondary, #57606a);
+ flex-shrink: 0;
+}
+
+.share-mode-card.is-selected .share-card-icon {
+ color: var(--accent-color);
+}
+
+.share-card-body {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ flex: 1;
+ min-width: 0;
+}
+
+.share-card-title {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-color);
+}
+
+.share-card-desc {
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+}
+
+.share-card-check {
+ width: 18px;
+ text-align: center;
+ color: var(--accent-color);
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ flex-shrink: 0;
+}
+
+.share-mode-card.is-selected .share-card-check {
+ opacity: 1;
+}
+
+.share-url-row {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.share-url-input {
+ flex: 1;
+ font-size: 12px;
+ font-family: var(--font-mono, monospace);
+ color: var(--text-secondary, #57606a);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.share-copy-btn {
+ flex-shrink: 0;
+ padding: 6px 10px;
+}
+
+.share-copy-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.share-modal-notice {
+ font-size: 11px;
+ color: var(--text-secondary, #57606a);
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 5px;
+}
+
+/* ==========================================================================
+ Multilingual & CJK Optimization styles added by Aegis SEO agency
+ ========================================================================== */
+.lang-select-item {
+ display: flex !important;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+ transition: background-color 0.2s ease, padding-left 0.2s ease;
+}
+
+.lang-select-item:hover {
+ padding-left: 12px;
+}
+
+.lang-select-item.active {
+ background-color: var(--accent-color) !important;
+ color: #ffffff !important;
+ font-weight: 600;
+}
+
+/* Adjust CJK text layout for maximum readability inside the preview pane */
+html[lang="zh"] .markdown-body,
+html[lang="ja"] .markdown-body,
+html[lang="ko"] .markdown-body {
+ line-height: 1.75 !important;
+ letter-spacing: 0.03em;
+ word-break: keep-all;
+ overflow-wrap: break-word;
+ text-align: justify;
+}
+
+/* Specific heading spacing improvements for CJK characters */
+html[lang="zh"] .markdown-body h1, html[lang="zh"] .markdown-body h2, html[lang="zh"] .markdown-body h3,
+html[lang="ja"] .markdown-body h1, html[lang="ja"] .markdown-body h2, html[lang="ja"] .markdown-body h3,
+html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang="ko"] .markdown-body h3 {
+ font-weight: 700;
+ letter-spacing: 0.02em;
+ margin-top: 1.4em;
+ margin-bottom: 0.6em;
+}
+
+/* Smooth fade and scale transition for dropdown active states */
+.dropdown-menu {
+ opacity: 0;
+ transform: translateY(8px) scale(0.98);
+ display: block;
+ visibility: hidden;
+ transition: opacity 0.2s cubic-bezier(0.16, 1, 0.3, 1), transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), visibility 0.2s;
+}
+
+.dropdown-menu.show {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ visibility: visible;
+}
+
+/* ========================================
+ FIND & REPLACE FLOATING PANEL DESIGN
======================================== */
-.pdf-progress-overlay {
- position: fixed;
- inset: 0;
- z-index: 2600;
+.find-replace-panel {
+ position: fixed;
+ top: 100px;
+ right: 20px;
+ width: 340px;
+ background-color: var(--fr-bg);
+ border: 1px solid var(--fr-border);
+ border-radius: 12px;
+ box-shadow: var(--fr-shadow);
+ z-index: 1050;
+ display: flex;
+ flex-direction: column;
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ transition: opacity 0.2s ease, transform 0.2s ease;
+ user-select: none;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
+}
+
+.find-replace-panel.docked {
+ position: relative;
+ top: 0 !important;
+ right: 0 !important;
+ height: 100%;
+ border-radius: 0;
+ border-top: none;
+ border-bottom: none;
+ border-right: none;
+ border-left: 1px solid var(--fr-border);
+ box-shadow: none;
+ backdrop-filter: none;
+ z-index: 10;
+ flex-shrink: 0;
+}
+
+.find-replace-panel.docked #find-replace-reset,
+.find-replace-panel.docked #find-replace-reset-footer {
+ display: none !important;
+}
+
+.find-replace-header {
display: flex;
align-items: center;
- justify-content: center;
- background: rgba(0, 0, 0, 0.45);
- padding: 20px;
+ justify-content: space-between;
+ padding: 8px 12px;
+ border-bottom: 1px solid var(--fr-border);
+ cursor: move;
+ background-color: var(--header-bg);
+ border-top-left-radius: 11px;
+ border-top-right-radius: 11px;
}
-.pdf-progress-modal {
- width: min(92vw, 420px);
- background: var(--header-bg);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- box-shadow: 0 16px 48px rgba(0, 0, 0, 0.28);
+.find-replace-panel.docked .find-replace-header {
+ cursor: default;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.find-replace-title {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-color);
+}
+
+.find-replace-header-actions {
+ display: flex;
+ gap: 4px;
+}
+
+.panel-icon-btn {
+ background: none;
+ border: none;
color: var(--text-color);
+ font-size: 12px;
+ cursor: pointer;
+ padding: 2px 6px;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background-color 0.15s ease;
+}
+
+.panel-icon-btn:hover {
+ background-color: var(--button-hover);
+}
+
+.find-replace-body {
+ padding: 12px;
display: flex;
flex-direction: column;
- gap: 16px;
- padding: 20px;
+ gap: 8px;
}
-.pdf-progress-header {
+.find-replace-field-row {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+}
+
+.find-input-container, .replace-input-container {
display: flex;
align-items: center;
- justify-content: space-between;
- gap: 12px;
+ border: 1px solid var(--fr-border);
+ border-radius: 6px;
+ background-color: var(--bg-color);
+ padding: 2px 4px;
+ width: 100%;
}
-.pdf-progress-title {
- margin: 0;
- font-size: 15px;
+.find-input-container:focus-within, .replace-input-container:focus-within {
+ border-color: var(--accent-color);
+ box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.15);
+}
+
+.find-input-field {
+ flex: 1;
+ border: none;
+ background: transparent;
+ color: var(--text-color);
+ font-size: 13px;
+ padding: 4px 6px;
+ outline: none;
+ width: 50%;
+}
+
+.find-options-group {
+ display: flex;
+ gap: 2px;
+}
+
+.find-option-btn {
+ background: none;
+ border: none;
+ color: var(--text-secondary);
+ font-size: 11px;
font-weight: 600;
+ cursor: pointer;
+ padding: 2px 5px;
+ border-radius: 4px;
+ transition: background-color 0.12s ease, color 0.12s ease;
+ min-width: 22px;
+ height: 22px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
}
-.pdf-progress-percent {
- font-size: 28px;
+.find-option-btn:hover {
+ background-color: var(--button-hover);
+ color: var(--text-color);
+}
+
+.find-option-btn.active {
+ background-color: var(--fr-btn-active-bg);
+ color: var(--fr-btn-active);
+}
+
+.find-error-drawer {
+ background-color: var(--fr-error-bg);
+ border: 1px solid var(--fr-error-border);
+ border-radius: 6px;
+ padding: 6px 10px;
+ font-size: 11px;
+ color: var(--fr-text-danger);
+ line-height: 1.3;
+}
+
+.find-replace-meta-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 2px 4px;
+}
+
+.find-match-count {
+ font-size: 12px;
+ color: var(--text-secondary);
+}
+
+.find-nav-group {
+ display: flex;
+ gap: 4px;
+}
+
+.find-nav-arrow-btn {
+ background: none;
+ border: 1px solid var(--fr-border);
+ color: var(--text-color);
+ font-size: 12px;
+ cursor: pointer;
+ padding: 2px 8px;
+ border-radius: 4px;
+ transition: background-color 0.15s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.find-nav-arrow-btn:hover:not(:disabled) {
+ background-color: var(--button-hover);
+}
+
+.find-nav-arrow-btn:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
+.find-drawer-toggle-row {
+ border-top: 1px solid var(--fr-border);
+ margin-top: 4px;
+ padding-top: 6px;
+}
+
+.drawer-toggle-btn {
+ background: none;
+ border: none;
+ color: var(--text-secondary);
+ font-size: 12px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ padding: 2px 4px;
+ border-radius: 4px;
+ transition: color 0.15s ease;
+}
+
+.drawer-toggle-btn:hover {
+ color: var(--text-color);
+}
+
+.find-replace-drawer-content {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding: 4px 6px;
+ border-top: 1px dashed var(--fr-border);
+ margin-top: 2px;
+ padding-top: 8px;
+}
+
+.drawer-field {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.drawer-field.check-field {
+ flex-direction: row;
+ align-items: center;
+ gap: 6px;
+ padding: 2px 0;
+}
+
+.drawer-label {
+ font-size: 11px;
font-weight: 600;
- line-height: 1;
+ color: var(--text-secondary);
}
-.pdf-progress-track {
+.drawer-select {
width: 100%;
- height: 10px;
- background: var(--button-bg);
- border: 1px solid var(--border-color);
- border-radius: 999px;
- overflow: hidden;
+ padding: 4px 6px;
+ border-radius: 4px;
+ border: 1px solid var(--fr-border);
+ background-color: var(--bg-color);
+ color: var(--text-color);
+ font-size: 12px;
+ outline: none;
}
-.pdf-progress-fill {
- width: 0%;
- height: 100%;
- background: var(--accent-color);
- border-radius: inherit;
- transition: width 0.18s ease;
+.drawer-checkbox {
+ margin: 0;
+ cursor: pointer;
}
-.pdf-progress-details {
+.drawer-label-checkbox {
+ font-size: 12px;
+ color: var(--text-color);
+ cursor: pointer;
+}
+
+.find-replace-actions-footer {
display: flex;
- flex-direction: column;
gap: 6px;
+ padding: 8px 12px 12px 12px;
+ border-top: 1px solid var(--fr-border);
+ background-color: var(--header-bg);
+ border-bottom-left-radius: 11px;
+ border-bottom-right-radius: 11px;
+}
+
+.find-replace-panel.docked .find-replace-actions-footer {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.fr-action-btn {
+ flex: 1;
+ padding: 6px 8px;
font-size: 12px;
- color: var(--text-secondary);
+ font-weight: 500;
+ border-radius: 6px;
+ border: 1px solid var(--fr-border);
+ background-color: var(--button-bg);
+ color: var(--text-color);
+ cursor: pointer;
+ transition: background-color 0.15s ease, border-color 0.15s ease;
+ text-align: center;
}
-.pdf-progress-detail {
+.fr-action-btn:hover:not(:disabled) {
+ background-color: var(--button-hover);
+}
+
+.fr-action-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.fr-action-btn.secondary {
+ max-width: 60px;
+}
+
+/* ========================================
+ DIFF PREVIEW CONTAINER
+ ======================================== */
+.diff-preview-body {
+ padding: 16px;
display: flex;
- justify-content: space-between;
+ flex-direction: column;
gap: 12px;
}
-.pdf-progress-detail strong {
- color: var(--text-color);
- font-weight: 600;
+.diff-container {
+ border: 1px solid var(--fr-border);
+ border-radius: 8px;
+ background-color: var(--bg-color);
+ max-height: 400px;
+ overflow: auto;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+ font-size: 12px;
+ line-height: 1.5;
}
-.pdf-progress-actions {
+.diff-line {
display: flex;
- justify-content: flex-end;
+ padding: 1px 8px;
}
-.tool-button.pdf-export-loading,
-.mobile-menu-item.pdf-export-loading {
+.diff-line.addition {
+ background-color: rgba(46, 160, 67, 0.15);
+ color: #3fb950;
+}
+
+.diff-line.deletion {
+ background-color: rgba(248, 81, 73, 0.15);
+ color: #f85149;
+}
+
+.diff-line.context {
+ color: var(--text-secondary);
+}
+
+.diff-line-num {
+ width: 40px;
+ flex-shrink: 0;
+ text-align: right;
+ padding-right: 12px;
+ border-right: 1px solid var(--fr-border);
+ user-select: none;
+ opacity: 0.5;
+}
+
+.diff-line-content {
+ padding-left: 12px;
+ white-space: pre-wrap;
+ word-break: break-all;
+}
+
+/* ========================================
+ DOCK LAYOUT CODES
+ ======================================== */
+.content-container {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+ position: relative;
+}
+
+.editor-dock-wrapper {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+ position: relative;
+}
+
+.editor-pane-inner {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ position: relative;
+ overflow: hidden;
+}
+
+/* ========================================
+ MOBILE & TABLET FIND PANEL RESPONSIVE FIXES
+ ======================================== */
+@media (max-width: 1079px) {
+ #find-replace-dock {
+ display: none !important;
+ }
+}
+
+@media (max-width: 768px) {
+ /* Prevent full screen expansion of floating panel on small mobile viewports */
+ .find-replace-panel {
+ width: calc(100% - 24px) !important;
+ right: 12px !important;
+ left: 12px !important;
+ top: 80px !important;
+ }
+}
+
+/* ========================================
+ SKELETON LOADING SHIMMER SYSTEM
+ ======================================== */
+.skeleton-placeholder {
+ display: block;
+ background-color: var(--skeleton-bg);
+ border-radius: 6px;
+ position: relative;
+ overflow: hidden;
+ /* PERF-017: Removed skeleton-pulse; shimmer-only is sufficient and halves GPU compositing layers */
+ /* animation: skeleton-pulse 2.2s cubic-bezier(0.4, 0, 0.2, 1) infinite alternate; */
+}
+
+.skeleton-placeholder::after {
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ transform: translateX(-100%);
+ background-image: linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0) 0%,
+ var(--skeleton-glow) 50%,
+ rgba(255, 255, 255, 0) 100%
+ );
+ animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
+}
+
+@keyframes skeleton-shimmer {
+ 100% {
+ transform: translateX(100%);
+ }
+}
+
+@keyframes skeleton-pulse {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.82;
+ }
+}
+
+.skeleton-circle {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ margin: 0 auto;
+}
+
+.skeleton-text {
+ height: 12px;
+ width: 80%;
+ margin: 4px auto;
+ border-radius: 3px;
+}
+
+.skeleton-tree-folder {
+ height: 16px;
+ width: 140px;
+ margin: 6px 0;
+ display: inline-block;
+}
+
+.skeleton-tree-file {
+ height: 14px;
+ width: 180px;
+ margin: 4px 0 4px 12px;
+ display: inline-block;
+}
+
+/* Screen reader accessibility utility */
+.visually-hidden {
+ position: absolute !important;
+ width: 1px !important;
+ height: 1px !important;
+ padding: 0 !important;
+ margin: -1px !important;
+ overflow: hidden !important;
+ clip: rect(0, 0, 0, 0) !important;
+ clip-path: inset(50%) !important;
+ white-space: nowrap !important;
+ border: 0 !important;
+}
+
+/* Article skeleton layout structures */
+.skeleton-title {
+ height: 28px;
+ width: 35%;
+ margin-bottom: 24px;
+ border-radius: 8px;
+}
+
+.skeleton-subtitle {
+ height: 20px;
+ width: 20%;
+ margin-bottom: 18px;
+ margin-top: 32px;
+ border-radius: 6px;
+}
+
+.skeleton-line {
+ height: 14px;
+ margin-bottom: 12px;
+ border-radius: 6px;
+}
+
+/* Symmetrical dynamic widths */
+.skeleton-w90 { width: 90%; }
+.skeleton-w92 { width: 92%; }
+.skeleton-w88 { width: 88%; }
+.skeleton-w85 { width: 85%; }
+.skeleton-w60 { width: 60%; }
+.skeleton-w45 { width: 45%; }
+
+/* Editor pane skeleton overlay */
+.editor-skeleton {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 30px 24px 24px calc(24px + var(--line-number-gutter));
+ z-index: 10;
pointer-events: none;
+ background: var(--editor-bg);
+ box-sizing: border-box;
+ overflow: hidden;
+ transition: opacity 0.3s ease;
+}
+
+.editor-pane:not(.is-loading) .editor-skeleton {
+ display: none;
+}
+
+.editor-pane.is-loading textarea {
+ opacity: 0; /* Completely hide editor content while initial bootstrap skeleton runs */
+}
+
+/* Preview pane skeleton container */
+.skeleton-preview-container {
+ display: block;
+ width: 100%;
+ box-sizing: border-box;
+ padding: 10px 4px;
+ background: transparent;
+ transition: opacity 0.3s ease;
+}
+
+/* Mermaid compilation loading states */
+.mermaid-container.is-loading {
+ min-height: 180px;
+ background-color: var(--skeleton-bg);
+ border-radius: 8px;
+ border: 1px solid var(--border-color);
+ position: relative;
+ overflow: hidden;
+ animation: skeleton-pulse 2.2s cubic-bezier(0.4, 0, 0.2, 1) infinite alternate;
+}
+
+.mermaid-container.is-loading .mermaid {
+ opacity: 0; /* Hide raw chart source code during compile */
+}
+
+.mermaid-container.is-loading::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ transform: translateX(-100%);
+ background-image: linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0) 0%,
+ var(--skeleton-glow) 50%,
+ rgba(255, 255, 255, 0) 100%
+ );
+ animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
+}
+
+/* Accessibility: respect user's motion preferences */
+@media (prefers-reduced-motion: reduce) {
+ .skeleton-placeholder,
+ .skeleton-placeholder::after,
+ .mermaid-container.is-loading,
+ .mermaid-container.is-loading::after {
+ animation: none;
+ }
+ .drag-overlay-inner {
+ animation: none;
+ }
+ .tab-item-new {
+ animation: none;
+ }
+ body,
+ .app-header,
+ .editor-pane,
+ .preview-pane,
+ .tool-button,
+ .markdown-tool-btn {
+ transition: none;
+ }
}
-/* ========================================
- RESET MODAL FORM FIELDS
- ======================================== */
-
-.reset-modal-field {
- display: flex;
- flex-direction: column;
- gap: 6px;
- text-align: left;
-}
-
-.reset-modal-field-group {
- display: flex;
- flex-direction: column;
- gap: 12px;
-}
-
-.reset-modal-label {
- font-size: 12px;
- color: var(--text-secondary, #57606a);
- font-weight: 600;
-}
-
-.reset-modal-toggle-group {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.reset-modal-option {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 13px;
- color: var(--text-color);
-}
-
-.reset-modal-option input {
- margin: 0;
-}
-
-/* ========================================
- RENAME MODAL INPUT
- ======================================== */
-
-.rename-modal-input {
- width: 100%;
- padding: 7px 10px;
- border-radius: 6px;
- border: 1px solid var(--border-color);
- background: var(--bg-color);
- color: var(--text-color);
- font-size: 13px;
- outline: none;
- box-sizing: border-box;
-}
-
-.rename-modal-input:focus {
- border-color: var(--accent-color);
-}
-
-/* ========================================
- TOOLBAR POPUP PANELS
- ======================================== */
-
-.reset-modal-box--xl {
- width: min(94vw, 980px);
- max-width: 980px;
-}
-
-.modal-empty {
- margin: 0;
- font-size: 12px;
- color: var(--text-secondary, #57606a);
- text-align: center;
-}
-
-.emoji-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
- gap: 12px;
- max-height: min(52vh, 440px);
- overflow: auto;
- padding: 4px;
-}
-
-.symbol-grid {
- display: flex;
- flex-direction: column;
- gap: 16px;
- max-height: min(52vh, 440px);
- overflow: auto;
- padding: 4px 2px;
-}
-
-.symbol-section-title {
- margin: 0;
- font-size: 11px;
- text-transform: uppercase;
- letter-spacing: 0.08em;
- color: var(--text-secondary, #57606a);
-}
-
-.symbol-section-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
- gap: 12px;
-}
-
-.alert-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
- gap: 12px;
- max-height: min(45vh, 360px);
- overflow: auto;
- padding: 2px;
-}
-
-.emoji-item,
-.symbol-item,
-.alert-option {
- display: flex;
- flex-direction: column;
- gap: 8px;
- align-items: center;
- border: 1px solid var(--border-color);
- border-radius: 10px;
- padding: 10px;
- background: var(--bg-color);
- color: var(--text-color);
- cursor: pointer;
- transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
-}
-
-.emoji-item:focus-visible,
-.symbol-item:focus-visible,
-.alert-option:focus-visible {
- outline: 2px solid var(--accent-color);
- outline-offset: 2px;
-}
-
-.emoji-item.is-selected,
-.symbol-item.is-selected,
-.alert-option.is-selected {
- border-color: var(--accent-color);
- box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2);
- background-color: rgba(88, 166, 255, 0.08);
-}
-
-.emoji-preview {
- width: 36px;
- height: 36px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.emoji-preview img {
- width: 32px;
- height: 32px;
-}
-
-.emoji-shortcode {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 12px;
- color: var(--text-secondary, #57606a);
- text-align: center;
-}
-
-.emoji-copy-btn,
-.symbol-copy-btn {
- border: none;
- background: transparent;
- color: var(--text-secondary, #57606a);
- cursor: pointer;
- padding: 2px;
- border-radius: 4px;
-}
-
-.emoji-copy-btn:hover,
-.symbol-copy-btn:hover {
- color: var(--text-color);
- background: var(--button-hover);
-}
-
-.emoji-copy-btn.is-copied,
-.symbol-copy-btn.is-copied {
- color: var(--accent-color);
-}
-
-.symbol-preview {
- font-size: 28px;
- line-height: 1;
-}
-
-.symbol-code {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 12px;
- color: var(--text-secondary, #57606a);
-}
-
-.alert-option {
- align-items: stretch;
- text-align: left;
- padding: 12px;
-}
-
-.alert-preview {
- margin: 0;
-}
-
-.alert-preview .markdown-alert {
- padding: 0.5rem 0.9rem;
- border-left: 0.25em solid;
- border-radius: 0.375rem;
-}
-
-.alert-preview .markdown-alert-title {
- margin: 0 0 6px;
- font-weight: 600;
- line-height: 1.25;
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.alert-preview .markdown-alert-icon {
- display: inline-flex;
- width: 16px;
- height: 16px;
-}
-
-.alert-preview .markdown-alert-icon svg {
- width: 16px;
- height: 16px;
- fill: currentColor;
-}
-
-.alert-preview .markdown-alert > *:not(.markdown-alert-title) {
- color: var(--text-color);
-}
-
-.alert-preview .markdown-alert-note {
- color: #0969da;
- border-left-color: #0969da;
- background-color: #ddf4ff;
-}
-
-.alert-preview .markdown-alert-tip {
- color: #1a7f37;
- border-left-color: #1a7f37;
- background-color: #dafbe1;
-}
-
-.alert-preview .markdown-alert-important {
- color: #8250df;
- border-left-color: #8250df;
- background-color: #fbefff;
-}
-
-.alert-preview .markdown-alert-warning {
- color: #9a6700;
- border-left-color: #9a6700;
- background-color: #fff8c5;
-}
-
-.alert-preview .markdown-alert-caution {
- color: #cf222e;
- border-left-color: #cf222e;
- background-color: #ffebe9;
-}
-
-[data-theme="dark"] .alert-preview .markdown-alert-note {
- color: #4493f8;
- border-left-color: #4493f8;
- background-color: rgba(31, 111, 235, 0.15);
-}
-
-[data-theme="dark"] .alert-preview .markdown-alert-tip {
- color: #3fb950;
- border-left-color: #3fb950;
- background-color: rgba(35, 134, 54, 0.15);
-}
-
-[data-theme="dark"] .alert-preview .markdown-alert-important {
- color: #ab7df8;
- border-left-color: #ab7df8;
- background-color: rgba(137, 87, 229, 0.15);
-}
-
-[data-theme="dark"] .alert-preview .markdown-alert-warning {
- color: #d29922;
- border-left-color: #d29922;
- background-color: rgba(210, 153, 34, 0.18);
-}
-
-[data-theme="dark"] .alert-preview .markdown-alert-caution {
- color: #f85149;
- border-left-color: #f85149;
- background-color: rgba(248, 81, 73, 0.18);
-}
-
-.github-import-error {
- margin: 0;
- font-size: 12px;
- color: var(--color-danger-fg, #d73a49);
- text-align: left;
- line-height: 1.5;
-}
-
-.github-import-error.is-info {
- color: var(--text-secondary, #57606a);
-}
-
-#github-import-modal .reset-modal-box {
- width: 60vw;
- max-width: 60vw;
- min-width: 340px;
- padding: 30px 34px;
- gap: 16px;
- box-shadow: 0 20px 48px rgba(0, 0, 0, 0.22);
-}
-
-#github-import-modal .reset-modal-message {
- font-size: 18px;
- line-height: 1.35;
- text-align: left;
-}
-
-#github-import-url,
-#github-import-file-select {
- min-height: 46px;
- padding: 10px 12px;
- font-size: 15px;
-}
-
-#github-import-file-select {
- min-height: 180px;
-}
-
-.github-import-tree {
- max-height: 420px;
- overflow: auto;
- border: 1px solid var(--border-color);
- border-radius: 10px;
- padding: 12px;
- background: var(--bg-color);
-}
-
-.github-import-selection-toolbar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- padding: 10px 12px;
- border: 1px solid var(--border-color);
- border-radius: 8px;
- background: var(--button-bg);
-}
-
-.github-import-selected-count {
- font-size: 14px;
- font-weight: 600;
- color: var(--text-color);
-}
-
-.github-import-tree ul {
- list-style: none;
- margin: 0;
- padding-left: 18px;
-}
-
-.github-import-tree > ul {
- padding-left: 4px;
-}
-
-.github-import-tree li {
- margin: 2px 0;
-}
-
-.github-tree-folder-label {
- display: inline-block;
- font-size: 14px;
- color: var(--text-secondary, #57606a);
- margin-bottom: 4px;
-}
-
-.github-tree-file-btn {
- border: 0;
- background: transparent;
- color: var(--text-color);
- cursor: pointer;
- padding: 6px 8px;
- border-radius: 6px;
- text-align: left;
- width: 100%;
- font-size: 14px;
-}
-
-.github-tree-file-btn:hover,
-.github-tree-file-btn:focus-visible {
- background: var(--button-hover);
- outline: none;
-}
-
-.github-tree-file-btn.is-selected {
- background: rgba(56, 139, 253, 0.14);
- color: var(--accent-color);
-}
-
-#github-import-modal .reset-modal-actions {
- gap: 12px;
-}
-
-#github-import-modal .reset-modal-btn {
- min-height: 42px;
- padding: 9px 18px;
- font-size: 14px;
-}
-
-@media (max-width: 576px) {
- #github-import-modal .reset-modal-box {
- width: 95vw;
- max-width: 95vw;
- min-width: 0;
- padding: 20px;
- gap: 14px;
- }
-
- .github-import-selection-toolbar {
- flex-direction: column;
- align-items: stretch;
- }
-
- #github-import-modal .reset-modal-message {
- font-size: 16px;
- }
-
- #github-import-modal .reset-modal-actions {
- flex-direction: column-reverse;
- }
-
- #github-import-modal .reset-modal-btn {
- width: 100%;
- }
-}
-
-.frontmatter-table {
- border-collapse: collapse;
- margin-bottom: 1.5em;
- font-size: 0.9em;
- width: auto;
- max-width: 100%;
-}
-
-.frontmatter-table th,
-.frontmatter-table td {
- border: 1px solid var(--border-color);
- padding: 6px 13px;
- vertical-align: top;
- color: var(--text-color);
-}
-
-.frontmatter-table tr:nth-child(odd) th,
-.frontmatter-table tr:nth-child(odd) td {
- background-color: var(--table-bg);
-}
-
-.frontmatter-table tr:nth-child(even) th,
-.frontmatter-table tr:nth-child(even) td {
- background-color: var(--editor-bg);
-}
-
-.frontmatter-table th {
- font-weight: 600;
- text-align: right;
- white-space: nowrap;
- vertical-align: middle;
-}
-
-.frontmatter-table td {
- text-align: left;
-}
-
-.fm-complex {
- margin: 0;
- padding: 4px 6px;
- font-size: 0.8em;
- font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
- white-space: pre-wrap;
- word-break: break-word;
- background: transparent;
- border: none;
- color: var(--text-color);
-}
-
-.fm-tag {
- display: inline-block;
- padding: 2px 8px;
- margin: 2px 3px 2px 0;
- border: 1px solid var(--border-color);
- border-radius: 2em;
- font-size: 0.8em;
- font-weight: 500;
- color: var(--accent-color);
- background-color: var(--button-bg);
- white-space: nowrap;
-}
-
-/* ========================================
- RTL SUPPORT
- ======================================== */
-
-[dir="rtl"] body {
- direction: rtl;
-}
-
-[dir="rtl"] .editor-pane {
- padding-left: 0px;
- padding-right: 20px;
- border-right: none;
- border-left: 1px solid var(--border-color);
-}
-
-[dir="rtl"] #markdown-editor,
-[dir="rtl"] .markdown-body {
- direction: rtl;
- text-align: right;
-}
-
-[dir="rtl"] .markdown-body pre,
-[dir="rtl"] .markdown-body code,
-[dir="rtl"] .fm-complex {
- direction: ltr;
- text-align: left;
-}
-
-[dir="rtl"] .line-numbers {
- left: auto;
- right: 20px;
- padding: 10px 0 10px 8px;
- text-align: left;
- border-right: none;
- border-left: 1px solid var(--border-color);
-}
-
-[dir="rtl"] #markdown-editor {
- padding-left: 10px;
- padding-right: calc(10px + var(--line-number-gutter));
-}
-
-[dir="rtl"] .editor-highlight-layer {
- inset: 20px calc(20px + var(--line-number-gutter)) 20px 0;
-}
-
-[dir="rtl"] .mobile-menu-item,
-[dir="rtl"] .tab-menu-item,
-[dir="rtl"] .modal-header .reset-modal-message,
-[dir="rtl"] .reset-modal-field,
-[dir="rtl"] .alert-option,
-[dir="rtl"] .github-import-error,
-[dir="rtl"] #github-import-modal .reset-modal-message,
-[dir="rtl"] .github-tree-file-btn,
-[dir="rtl"] .frontmatter-table td {
- text-align: right;
-}
-
-[dir="rtl"] .github-import-tree ul {
- padding-left: 0;
- padding-right: 18px;
-}
-
-[dir="rtl"] .github-import-tree > ul {
- padding-right: 4px;
-}
-
-[dir="rtl"] .markdown-body .markdown-alert,
-[dir="rtl"] .alert-preview .markdown-alert {
- border-left: 0;
- border-right: 0.25em solid currentColor;
-}
-
-/* ============================================
- SHARE MODAL
- ============================================ */
-
-.share-modal-description {
- font-size: 13px;
- color: var(--text-secondary, #57606a);
- margin: 0;
-}
-
-.share-mode-cards {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.share-mode-card {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 12px 14px;
- border-radius: 8px;
- border: 1px solid var(--border-color);
- background: var(--bg-color);
- cursor: pointer;
- transition: border-color 0.15s ease, background-color 0.15s ease;
- user-select: none;
-}
-
-.share-mode-card:hover {
- border-color: var(--accent-color);
- background: var(--button-hover);
-}
-
-.share-mode-card.is-selected {
- border-color: var(--accent-color);
- background: color-mix(in srgb, var(--accent-color) 8%, transparent);
-}
-
-.share-mode-card input[type="radio"] {
- display: none;
-}
-
-.share-card-icon {
- font-size: 18px;
- width: 28px;
- text-align: center;
- color: var(--text-secondary, #57606a);
- flex-shrink: 0;
-}
-
-.share-mode-card.is-selected .share-card-icon {
- color: var(--accent-color);
-}
-
-.share-card-body {
- display: flex;
- flex-direction: column;
- gap: 2px;
- flex: 1;
- min-width: 0;
-}
-
-.share-card-title {
- font-size: 13px;
- font-weight: 600;
- color: var(--text-color);
-}
-
-.share-card-desc {
- font-size: 12px;
- color: var(--text-secondary, #57606a);
-}
-
-.share-card-check {
- width: 18px;
- text-align: center;
- color: var(--accent-color);
- opacity: 0;
- transition: opacity 0.15s ease;
- flex-shrink: 0;
-}
-
-.share-mode-card.is-selected .share-card-check {
- opacity: 1;
-}
-
-.share-url-row {
- display: flex;
- gap: 8px;
- align-items: center;
-}
-
-.share-url-input {
- flex: 1;
- font-size: 12px;
- font-family: var(--font-mono, monospace);
- color: var(--text-secondary, #57606a);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.share-copy-btn {
- flex-shrink: 0;
- padding: 6px 10px;
-}
-
-.share-copy-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.share-modal-notice {
- font-size: 11px;
- color: var(--text-secondary, #57606a);
- margin: 0;
- display: flex;
- align-items: center;
- gap: 5px;
-}
-
-/* ==========================================================================
- Multilingual & CJK Optimization styles added by Aegis SEO agency
- ========================================================================== */
-.lang-select-item {
- display: flex !important;
- align-items: center;
- gap: 8px;
- cursor: pointer;
- transition: background-color 0.2s ease, padding-left 0.2s ease;
-}
-
-.lang-select-item:hover {
- padding-left: 12px;
-}
-
-.lang-select-item.active {
- background-color: var(--accent-color) !important;
- color: #ffffff !important;
- font-weight: 600;
-}
-
-/* Adjust CJK text layout for maximum readability inside the preview pane */
-html[lang="zh"] .markdown-body,
-html[lang="ja"] .markdown-body,
-html[lang="ko"] .markdown-body {
- line-height: 1.75 !important;
- letter-spacing: 0.03em;
- word-break: keep-all;
- overflow-wrap: break-word;
- text-align: justify;
-}
-
-/* Specific heading spacing improvements for CJK characters */
-html[lang="zh"] .markdown-body h1, html[lang="zh"] .markdown-body h2, html[lang="zh"] .markdown-body h3,
-html[lang="ja"] .markdown-body h1, html[lang="ja"] .markdown-body h2, html[lang="ja"] .markdown-body h3,
-html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang="ko"] .markdown-body h3 {
- font-weight: 700;
- letter-spacing: 0.02em;
- margin-top: 1.4em;
- margin-bottom: 0.6em;
-}
-
-/* Smooth fade and scale transition for dropdown active states */
-.dropdown-menu {
- opacity: 0;
- transform: translateY(8px) scale(0.98);
- display: block;
- visibility: hidden;
- transition: opacity 0.2s cubic-bezier(0.16, 1, 0.3, 1), transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), visibility 0.2s;
-}
-
-.dropdown-menu.show {
- opacity: 1;
- transform: translateY(0) scale(1);
- visibility: visible;
-}
-
-/* ========================================
- FIND & REPLACE FLOATING PANEL DESIGN
- ======================================== */
-
-.find-replace-panel {
- position: fixed;
- top: 100px;
- right: 20px;
- width: 340px;
- background-color: var(--fr-bg);
- border: 1px solid var(--fr-border);
- border-radius: 12px;
- box-shadow: var(--fr-shadow);
- z-index: 1050;
- display: flex;
- flex-direction: column;
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- transition: opacity 0.2s ease, transform 0.2s ease;
- user-select: none;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
-}
-
-.find-replace-panel.docked {
- position: relative;
- top: 0 !important;
- right: 0 !important;
- height: 100%;
- border-radius: 0;
- border-top: none;
- border-bottom: none;
- border-right: none;
- border-left: 1px solid var(--fr-border);
- box-shadow: none;
- backdrop-filter: none;
- z-index: 10;
- flex-shrink: 0;
-}
-
-.find-replace-panel.docked #find-replace-reset,
-.find-replace-panel.docked #find-replace-reset-footer {
- display: none !important;
-}
-
-.find-replace-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 8px 12px;
- border-bottom: 1px solid var(--fr-border);
- cursor: move;
- background-color: var(--header-bg);
- border-top-left-radius: 11px;
- border-top-right-radius: 11px;
-}
-
-.find-replace-panel.docked .find-replace-header {
- cursor: default;
- border-top-left-radius: 0;
- border-top-right-radius: 0;
-}
-
-.find-replace-title {
- font-size: 13px;
- font-weight: 600;
- color: var(--text-color);
-}
-
-.find-replace-header-actions {
- display: flex;
- gap: 4px;
-}
-
-.panel-icon-btn {
- background: none;
- border: none;
- color: var(--text-color);
- font-size: 12px;
- cursor: pointer;
- padding: 2px 6px;
- border-radius: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: background-color 0.15s ease;
-}
-
-.panel-icon-btn:hover {
- background-color: var(--button-hover);
-}
-
-.find-replace-body {
- padding: 12px;
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.find-replace-field-row {
- display: flex;
- flex-direction: column;
- position: relative;
-}
-
-.find-input-container, .replace-input-container {
- display: flex;
- align-items: center;
- border: 1px solid var(--fr-border);
- border-radius: 6px;
- background-color: var(--bg-color);
- padding: 2px 4px;
- width: 100%;
-}
-
-.find-input-container:focus-within, .replace-input-container:focus-within {
- border-color: var(--accent-color);
- box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.15);
-}
-
-.find-input-field {
- flex: 1;
- border: none;
- background: transparent;
- color: var(--text-color);
- font-size: 13px;
- padding: 4px 6px;
- outline: none;
- width: 50%;
-}
-
-.find-options-group {
- display: flex;
- gap: 2px;
-}
-
-.find-option-btn {
- background: none;
- border: none;
- color: var(--text-secondary);
- font-size: 11px;
- font-weight: 600;
- cursor: pointer;
- padding: 2px 5px;
- border-radius: 4px;
- transition: background-color 0.12s ease, color 0.12s ease;
- min-width: 22px;
- height: 22px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
-}
-
-.find-option-btn:hover {
- background-color: var(--button-hover);
- color: var(--text-color);
-}
-
-.find-option-btn.active {
- background-color: var(--fr-btn-active-bg);
- color: var(--fr-btn-active);
-}
-
-.find-error-drawer {
- background-color: var(--fr-error-bg);
- border: 1px solid var(--fr-error-border);
- border-radius: 6px;
- padding: 6px 10px;
- font-size: 11px;
- color: var(--fr-text-danger);
- line-height: 1.3;
-}
-
-.find-replace-meta-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 2px 4px;
-}
-
-.find-match-count {
- font-size: 12px;
- color: var(--text-secondary);
-}
-
-.find-nav-group {
- display: flex;
- gap: 4px;
-}
-
-.find-nav-arrow-btn {
- background: none;
- border: 1px solid var(--fr-border);
- color: var(--text-color);
- font-size: 12px;
- cursor: pointer;
- padding: 2px 8px;
- border-radius: 4px;
- transition: background-color 0.15s ease;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.find-nav-arrow-btn:hover:not(:disabled) {
- background-color: var(--button-hover);
-}
-
-.find-nav-arrow-btn:disabled {
- opacity: 0.4;
- cursor: not-allowed;
-}
-
-.find-drawer-toggle-row {
- border-top: 1px solid var(--fr-border);
- margin-top: 4px;
- padding-top: 6px;
-}
-
-.drawer-toggle-btn {
- background: none;
- border: none;
- color: var(--text-secondary);
- font-size: 12px;
- cursor: pointer;
- display: flex;
- align-items: center;
- padding: 2px 4px;
- border-radius: 4px;
- transition: color 0.15s ease;
-}
-
-.drawer-toggle-btn:hover {
- color: var(--text-color);
-}
-
-.find-replace-drawer-content {
- display: flex;
- flex-direction: column;
- gap: 8px;
- padding: 4px 6px;
- border-top: 1px dashed var(--fr-border);
- margin-top: 2px;
- padding-top: 8px;
-}
-
-.drawer-field {
- display: flex;
- flex-direction: column;
- gap: 4px;
-}
-
-.drawer-field.check-field {
- flex-direction: row;
- align-items: center;
- gap: 6px;
- padding: 2px 0;
-}
-
-.drawer-label {
- font-size: 11px;
- font-weight: 600;
- color: var(--text-secondary);
-}
-
-.drawer-select {
- width: 100%;
- padding: 4px 6px;
- border-radius: 4px;
- border: 1px solid var(--fr-border);
- background-color: var(--bg-color);
- color: var(--text-color);
- font-size: 12px;
- outline: none;
-}
-
-.drawer-checkbox {
- margin: 0;
- cursor: pointer;
-}
-
-.drawer-label-checkbox {
- font-size: 12px;
- color: var(--text-color);
- cursor: pointer;
-}
-
-.find-replace-actions-footer {
- display: flex;
- gap: 6px;
- padding: 8px 12px 12px 12px;
- border-top: 1px solid var(--fr-border);
- background-color: var(--header-bg);
- border-bottom-left-radius: 11px;
- border-bottom-right-radius: 11px;
-}
-
-.find-replace-panel.docked .find-replace-actions-footer {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
-}
-
-.fr-action-btn {
- flex: 1;
- padding: 6px 8px;
- font-size: 12px;
- font-weight: 500;
- border-radius: 6px;
- border: 1px solid var(--fr-border);
- background-color: var(--button-bg);
- color: var(--text-color);
- cursor: pointer;
- transition: background-color 0.15s ease, border-color 0.15s ease;
- text-align: center;
-}
-
-.fr-action-btn:hover:not(:disabled) {
- background-color: var(--button-hover);
-}
-
-.fr-action-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.fr-action-btn.secondary {
- max-width: 60px;
-}
-
-/* ========================================
- DIFF PREVIEW CONTAINER
- ======================================== */
-.diff-preview-body {
- padding: 16px;
- display: flex;
- flex-direction: column;
- gap: 12px;
-}
-
-.diff-container {
- border: 1px solid var(--fr-border);
- border-radius: 8px;
- background-color: var(--bg-color);
- max-height: 400px;
- overflow: auto;
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
- font-size: 12px;
- line-height: 1.5;
-}
-
-.diff-line {
- display: flex;
- padding: 1px 8px;
-}
-
-.diff-line.addition {
- background-color: rgba(46, 160, 67, 0.15);
- color: #3fb950;
-}
-
-.diff-line.deletion {
- background-color: rgba(248, 81, 73, 0.15);
- color: #f85149;
-}
-
-.diff-line.context {
- color: var(--text-secondary);
-}
-
-.diff-line-num {
- width: 40px;
- flex-shrink: 0;
- text-align: right;
- padding-right: 12px;
- border-right: 1px solid var(--fr-border);
- user-select: none;
- opacity: 0.5;
-}
-
-.diff-line-content {
- padding-left: 12px;
- white-space: pre-wrap;
- word-break: break-all;
-}
-
-/* ========================================
- DOCK LAYOUT CODES
- ======================================== */
-.content-container {
- display: flex;
- flex: 1;
- overflow: hidden;
- position: relative;
-}
-
-.editor-dock-wrapper {
- display: flex;
- flex: 1;
- overflow: hidden;
- position: relative;
-}
-
-.editor-pane-inner {
- display: flex;
- flex-direction: column;
- flex: 1;
- position: relative;
- overflow: hidden;
-}
-
-/* ========================================
- MOBILE & TABLET FIND PANEL RESPONSIVE FIXES
- ======================================== */
-@media (max-width: 1079px) {
- #find-replace-dock {
- display: none !important;
- }
-}
-
-@media (max-width: 768px) {
- /* Prevent full screen expansion of floating panel on small mobile viewports */
- .find-replace-panel {
- width: calc(100% - 24px) !important;
- right: 12px !important;
- left: 12px !important;
- top: 80px !important;
- }
-}
-
-/* ========================================
- SKELETON LOADING SHIMMER SYSTEM
- ======================================== */
-.skeleton-placeholder {
- display: block;
- background-color: var(--skeleton-bg);
- border-radius: 6px;
- position: relative;
- overflow: hidden;
- /* PERF-017: Removed skeleton-pulse; shimmer-only is sufficient and halves GPU compositing layers */
- /* animation: skeleton-pulse 2.2s cubic-bezier(0.4, 0, 0.2, 1) infinite alternate; */
-}
-
-.skeleton-placeholder::after {
- content: "";
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- transform: translateX(-100%);
- background-image: linear-gradient(
- 90deg,
- rgba(255, 255, 255, 0) 0%,
- var(--skeleton-glow) 50%,
- rgba(255, 255, 255, 0) 100%
- );
- animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
-}
-
-@keyframes skeleton-shimmer {
- 100% {
- transform: translateX(100%);
- }
-}
-
-@keyframes skeleton-pulse {
- 0%, 100% {
- opacity: 1;
- }
- 50% {
- opacity: 0.82;
- }
-}
-
-.skeleton-circle {
- width: 32px;
- height: 32px;
- border-radius: 50%;
- margin: 0 auto;
-}
-
-.skeleton-text {
- height: 12px;
- width: 80%;
- margin: 4px auto;
- border-radius: 3px;
-}
-
-.skeleton-tree-folder {
- height: 16px;
- width: 140px;
- margin: 6px 0;
- display: inline-block;
-}
-
-.skeleton-tree-file {
- height: 14px;
- width: 180px;
- margin: 4px 0 4px 12px;
- display: inline-block;
-}
-
-/* Screen reader accessibility utility */
-.visually-hidden {
- position: absolute !important;
- width: 1px !important;
- height: 1px !important;
- padding: 0 !important;
- margin: -1px !important;
- overflow: hidden !important;
- clip: rect(0, 0, 0, 0) !important;
- clip-path: inset(50%) !important;
- white-space: nowrap !important;
- border: 0 !important;
-}
-
-/* Article skeleton layout structures */
-.skeleton-title {
- height: 28px;
- width: 35%;
- margin-bottom: 24px;
- border-radius: 8px;
-}
-
-.skeleton-subtitle {
- height: 20px;
- width: 20%;
- margin-bottom: 18px;
- margin-top: 32px;
- border-radius: 6px;
-}
-
-.skeleton-line {
- height: 14px;
- margin-bottom: 12px;
- border-radius: 6px;
-}
-
-/* Symmetrical dynamic widths */
-.skeleton-w90 { width: 90%; }
-.skeleton-w92 { width: 92%; }
-.skeleton-w88 { width: 88%; }
-.skeleton-w85 { width: 85%; }
-.skeleton-w60 { width: 60%; }
-.skeleton-w45 { width: 45%; }
-
-/* Editor pane skeleton overlay */
-.editor-skeleton {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- padding: 30px 24px 24px calc(24px + var(--line-number-gutter));
- z-index: 10;
- pointer-events: none;
- background: var(--editor-bg);
- box-sizing: border-box;
- overflow: hidden;
- transition: opacity 0.3s ease;
-}
-
-.editor-pane:not(.is-loading) .editor-skeleton {
- display: none;
-}
-
-.editor-pane.is-loading textarea {
- opacity: 0; /* Completely hide editor content while initial bootstrap skeleton runs */
-}
-
-/* Preview pane skeleton container */
-.skeleton-preview-container {
- display: block;
- width: 100%;
- box-sizing: border-box;
- padding: 10px 4px;
- background: transparent;
- transition: opacity 0.3s ease;
-}
-
-/* Mermaid compilation loading states */
-.mermaid-container.is-loading {
- min-height: 180px;
- background-color: var(--skeleton-bg);
- border-radius: 8px;
- border: 1px solid var(--border-color);
- position: relative;
- overflow: hidden;
- animation: skeleton-pulse 2.2s cubic-bezier(0.4, 0, 0.2, 1) infinite alternate;
-}
-
-.mermaid-container.is-loading .mermaid {
- opacity: 0; /* Hide raw chart source code during compile */
-}
-
-.mermaid-container.is-loading::after {
- content: "";
- position: absolute;
- inset: 0;
- transform: translateX(-100%);
- background-image: linear-gradient(
- 90deg,
- rgba(255, 255, 255, 0) 0%,
- var(--skeleton-glow) 50%,
- rgba(255, 255, 255, 0) 100%
- );
- animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
-}
-
-/* Accessibility: respect user's motion preferences */
-@media (prefers-reduced-motion: reduce) {
- .skeleton-placeholder,
- .skeleton-placeholder::after,
- .mermaid-container.is-loading,
- .mermaid-container.is-loading::after {
- animation: none;
- }
- .drag-overlay-inner {
- animation: none;
- }
- .tab-item-new {
- animation: none;
- }
- body,
- .app-header,
- .editor-pane,
- .preview-pane,
- .tool-button,
- .markdown-tool-btn {
- transition: none;
- }
-}
-
\ No newline at end of file
+
+/* ========================================
+ ENTERPRISE PRINT & PDF LAYOUT
+ ======================================== */
+@media print {
+ @page {
+ size: A4;
+ margin: 15mm;
+ }
+
+ body {
+ background-color: #ffffff !important;
+ color: #24292e !important;
+ font-size: 12pt;
+ line-height: 1.5;
+ }
+
+ /* Hide UI elements during print */
+ .app-container,
+ .toolbar,
+ .mobile-menu,
+ .stats-container,
+ .resize-divider,
+ .pdf-progress-overlay,
+ .modal,
+ .toast,
+ .navbar,
+ .btn {
+ display: none !important;
+ }
+
+ /* Show and fit print content */
+ .pdf-export {
+ position: static !important;
+ left: 0 !important;
+ top: 0 !important;
+ width: 100% !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ background-color: transparent !important;
+ color: inherit !important;
+ display: block !important;
+ box-shadow: none !important;
+ }
+
+ /* Page breaks & fragmentation */
+ h1, h2, h3, h4, h5, h6 {
+ break-after: avoid-page;
+ page-break-after: avoid;
+ break-inside: avoid;
+ }
+
+ p, li, dd, dt, blockquote {
+ orphans: 3;
+ widows: 3;
+ }
+
+ img, figure, figcaption,
+ .mermaid-container, .mermaid, .mermaid svg,
+ .markdown-alert, .alert, blockquote {
+ break-inside: avoid;
+ page-break-inside: avoid;
+ }
+
+ /* Short code blocks should not split; allow long ones to split */
+ pre {
+ break-inside: auto;
+ page-break-inside: auto;
+ white-space: pre-wrap !important;
+ word-break: break-all;
+ }
+
+ pre:not(.oversized) {
+ break-inside: avoid;
+ page-break-inside: avoid;
+ }
+
+ pre code {
+ white-space: pre-wrap !important;
+ word-break: break-all;
+ }
+
+ /* Tables spanning pages and repeating headers */
+ table {
+ break-inside: auto;
+ page-break-inside: auto;
+ width: 100% !important;
+ border-collapse: collapse;
+ }
+
+ thead {
+ display: table-header-group;
+ break-after: avoid-page;
+ page-break-after: avoid;
+ }
+
+ tr {
+ break-inside: avoid;
+ page-break-inside: avoid;
+ }
+
+ /* Scale oversized assets to fit page height/width */
+ img, svg, .mermaid-container, .mermaid svg {
+ max-width: 100% !important;
+ max-height: calc(100vh - 30mm) !important;
+ height: auto !important;
+ object-fit: contain !important;
+ }
+
+ /* Force background colors to print */
+ * {
+ -webkit-print-color-adjust: exact !important;
+ print-color-adjust: exact !important;
+ }
+}
\ No newline at end of file
diff --git a/docs/pdf-export-engine-reengineering-report.md b/docs/pdf-export-engine-reengineering-report.md
new file mode 100644
index 0000000..022c986
--- /dev/null
+++ b/docs/pdf-export-engine-reengineering-report.md
@@ -0,0 +1,673 @@
+# Enterprise PDF Export Engine Re-engineering Program ā Final Investigation Report
+
+**Report date:** June 6, 2026
+**Repository:** `Markdown-Viewer`
+**Centralized manager:** Agent 0 ā Chief PDF Platform Architect
+**Scope:** PDF export, generation, rendering, performance, pagination, and PDF-specific layout only.
+**Change policy:** This investigation changes no product code. It creates only this report.
+
+## Governance and evidence standard
+
+Agent 0 created exactly ten specialized agents (Agents 1ā10), assigned the roles requested below, coordinated their investigations, reviewed their independent outputs, reconciled disagreements, and retained final approval authority. No additional specialized agent was created. Agent 0 verified material claims against repository evidence and current public primary sources before approving them for this report.
+
+Claims are classified as follows:
+
+- **Observed:** directly established by repository code, documentation, or a cited public source.
+- **Inferred:** a technical conclusion derived from observed implementation behavior; it still requires runtime confirmation where noted.
+- **Proposed:** a design or validation recommendation, not a description of current behavior.
+- **Unverified proprietary detail:** a claim that cannot be confirmed from public source or open code and is not used as an architectural premise.
+
+No measured export-time claim is presented as fact. This checkout contains no PDF benchmark harness, browser executable, generated large-document corpus, or automated PDF visual test suite. The requested 50/100/250/500/1000-page measurements therefore remain acceptance tests, not fabricated results.
+
+---
+
+## Section 1 ā Current PDF Export Architecture Analysis
+
+### 1.1 End-to-end workflow
+
+1. **User entry point.** Desktop and mobile PDF controls dispatch the same browser-side click handler. The mobile control forwards to the primary PDF control (`script.js:6172`, `script.js:7461-7650`).
+2. **Dependency acquisition.** On first export, the handler lazy-loads jsPDF 2.5.1 and html2canvas 1.4.1 from CDN definitions (`script.js:28-35`, `script.js:7473-7477`). The desktop build copies shared application files and rewrites CDN assets for offline use during preparation (`desktop-app/prepare.js:45-57`, `desktop-app/prepare.js:139-202`).
+3. **Duplicate Markdown conversion.** Export reads the editor text and synchronously invokes `marked.parse`, then sanitizes the resulting HTML (`script.js:7480-7488`). This is separate from the normal preview pipeline, which can render in `preview-worker.js` and returns HTML to the UI (`preview-worker.js:330-354`, `preview-worker.js:456-480`).
+4. **Off-screen export DOM.** Export creates a fixed-position, off-screen `.markdown-body.pdf-export` element, assigns a 210 mm width, injects the sanitized HTML, applies theme colors, and appends it to the live document (`script.js:7490-7510`).
+5. **Special renderer replay.** Mermaid nodes in the export clone are rendered again with Mermaid; math is typeset again with MathJax; assistive MathML and math scripts are then removed from the export clone (`script.js:7512-7563`). Syntax highlighting is produced during Markdown rendering through the configured renderer (`preview-worker.js:306-320`; corresponding main-thread renderer in `script.js:914-933`).
+6. **Width mutation.** If content overflows horizontally, export increases the entire export container width to its `scrollWidth` (`script.js:6986-7004`, `script.js:7565-7567`).
+7. **Manual page-break heuristic.** Code identifies every `img`, `svg`, `pre`, and `table`; repeatedly reads geometry; calculates synthetic A4 boundaries; and adds top margins to elements predicted to cross boundaries (`script.js:7011-7025`, `script.js:7034-7051`, `script.js:7060-7075`, `script.js:7143-7202`, `script.js:7242-7292`, `script.js:7302-7354`).
+8. **Oversize mutation.** Elements taller than a synthetic page are CSS-transformed down, with a negative bottom margin. Scaling is clamped to 50%, after which the code explicitly warns content may be cut off (`script.js:7364-7418`, `script.js:7425-7454`).
+9. **Monolithic raster capture.** html2canvas captures the *entire* export DOM into one canvas. Its `windowHeight` is the document's full `scrollHeight`; scale is 2.0, 1.5, or 1.25 according to a coarse pixel-area threshold (`script.js:6974-6979`, `script.js:7593-7603`).
+10. **Raster page slicing.** The single canvas is sliced into one page canvas per A4 page. Every page slice is synchronously converted to a PNG data URL and embedded as an image in jsPDF (`script.js:7607-7633`).
+11. **Download and cleanup.** jsPDF saves `document.pdf`; temporary UI and DOM state are removed (`script.js:7635-7649`, `script.js:6941-6962`).
+
+### 1.2 Browser rendering and dependency graph
+
+```text
+Editor Markdown
+ āā main-thread marked.parse (export-only second parse)
+ āā DOMPurify sanitize
+ āā off-screen export DOM
+ āā Mermaid lazy load + render (export-only replay)
+ āā MathJax typeset (export-only replay)
+ āā image/font/browser layout
+ āā repeated geometry analysis and margin mutation
+ āā html2canvas full-document raster
+ āā full-size canvas
+ āā per-page canvas + PNG data URL
+ āā jsPDF image-only pages
+ āā document.pdf
+```
+
+Principal PDF dependencies are explicitly documented as html2canvas plus jsPDF (`README.md:183-194`; `wiki/FAQ.md:71-75`). The project documentation itself states that PDF differences are a known limitation of the html2canvas approach and recommends browser Print ā Save as PDF for higher quality (`wiki/FAQ.md:144-146`).
+
+### 1.3 Memory lifecycle
+
+At peak, the current path can simultaneously retain:
+
+- Markdown source and parsed/sanitized HTML strings;
+- the full off-screen export DOM and rendered SVG/math subtrees;
+- html2canvas's internal cloned/rendering structures;
+- one full-document RGBA canvas (approximately `width Ć height Ć 4` bytes before implementation overhead);
+- one page canvas;
+- one base64 PNG string (base64 adds roughly one-third encoding overhead before JavaScript string overhead);
+- jsPDF's accumulated image/PDF buffers.
+
+This is an inferred peak-memory model from `script.js:7492-7509` and `script.js:7596-7631`. The browser or desktop webview's exact internal copies must be measured with heap and process-memory profiling.
+
+### 1.4 Existing positive controls
+
+The current implementation includes useful safeguards that should be preserved semantically during migration:
+
+- lazy PDF dependency loading (`script.js:7473-7477`);
+- single active export guard and disabled triggers (`script.js:6797-6799`, `script.js:6920-6937`, `script.js:7461-7467`);
+- progress and ETA UI (`script.js:6841-6916`);
+- cancellation checks between phases and pages (`script.js:6811-6829`, `script.js:7611-7633`);
+- adaptive raster scale (`script.js:6974-6979`);
+- cleanup in `finally` (`script.js:7640-7649`);
+- PDF-specific table paint workarounds (`styles.css:1507-1545`).
+
+The cancellation wrapper rejects the caller's wait, but it does not prove that html2canvas's underlying synchronous work stops. That is an inference requiring profiling (`script.js:6817-6829`, `script.js:7596-7603`).
+
+---
+
+## Section 2 ā Agent Findings
+
+### Agent 1 ā Frontend Architect
+
+**Independent findings**
+
+- **Observed:** The PDF handler is embedded in the 8,887-line `script.js`, and it directly coordinates loading, parsing, rendering, layout, capture, PDF assembly, UI state, cancellation, and download (`script.js:6782-7650`). This is high coupling and prevents isolated testing.
+- **Observed:** Normal preview has a worker-capable render pipeline, but PDF export calls `marked.parse` on the main thread (`preview-worker.js:330-354`, `script.js:7480-7487`).
+- **Observed:** Export re-renders Mermaid and MathJax into a second DOM rather than consuming a stable export snapshot (`script.js:7512-7563`).
+- **Inferred bottleneck:** Full-document capture and PNG conversion dominate for large documents; duplicate parse/render and repeated forced layout amplify the cost.
+- **Note:** Recommended separating `ExportDocumentBuilder`, `AssetReadinessGate`, `PrintLayout`, and platform `PdfBackend` interfaces. The UI handler should only start/cancel a job and display progress.
+
+**Agent 0 decision:** **Approved.** Coupling and duplicate work are directly supported by code. The relative share of each bottleneck remains a measurement item.
+
+### Agent 2 ā PDF Engine Specialist
+
+**Independent findings**
+
+- **Observed:** The engine creates one document-sized bitmap and then embeds a PNG for every page (`script.js:7596-7631`). Text, vector diagrams, and links therefore do not remain native PDF text/vector/link objects through this path.
+- **Observed:** Page boundaries are simulated in CSS pixels before capture, then actual PDF slices are calculated from canvas dimensions and millimetres afterward (`script.js:7060-7075`, `script.js:7607-7621`).
+- **Inferred root cause of slowness:** Work and memory scale with total rendered pixel area, while PNG encoding and PDF image compression add per-page CPU cost.
+- **Inferred root cause of layout mismatch:** The pre-capture page model and post-capture slice model can diverge after width fitting, scaling, font/image completion, rounding, transforms, and margin mutation.
+- **Recommendation:** Replace screenshot-to-PDF with Chromium's print compositor, preserving text/vector output and delegating pagination to a paged-media engine.
+
+**Agent 0 decision:** **Approved.** This is the central architecture decision.
+
+### Agent 3 ā Performance Engineer
+
+**Independent findings**
+
+- **Observed:** Geometry is read for all target elements and the document is re-analysed up to ten times after style mutation (`script.js:7034-7051`, `script.js:7302-7345`).
+- **Inferred:** Alternating geometry reads and margin writes can trigger repeated style/layout calculation; complexity is approximately `O(iterations Ć (targets + boundaries comparisons))`, with up to ten whole-document passes.
+- **Observed:** `canvas.toDataURL('image/png')` is called inside the page loop on the main thread (`script.js:7623-7632`).
+- **Observed:** `requestAnimationFrame` yields occur only between major phases/pages; they do not partition html2canvas capture, canvas draw, PNG encoding, or `addImage` internally (`script.js:6964-6968`, `script.js:7596-7603`, `script.js:7623-7632`).
+- **Recommendation:** Profile long tasks, JS heap, DOM nodes, layout duration, canvas allocation, encode duration, and peak resident memory. Move generation out of the interactive renderer process where platform permits.
+
+**Agent 0 decision:** **Approved with qualification.** Exact percentages and task durations are not yet measured.
+
+### Agent 4 ā Large Document Specialist
+
+**Independent findings**
+
+- **Observed:** No checked-in benchmark harness, fixture generator, browser automation suite, or PDF parser/visual comparator exists. `desktop-app/package.json` only contains setup/development/build scripts.
+- **Observed environment limitation:** This investigation environment has no Chromium/Chrome/Firefox executable and no prepared desktop `libs` directory, so trustworthy 50/100/250/500/1000-page runs could not be executed.
+- **Static scalability assessment:** One canvas whose height equals the full document (`script.js:7596-7603`) creates a hard scalability risk. html2canvas's official FAQ warns that empty or truncated output can occur when browser canvas-size limits are reached:
.
+- **Recommendation:** Do not claim current timing numbers. Build deterministic fixtures and execute the matrix in Section 9 on representative low/mid/high hardware.
+
+**Agent 0 decision:** **Approved.** Absence of measurements is explicitly recorded; no synthetic result is substituted.
+
+### Agent 5 ā Markdown Rendering Specialist
+
+**Independent findings**
+
+- **Observed:** Export does not reuse worker-rendered preview HTML; it reparses Markdown on the main thread (`script.js:7480-7487`).
+- **Observed:** Mermaid and MathJax are replayed in the export DOM (`script.js:7512-7563`).
+- **Observed:** Syntax highlighting occurs as part of code rendering (`preview-worker.js:306-320`), so the export parse may repeat highlighting work for every code block.
+- **Inferred:** Image readiness is not represented by an explicit `Promise.all(img.decode())` gate before page analysis/capture. html2canvas may perform its own loading, but layout analysis can still precede final intrinsic image geometry.
+- **Recommendation:** Build a single immutable export snapshot in a worker where possible, then await fonts, decoded images, Mermaid completion, Math completion, and two stable layout frames before print.
+
+**Agent 0 decision:** **Approved.** The image-readiness gap is a code-level absence, while resulting failures require runtime fixtures.
+
+### Agent 6 ā Document Layout Engineer
+
+**Independent findings**
+
+- **Observed:** Only `img`, `svg`, `pre`, and whole `table` elements are protected by the manual algorithm (`script.js:7011-7025`). It does not model figures/captions, headings with following paragraphs, list items, blockquotes, alerts, table rows, widows, or orphans.
+- **Observed:** āPage breaksā are implemented as accumulating `margin-top`, not semantic `break-before`/`break-inside` rules (`script.js:7274-7291`).
+- **Observed:** A table larger than one page is scaled as one unit rather than fragmented by row; code blocks larger than one page are similarly scaled, with a 50% floor that can still cut content (`script.js:7364-7454`).
+- **Observed:** After oversized transforms are applied, the code does not run a final full pagination stabilization before capture (`script.js:7569-7578`).
+- **Inferred:** Mermaid handling targets nested SVGs and sometimes their parent, which can create duplicate/nested target interactions because both container descendants and other graphic nodes participate independently.
+- **Recommendation:** Use print CSS and browser fragmentation: `break-inside: avoid-page` for atomic content that fits; allow controlled row/line fragmentation for oversized tables/code; repeat table headers; apply heading keep-with-next rules; never downscale body text/code to 50% merely to avoid a break.
+
+**Agent 0 decision:** **Approved.** Browser support and exact oversized policies must be covered by cross-platform golden tests.
+
+### Agent 7 ā Obsidian Benchmark Researcher
+
+**Independent findings**
+
+- **Observed public facts:** Obsidian is proprietary; its production export implementation is not available for source inspection. The official organization publishes help and APIs, not the application source: .
+- **Observed public ecosystem:** Community export plugins advertise either Electron `printToPDF` or Pandoc-based pipelines, demonstrating two common ecosystem strategies, but plugin claims are not evidence of Obsidian's internal implementation. Examples: and .
+- **Qualification:** Claims that Obsidian internally uses a specific print API are **unverified proprietary detail** and are excluded from the design rationale.
+- **Benchmark lesson:** Match the rendered HTML with print-specific CSS, expose explicit page controls, and use a real print/PDF compositor; preserve a Pandoc/LaTeX route only for publication workflows that intentionally trade preview fidelity for typesetting control.
+
+**Agent 0 decision:** **Approved with qualification.** No proprietary implementation inference is presented as fact.
+
+### Agent 8 ā VS Code & Typora Benchmark Researcher
+
+**Independent findings**
+
+- **Observed:** VS Code core does not provide a built-in Markdown-to-PDF architecture documented as such; comparison must be to extensions. The open-source `yzane/vscode-markdown-pdf` extension uses a Chromium-based browser and Puppeteer PDF options: .
+- **Observed:** As of its public 2.0.x documentation/release notes, that extension resolves installed Chrome/Edge/Chromium or a managed Chromium and includes unit/integration test suites. This is an open-source implementation fact, not a statement about VS Code core.
+- **Observed:** Typora's official export documentation says its PDF is rendered from HTML, supports paper size/margins/theme/page-break/header/footer configuration, and also offers an optional Pandoc/LaTeX PDF route: .
+- **Qualification:** Typora is proprietary. Public documentation establishes features and HTML-derived output, but not its private internal call graph or performance characteristics.
+- **Recommendation:** Adopt the proven HTML ā Chromium print path for fidelity, with configuration surfaces for paper/margins/background/header/footer, and maintain an optional future publication backend only if demanded.
+
+**Agent 0 decision:** **Approved.** The VS Code comparison is correctly scoped to a named extension.
+
+### Agent 9 ā QA & Regression Engineer
+
+**Independent findings**
+
+- **Observed:** No automated PDF export tests are checked in. Existing documentation acknowledges visual differences and recommends browser printing (`wiki/FAQ.md:144-146`).
+- **Observed:** Web and desktop share product sources through a copy/rewrite build step (`desktop-app/prepare.js:45-57`), but checked-in copies can drift until preparation runs.
+- **Required regression baseline:** Markdown constructs, theme colors, GitHub alerts, local/remote images, SVG, Mermaid, MathJax, syntax highlighting, rowspan/colspan tables, links, Unicode, emoji, page size, margins, and cancellation.
+- **Recommendation:** Use structural PDF assertions plus visual golden pages and semantic extraction checks. Compare legacy and new output on a frozen corpus before removal of the fallback.
+
+**Agent 0 decision:** **Approved.** A two-layer visual and semantic test strategy is mandatory.
+
+### Agent 10 ā Desktop Platform Engineer
+
+**Independent findings**
+
+- **Observed:** Desktop is Neutralino 6.5.0 in window mode, not Electron (`desktop-app/neutralino.config.json:1-10`, `desktop-app/neutralino.config.json:27-44`, `desktop-app/neutralino.config.json:60-66`).
+- **Observed:** Desktop receives the same `script.js`, worker, styles, and assets from the web root during preparation (`desktop-app/prepare.js:45-57`). Therefore it currently executes the same raster engine in its webview.
+- **Observed:** The current native allow list includes file and dialog APIs but no process-launching capability (`desktop-app/neutralino.config.json:16-25`).
+- **Platform conclusion:** Electron `webContents.printToPDF` cannot simply be called from this application. A desktop background exporter requires either (a) a Neutralino extension/sidecar with a controlled Chromium/DevTools Protocol backend, (b) a supported native print-to-PDF API exposed by the selected webview platform, or (c) a desktop-shell migration, which is not justified solely for PDF export without a separate product decision.
+- **Recommendation:** Prefer a narrowly scoped sidecar/extension backend and keep the interactive renderer responsive. Do not migrate the whole desktop shell as part of this PDF program.
+
+**Agent 0 decision:** **Approved.** This resolves a disagreement with agents proposing direct Electron use.
+
+---
+
+## Section 3 ā Performance Bottleneck Analysis
+
+### 3.1 Critical path
+
+| Stage | Current behavior | Scaling/risk | Priority |
+|---|---|---|---|
+| Parse/sanitize | Duplicate main-thread parse | Document size and code blocks | Medium |
+| Mermaid/math | Duplicate export rendering | Number/complexity of diagrams/equations | Mediumāhigh |
+| Layout heuristic | Up to 10 geometry/mutation passes | Targets Ć pages Ć iterations | High |
+| Full capture | One document-height canvas | Total pixel area | Critical |
+| Page slicing | Canvas allocation/draw per page | Pages Ć page pixels | High |
+| PNG encoding | `toDataURL` per page | Pages and image entropy | Critical |
+| jsPDF assembly | Full-page PNG per page | Pages and compressed bytes | High |
+| UI execution | Interactive renderer/main thread | Long tasks and freeze risk | Critical |
+
+### 3.2 Why 10ā15 minute exports are plausible
+
+This duration is a user-reported symptom, not reproduced in this environment. It is technically plausible because the pipeline performs several total-document operations and produces high-resolution raster pages. At A4 proportions, doubling capture scale approximately quadruples pixel count. A long document can therefore create hundreds of millions of pixel operations, large transient allocations, repeated PNG compression, and garbage-collection pressure.
+
+### 3.3 Responsiveness defects
+
+- The progress overlay does not make synchronous capture or encoding non-blocking.
+- `AbortController` checks cannot preempt a library while it is executing synchronous work.
+- The desktop webview shares the same renderer workload as the UI.
+- No backpressure, page streaming, worker-owned OffscreenCanvas path, or child-process isolation exists.
+
+### 3.4 Instrumentation required before implementation acceptance
+
+Record per phase: wall time, CPU time where available, long tasks over 50 ms, peak JS heap, peak process resident set, DOM-node count, layout/style duration, canvas dimensions/bytes, output bytes, page count, and cancellation latency. Include hardware/OS/browser versions and cold/warm dependency state.
+
+---
+
+## Section 4 ā Layout Quality Analysis
+
+### 4.1 Images and Mermaid
+
+Current logic predicts crossings and pushes elements with margins. This can work for some atomic graphics, but it is fragile because pagination is not performed by the same engine that later slices pages. Oversized content is transformed rather than semantically paginated, and the 50% clamp admits cut-off output (`script.js:7374-7387`). Mermaid is rasterized as part of the page image, losing vector scalability.
+
+### 4.2 Code blocks
+
+A `pre` that fits is pushed to a later synthetic page; a taller `pre` is scaled as a whole. Professional behavior should be policy-driven:
+
+- keep short code blocks atomic;
+- allow oversized blocks to fragment between lines;
+- repeat an optional code caption/header, not the whole block;
+- preserve readable font size;
+- show an explicit continuation marker only if product design approves it.
+
+### 4.3 Tables
+
+Whole-table avoidance is unsuitable for multi-page tables. The target behavior is row-boundary fragmentation, repeated ``, avoidance within ordinary rows, controlled splitting only for an individually oversized row, and preservation of rowspan/colspan semantics. Existing cell-background workarounds show that raster capture already has table-specific fidelity issues (`styles.css:1511-1545`).
+
+### 4.4 Complex flow
+
+Current targeting omits heading keep-with-next, figures with captions, alerts, blockquotes, lists, widows/orphans, footnotes, and user-inserted semantic page breaks. Chromium paged media supports `@page` and fragmentation controls; MDN documents `break-before`, `break-after`, `break-inside`, `orphans`, and `widows`: and .
+
+### 4.5 Important limitation
+
+No engine can keep an element taller than the printable page entirely unbroken. āPrevent splittingā must mean:
+
+1. avoid splitting atomic content that fits on one page;
+2. apply an explicit, tested fallback for oversized content (scale graphics within a readable bound, rotate/select landscape where allowed, or fragment at semantic boundaries);
+3. never silently clip.
+
+---
+
+## Section 5 ā Industry Benchmark Findings
+
+| Product/project | Publicly established approach | Useful technique | Qualification |
+|---|---|---|---|
+| Obsidian | Proprietary app; community plugins use Electron print or Pandoc | Print-specific styling; plugin ecosystem demonstrates demand for page controls | Internal native pipeline not publicly verified |
+| Typora | Official docs: PDF rendered from HTML; configurable page size, margins, theme, breaks, header/footer; optional Pandoc/LaTeX | HTML print fidelity plus advanced alternate backend | Proprietary internals/performance unknown |
+| VS Code Markdown PDF extension | Open-source Chromium/Puppeteer exporter | Installed/managed Chromium, `page.pdf`, configurable PDF options, tests | Extension, not VS Code core |
+| MarkText | Open-source Electron Markdown editor with PDF output | Relevant codebase for implementation comparison | Repository feature claim alone does not establish current performance |
+| Zettlr | Open-source Pandoc/LaTeX-centered publication workflow | AST/toolchain separation and templates | Different fidelity/performance trade-off from WYSIWYG print |
+| Chromium/Puppeteer | Native paged PDF compositor with page size, margins, background, headers/footers, CSS page-size preference, font waiting | Vector text, browser pagination, accessibility options | Requires browser/native backend; not silently available to ordinary web pages |
+| html2canvas | DOM-to-canvas renderer with documented canvas/CORS constraints | Useful for screenshots, not ideal as the primary long-document PDF engine | Official FAQ warns about canvas limits |
+
+### Source-backed standards and APIs
+
+- Puppeteer PDF options include format, margins, background, header/footer, CSS page-size preference, tagged PDF, and font waiting: .
+- Electron exposes `webContents.printToPDF`, but this repository does not use Electron: .
+- Chrome DevTools Protocol provides browser instrumentation used by automation tools: .
+- CSS Paged Media and Fragmentation are the standards-based pagination model: .
+- Zettlr publicly states that Pandoc is expected for import/export and exposes publication templates: and .
+- MarkText publicly identifies itself as an Electron Markdown editor with HTML/PDF output: .
+
+### Industry conclusion
+
+The recurring proven patterns are:
+
+1. create deterministic HTML;
+2. load all dependent assets before pagination;
+3. apply dedicated print CSS;
+4. let a browser print compositor or publication engine paginate;
+5. isolate heavy work from the interactive renderer;
+6. test PDFs structurally and visually;
+7. avoid turning every page into a high-resolution screenshot unless image output is specifically requested.
+
+---
+
+## Section 6 ā Root Cause Analysis
+
+### Primary root cause
+
+**The current engine treats a document as one giant screenshot and treats a PDF as a stack of PNG images.** This is fundamentally mismatched to long, structured, paginated documents.
+
+### Contributing causes
+
+1. **Monolithic full-document canvas:** memory and CPU follow pixel area and browser canvas limits.
+2. **Main-thread execution:** capture, drawing, PNG encoding, and assembly compete with UI responsiveness.
+3. **Two pagination coordinate systems:** synthetic CSS-pixel breaks precede millimetre-derived canvas slicing.
+4. **Layout mutation heuristic:** repeated geometry reads and margin writes are expensive and unstable.
+5. **Duplicate render path:** export reparses and rerenders Markdown, Mermaid, math, and highlighting.
+6. **Incomplete readiness contract:** no explicit unified gate for fonts, images, diagrams, and stable layout.
+7. **Wrong oversized-content policy:** scale-to-fit can harm readability and still clip.
+8. **No platform abstraction:** web and desktop run the same implementation despite different native capabilities.
+9. **No performance/visual acceptance harness:** regressions and target claims cannot be objectively enforced.
+10. **Single-file orchestration:** tightly coupled code inhibits isolated tests and incremental backend replacement.
+
+### Rejected explanations
+
+- **ājsPDF alone is slow.ā** Incomplete: jsPDF contributes assembly cost, but full canvas capture and per-page PNG encoding are upstream critical costs.
+- **āMermaid alone causes the freeze.ā** Incomplete: diagrams add work, but plain long text still traverses the monolithic raster path.
+- **āAdd more `requestAnimationFrame` calls.ā** Rejected as a primary fix: yielding around indivisible synchronous operations does not reduce their cost or memory.
+- **āOnly lower canvas scale.ā** Rejected: this trades quality for capacity while retaining the flawed architecture.
+- **āUse Electron directly.ā** Rejected for this repository: desktop is Neutralino, not Electron.
+
+---
+
+## Section 7 ā New PDF Export Architecture
+
+### 7.1 Approved target architecture
+
+```text
+ExportJobController (UI contract only)
+ āā cancellation / progress / telemetry
+ āā ExportDocumentBuilder
+ āā worker-based Markdown parse and sanitize
+ āā export-only immutable HTML document
+ āā deterministic IDs and metadata
+ āā AssetReadinessGate
+ āā document.fonts.ready
+ āā img.decode() / error policy
+ āā Mermaid render completion
+ āā Math render completion
+ āā stable-layout check
+ āā PrintLayout stylesheet
+ āā @page size/margins
+ āā break rules
+ āā table fragmentation/repeated headers
+ āā code and graphic oversized policies
+ āā light/dark/background policy
+ āā PdfBackend
+ āā WebPrintBackend
+ āā DesktopChromiumSidecarBackend
+ āā LegacyRasterBackend (temporary fallback only)
+```
+
+### 7.2 Shared export document builder
+
+- Must not modify editor, preview, toolbar design, search, tabs, themes, settings, or non-PDF Markdown behavior.
+- Consumes Markdown plus a frozen set of PDF options.
+- Reuses parsing modules/worker logic, but has export-only extensions and CSS.
+- Produces a complete isolated document (prefer sandboxed iframe/blob URL in web; temporary local HTML in desktop).
+- Resolves local images through a platform resource resolver and enforces a clear CORS/error policy.
+- Emits readiness and diagnostic events instead of inspecting the live preview DOM.
+
+### 7.3 Print layout policy
+
+- `@page { size: A4; margin: 15mm; }` by default, configurable through existing/new PDF-only options when product scope permits.
+- `break-inside: avoid-page` for images, figures, Mermaid containers, short `pre`, ordinary table rows, alerts, and other atomic blocks.
+- `break-after: avoid-page`/keep-with-next strategy for headings.
+- `orphans` and `widows` for paragraphs where supported.
+- Tables may span pages; `` repeats; whole-table avoidance is prohibited for long tables.
+- Oversized graphics scale to printable width/height with a documented minimum readability threshold and no silent clipping.
+- Oversized code blocks fragment at line boundaries instead of shrinking to 50%.
+- Print CSS owns pagination; no pixel-coordinate margin insertion algorithm remains in the final engine.
+
+### 7.4 Web backend
+
+A normal web page cannot silently write a PDF using Chromium's privileged `printToPDF` API. Therefore:
+
+- **Default privacy-preserving client path:** open an isolated prepared export document and invoke browser print, where the user selects āSave as PDF.ā This is standards-based, vector-capable, and already recommended by project documentation (`wiki/FAQ.md:73-75`, `wiki/FAQ.md:144-146`).
+- **Optional managed-service path:** only if product requirements demand one-click file download, send the prepared HTML/assets to a controlled headless-Chromium service. This changes the current all-client privacy model and requires explicit security/privacy approval; it is not approved by this report.
+- Keep the old raster download behind a temporary compatibility flag during migration, clearly labelled as legacy and unsuitable for very large documents.
+
+### 7.5 Desktop backend
+
+- Implement a narrowly scoped Neutralino extension/sidecar that launches or communicates with a pinned/validated Chromium-compatible executable using Puppeteer Core or Chrome DevTools Protocol.
+- Generate in a separate process, stream progress/logs, enforce timeout and cancellation by terminating the job process, write to a temporary file, then atomically move to the user-selected destination.
+- Package or resolve the browser deterministically and record its version. Prefer installed compatible Chrome/Edge/Chromium with an approved managed fallback, similar in principle to the open-source VS Code Markdown PDF extension.
+- Do not expose arbitrary process execution to page content. The extension accepts a constrained export request schema and whitelisted paths.
+- Do not migrate the whole app to Electron for this feature.
+
+### 7.6 Output characteristics
+
+Expected qualitative improvements:
+
+- selectable/searchable text;
+- vector SVG/Mermaid where Chromium preserves it;
+- functional hyperlinks where supported;
+- browser-native font shaping and pagination;
+- much lower peak raster memory;
+- no per-page PNG encoding loop;
+- semantic page fragmentation;
+- desktop cancellation that can terminate actual work.
+
+Tagged/accessibility output must be tested rather than assumed. Puppeteer exposes a tagged-PDF option, but semantic quality depends on source HTML and Chromium behavior.
+
+---
+
+## Section 8 ā Implementation Plan
+
+### Phase 0 ā Baseline and decision records
+
+1. Add architecture decision record selecting browser print composition over raster PDF.
+2. Freeze a representative PDF corpus and legacy outputs.
+3. Add telemetry hooks around the existing path before replacement.
+4. Define supported browsers/desktop OS versions and output fidelity rules.
+5. Define privacy decision: client print only for web unless separately approved.
+
+**Exit gate:** reproducible baseline and agreed acceptance thresholds.
+
+### Phase 1 ā Modular export document builder
+
+1. Extract PDF orchestration from `script.js` into PDF-only modules without changing behavior.
+2. Reuse worker parsing/highlighting logic through shared pure functions.
+3. Add isolated export document creation and resource resolution.
+4. Add `AssetReadinessGate` for fonts, decoded images, Mermaid, math, and layout stability.
+5. Preserve progress, cancellation, theme, GitHub alerts, and sanitation behavior.
+
+**Exit gate:** generated export HTML is deterministic and fixture-tested.
+
+### Phase 2 ā Standards-based print layout
+
+1. Create PDF-only print stylesheet.
+2. Implement block policies for images, Mermaid, tables, code, headings, alerts, lists, and oversized content.
+3. Remove manual margin insertion from the new backend.
+4. Add page-size/margin/background/header/footer schema as PDF-only options if approved.
+
+**Exit gate:** golden pagination corpus passes in pinned Chromium.
+
+### Phase 3 ā Web print backend
+
+1. Create sandboxed export iframe/window from the prepared document.
+2. Show a clear āPreparingā ā āReady to printā progression.
+3. Invoke print only after readiness.
+4. Ensure cleanup after `afterprint`, cancellation, or timeout.
+5. Keep legacy direct-download fallback during controlled rollout.
+
+**Exit gate:** supported browsers export without UI lockups in the test matrix.
+
+### Phase 4 ā Desktop sidecar backend
+
+1. Define minimal request/response protocol.
+2. Implement constrained Neutralino extension/sidecar.
+3. Resolve/pin Chromium and generate with `preferCSSPageSize`, `printBackground`, font waiting, and configured margins.
+4. Add hard cancellation, crash isolation, temporary-file cleanup, and atomic save.
+5. Package and sign per platform.
+
+**Exit gate:** desktop matrix passes and UI remains responsive during 1000-page generation.
+
+### Phase 5 ā Cutover and retirement
+
+1. Run dual-backend comparison in development/QA.
+2. Roll out new backend behind a PDF-only feature flag.
+3. Monitor failures, duration, peak memory, and cancellations without collecting document content.
+4. Make new backend default after gates pass.
+5. Remove html2canvas/jsPDF PDF dependencies and manual pagination only after fallback retirement approval.
+
+**Explicit non-goals:** no editor, preview, toolbar layout, search, tab, theme system, settings system, or non-export Markdown behavior redesign.
+
+---
+
+## Section 9 ā Performance Validation Plan
+
+### 9.1 Required fixture matrix
+
+Generate deterministic documents that produce approximately 50, 100, 250, 500, and 1000 A4 pages in each profile:
+
+1. text/headings/lists;
+2. syntax-highlighted code;
+3. large and numerous images;
+4. Mermaid diagrams;
+5. MathJax expressions;
+6. wide and long tables with rowspan/colspan;
+7. mixed āreal-world worst case.ā
+
+Use local assets for repeatability and a separate remote/CORS reliability suite.
+
+### 9.2 Metrics
+
+For every run capture:
+
+- prepare, render, readiness, paginate/print, write, and total duration;
+- p50/p95 over at least five warm runs plus one cold run;
+- renderer long-task count and maximum long task;
+- UI heartbeat/input latency during export;
+- peak JS heap and process RSS;
+- child-process peak RSS for desktop;
+- output size and page count;
+- cancellation latency at each phase;
+- failures, blank pages, clipped content, missing assets, and PDF parser errors.
+
+### 9.3 Target gates
+
+Targets must be tied to fixture definitions and reference hardware:
+
+- **Small (ā¤10 pages, ordinary content):** under 2 seconds on reference desktop backend; web preparation under 2 seconds before print UI.
+- **Medium (ā¤50 pages):** under 5 seconds on reference desktop backend.
+- **Large (ā¤250 pages mixed):** under 15 seconds on reference desktop backend, excluding remote asset download.
+- **Very large (500/1000 pages):** no UI freeze; heartbeat gaps under 100 ms in the interactive app; bounded memory with no crash; report actual completion time rather than promise under 15 seconds without evidence.
+- **Cancellation:** UI acknowledges within 100 ms; desktop job process terminates and cleans temporary files within 2 seconds.
+
+The user's ālarge <15 secondsā goal is adopted for a defined ā¤250-page reference workload. A universal 1000-page <15-second promise is not approved without benchmark evidence.
+
+### 9.4 Test hardware and platforms
+
+- Low: 4 logical cores, 8 GB RAM.
+- Reference: 8 logical cores, 16 GB RAM, SSD.
+- High: 12+ logical cores, 32 GB RAM.
+- Windows 11, current supported macOS, Ubuntu LTS.
+- Current stable Chrome/Edge plus one previous supported major for web; pinned desktop Chromium version.
+
+### 9.5 Comparative runs
+
+Run legacy raster and new backend on 50/100/250 pages where legacy completes safely. Do not force 500/1000 legacy runs after memory safety thresholds are exceeded. Report speedup as measured ratios, not projections.
+
+---
+
+## Section 10 ā Regression Prevention Plan
+
+### 10.1 Test layers
+
+1. **Unit:** option normalization, resource URL resolution, break-policy classification, readiness timeout/error handling, cancellation state machine.
+2. **HTML snapshot:** deterministic export DOM for every Markdown feature.
+3. **Browser integration:** render and print with pinned Chromium.
+4. **PDF structural:** page count, media box, metadata, text extraction, link annotations, embedded fonts, image presence, no corrupt objects.
+5. **Visual regression:** render selected PDF pages to images and compare with perceptual thresholds plus masked dynamic areas.
+6. **Cross-platform end-to-end:** web print flow and desktop save flow.
+7. **Performance budgets:** fail CI/nightly gates on statistically significant regression.
+
+### 10.2 Frozen corpus
+
+Include headings, paragraphs, emphasis, links, task lists, nested lists, blockquotes, GitHub alerts, footnotes if supported, code languages, math, Mermaid types, SVG/raster images, transparent images, very wide images, large images, tables, rowspan/colspan, Unicode/RTL/CJK/emoji, light/dark export policy, and malformed/missing assets.
+
+### 10.3 Compatibility controls
+
+- Compare the new export against the current preview and legacy PDF, but treat documented html2canvas defects as defects to fixānot golden behavior to preserve.
+- Preserve sanitation and no-network/default privacy expectations.
+- Version the export request schema and print stylesheet.
+- Pin desktop Chromium and update it through security-reviewed releases.
+- Keep output diagnostics free of document content by default.
+- Require Agent 0/architecture owner approval before removing the legacy fallback.
+
+---
+
+## Section 11 ā Web Verification
+
+### Supported workflow to verify
+
+1. Load each fixture through the normal web application.
+2. Start PDF export and confirm editor input remains responsive during preparation.
+3. Verify the export document is isolated and does not mutate preview/editor DOM.
+4. Confirm fonts, local images, permitted remote images, Mermaid, math, and highlighting are complete before print opens.
+5. Use Chrome/Edge āSave as PDFā; verify page size, margins, backgrounds, page count, text selection, links, and layout.
+6. Repeat cancellation before readiness and close/after-print cleanup.
+7. Test offline behavior with cached/local dependencies.
+8. Test CORS failures and missing assets with explicit warnings rather than silent omission.
+
+### Browser-specific acceptance
+
+- Chromium browsers are the reference because desktop generation also uses Chromium pagination.
+- Firefox/Safari must be tested for the web print flow; documented pagination differences may require browser-specific print CSS or a support limitation.
+- One-click silent file save is not a web acceptance criterion because it is not available to ordinary page JavaScript.
+
+### Current verification status
+
+**Static review complete; runtime verification pending.** This environment had no browser executable and no test harness. The repository currently recommends browser Print ā Save as PDF for higher quality (`wiki/FAQ.md:73-75`, `wiki/FAQ.md:144-146`), supporting feasibility but not replacing formal tests.
+
+---
+
+## Section 12 ā Desktop Verification
+
+### Desktop-specific workflow
+
+1. Run `desktop-app/prepare.js` and verify web PDF modules/styles are copied exactly.
+2. Launch Neutralino packages on Windows/macOS/Linux.
+3. Confirm the sidecar accepts only the constrained export schema and approved paths.
+4. Generate every fixture with pinned/resolved Chromium.
+5. Monitor UI renderer and sidecar separately for CPU/RSS.
+6. Cancel during parse, asset load, layout, print, and file write.
+7. Kill/crash the sidecar and verify recovery, diagnostics, and temp-file cleanup.
+8. Verify native save dialog, overwrite behavior, permissions, long paths, Unicode paths, read-only destinations, and disk-full errors.
+9. Verify offline export and package integrity/signing.
+10. Confirm output parity with the reference web Chromium print layout.
+
+### Current platform facts
+
+Neutralino configuration identifies version 6.5.0, window mode, and the existing native API allow list (`desktop-app/neutralino.config.json:1-25`, `desktop-app/neutralino.config.json:27-44`, `desktop-app/neutralino.config.json:60-66`). Shared root sources are copied into desktop resources during preparation (`desktop-app/prepare.js:45-57`). Thus current desktop export has the same performance architecture as web, while the proposed desktop backend must be added as a constrained platform service.
+
+### Current verification status
+
+**Static review complete; runtime verification pending.** No Neutralino binaries, browser engine executable, prepared offline libraries, or GUI test capability were available in this checkout environment.
+
+---
+
+## Section 13 ā Chief PDF Platform Architect Final Recommendation
+
+### Is the current PDF engine fundamentally flawed?
+
+**Yes, for the stated enterprise and large-document goals.** It is a competent small-document screenshot exporter with progress, cancellation checks, and several targeted workarounds, but its core full-document raster model is fundamentally unsuitable for scalable, high-fidelity paginated documents.
+
+### Is a rewrite justified?
+
+**Yesāa bounded PDF-subsystem rewrite is justified.** Do not rewrite the editor or preview. Replace PDF orchestration, print layout, and backends behind a narrow interface. Preserve the current engine temporarily as a fallback until regression and performance gates pass.
+
+### What architecture should replace it?
+
+A shared deterministic export-document builder plus asset-readiness gate and dedicated print stylesheet, feeding:
+
+- a standards-based browser print backend for web; and
+- an isolated Neutralino sidecar/extension using pinned/resolved Chromium PDF generation for desktop.
+
+Chromium paged-media compositionānot html2canvas plus per-page PNGsāis the approved primary engine. Pandoc/LaTeX is not the default because it risks diverging from current rendered Markdown, but it may be evaluated later as an optional publication backend under separate scope.
+
+### What performance improvements are expected?
+
+Expected, pending measurement:
+
+- elimination of the full-document RGBA canvas and per-page PNG data URLs;
+- elimination of repeated manual page-boundary stabilization in the new path;
+- lower main-renderer CPU and memory;
+- selectable vector/text output with smaller files for text-heavy documents;
+- desktop UI responsiveness through process isolation;
+- material speedups likely to be multiple-fold on long text-heavy documents.
+
+No numeric speedup is approved until Section 9 benchmarks run. The program accepts <2 s small, <5 s medium, and <15 s defined large targets, with responsiveness rather than an unsubstantiated fixed deadline for 500/1000 pages.
+
+### How will layout quality improve?
+
+- The same Chromium engine will paginate and emit the PDF, eliminating the current split between synthetic page coordinates and raster slicing.
+- Print CSS will express semantic break policies.
+- Images and Mermaid that fit will remain atomic; oversized graphics will use explicit no-clip policy.
+- Short code blocks remain atomic; oversized code fragments at line boundaries without unreadable global shrink.
+- Tables fragment by rows with repeatable headers rather than being treated as one giant image.
+- Text, links, and supported SVG remain native rather than page-wide PNG pixels.
+
+### Approved guardrails
+
+1. Strictly PDF-only product changes.
+2. No proprietary-app behavior asserted without public evidence.
+3. No replacement library selected solely by anecdote.
+4. No removal of fallback before objective regression gates.
+5. No web server upload/backend without separate privacy/security approval.
+6. No whole-desktop migration to Electron solely for PDF.
+7. No performance claim without reproducible fixtures and environment metadata.
+
+## Final Status: **APPROVED FOR IMPLEMENTATION**
+
+Approval covers the phased architecture and validation program in this report. It does **not** approve immediate deletion of the legacy exporter, a server-side web export service, or a desktop-shell migration. Production cutover remains conditional on the 50/100/250/500/1000-page performance and regression gates.
diff --git a/index.html b/index.html
index b6aa523..3ad3039 100644
--- a/index.html
+++ b/index.html
@@ -809,6 +809,45 @@ Open-source credits
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/preview-worker.js b/preview-worker.js
index ca19372..5f412d0 100644
--- a/preview-worker.js
+++ b/preview-worker.js
@@ -343,7 +343,13 @@ function configureMarked() {
function ensureLibraries(urls) {
if (!librariesLoaded) {
- importScripts(urls.marked, urls.highlight);
+ const scripts = [];
+ if (urls.marked) scripts.push(urls.marked);
+ if (urls.highlight) scripts.push(urls.highlight);
+ if (urls.purify) scripts.push(urls.purify);
+ if (scripts.length > 0) {
+ importScripts(...scripts);
+ }
librariesLoaded = true;
}
configureMarked();
@@ -461,6 +467,34 @@ function renderSegmentedMarkdown(markdown, options) {
self.onmessage = function(event) {
const data = event.data || {};
+ if (data.type === "render-full") {
+ try {
+ const options = data.options || {};
+ ensureLibraries(options.libraryUrls || {});
+ mermaidIdCounter = 0;
+ const html = marked.parse(data.markdown || "");
+ let sanitized = html;
+ if (typeof DOMPurify !== "undefined") {
+ sanitized = DOMPurify.sanitize(html, {
+ ADD_TAGS: ['mjx-container', 'svg', 'path', 'g', 'marker', 'defs', 'pattern', 'clipPath', 'input'],
+ ADD_ATTR: ['id', 'class', 'style', 'align', 'viewBox', 'd', 'fill', 'stroke', 'transform', 'marker-end', 'marker-start', 'type', 'checked', 'disabled', 'data-original-code']
+ });
+ }
+ self.postMessage({
+ type: "render-full-result",
+ requestId: data.requestId,
+ html: sanitized
+ });
+ } catch (error) {
+ self.postMessage({
+ type: "render-full-error",
+ requestId: data.requestId,
+ error: error && error.message ? error.message : "Full worker render failed."
+ });
+ }
+ return;
+ }
+
if (data.type !== "render") return;
try {
diff --git a/script.js b/script.js
index ee7fc2c..2908e65 100644
--- a/script.js
+++ b/script.js
@@ -551,6 +551,11 @@ document.addEventListener("DOMContentLoaded", function () {
if (data.type === "render-result") {
previewWorkerFailureCount = 0;
pending.resolve(data.result);
+ } else if (data.type === "render-full-result") {
+ previewWorkerFailureCount = 0;
+ pending.resolve(data.html);
+ } else if (data.type === "render-full-error") {
+ pending.reject(new Error(data.error || "Preview worker render-full failed."));
} else {
recordPreviewWorkerRenderFailure();
pending.reject(new Error(data.error || "Preview worker render failed."));
@@ -594,6 +599,54 @@ document.addEventListener("DOMContentLoaded", function () {
});
}
+ function requestFullWorkerRender(rawVal) {
+ const worker = getPreviewWorker();
+ if (!worker) return Promise.reject(new Error("Worker unavailable"));
+
+ const requestId = ++previewWorkerRequestCounter;
+ return new Promise(function(resolve, reject) {
+ const timeoutId = setTimeout(function() {
+ previewWorkerRequests.delete(requestId);
+ reject(new Error("Full render worker timeout."));
+ }, 30000); // 30s timeout for full document render
+
+ previewWorkerRequests.set(requestId, { resolve, reject, timeoutId });
+
+ try {
+ worker.postMessage({
+ type: "render-full",
+ requestId: requestId,
+ markdown: rawVal,
+ options: {
+ libraryUrls: getPreviewWorkerLibraryUrls()
+ },
+ });
+ } catch (e) {
+ previewWorkerRequests.delete(requestId);
+ clearTimeout(timeoutId);
+ reject(e);
+ }
+ });
+ }
+
+ async function parseMarkdownFull(markdown) {
+ try {
+ const html = await requestFullWorkerRender(markdown);
+ return html;
+ } catch (e) {
+ console.warn("Full worker render failed, falling back to main thread:", e);
+ const { frontmatter, body } = parseFrontmatter(markdown);
+ const tableHtml = frontmatter ? renderFrontmatterTable(frontmatter) : '';
+ const referenceData = extractReferenceDefinitions(body);
+ const parsedHtml = tableHtml + marked.parse(referenceData.cleanedMarkdown);
+ const sanitizedHtml = DOMPurify.sanitize(parsedHtml, {
+ ADD_TAGS: ['mjx-container', 'svg', 'path', 'g', 'marker', 'defs', 'pattern', 'clipPath', 'input'],
+ ADD_ATTR: ['id', 'class', 'style', 'align', 'viewBox', 'd', 'fill', 'stroke', 'transform', 'marker-end', 'marker-start', 'type', 'checked', 'disabled', 'data-original-code']
+ });
+ return sanitizedHtml;
+ }
+ }
+
function parseInlineWithoutFootnotes(text) {
suppressFootnotePreprocess = true;
try {
@@ -7458,195 +7511,411 @@ document.addEventListener("DOMContentLoaded", function () {
// End Oversized Graphics Scaling Functions
// ============================================
- exportPdf.addEventListener("click", async function (event) {
- event.preventDefault();
- if (activePdfExport) return;
+ // ============================================
+ // New Modular PDF Export Engine
+ // ============================================
+ const PdfExportEngine = {
+ ExportDocumentBuilder: {
+ build: async function(markdown, state) {
+ updatePdfProgress(state, 15, "Parsing markdown");
+ await waitForPdfFrame(state);
+
+ const sanitizedHtml = await parseMarkdownFull(markdown);
+ throwIfPdfExportAborted(state.signal);
- const progressState = createPdfProgressState();
- activePdfExport = progressState;
- setPdfExportTriggersBusy(progressState, true);
- document.body.appendChild(progressState.overlay);
- updatePdfProgress(progressState, 3, "Starting");
- progressState.overlay.querySelector(".pdf-progress-cancel")?.focus();
+ updatePdfProgress(state, 24, "Preparing document");
+ await waitForPdfFrame(state);
+
+ const tempElement = document.createElement("div");
+ state.tempElement = tempElement;
+ tempElement.className = "markdown-body pdf-export";
+ tempElement.innerHTML = sanitizedHtml;
+ enhanceGitHubAlerts(tempElement);
+ tempElement.style.padding = "20px";
+ tempElement.style.width = "210mm";
+ tempElement.style.margin = "0 auto";
+ tempElement.style.fontSize = "14px";
+ tempElement.style.position = "fixed";
+ tempElement.style.left = "-9999px";
+ tempElement.style.top = "0";
+
+ const currentTheme = document.documentElement.getAttribute("data-theme");
+ tempElement.style.backgroundColor = currentTheme === "dark" ? "#0d1117" : "#ffffff";
+ tempElement.style.color = currentTheme === "dark" ? "#c9d1d9" : "#24292e";
+
+ document.body.appendChild(tempElement);
+ await waitForPdfFrame(state);
+
+ await PdfExportEngine.AssetReadinessGate.awaitReady(tempElement, markdown, state);
+ throwIfPdfExportAborted(state.signal);
+
+ const pageHeightPx = tempElement.offsetWidth * (PAGE_CONFIG.contentHeight / PAGE_CONFIG.contentWidth);
+ tempElement.querySelectorAll("pre").forEach(pre => {
+ if (pre.offsetHeight > pageHeightPx) {
+ pre.classList.add("oversized");
+ }
+ });
- try {
- // PERF-002: Lazy-load PDF libraries on first export
- if (typeof jspdf === 'undefined' || typeof html2canvas === 'undefined') {
- updatePdfProgress(progressState, 8, "Loading PDF libraries");
- await runPdfAbortable(progressState, Promise.all([loadScript(CDN.jspdf), loadScript(CDN.html2canvas)]));
- throwIfPdfExportAborted(progressState.signal);
+ const parentStyles = Array.from(document.querySelectorAll('link[rel="stylesheet"], style'))
+ .map(el => el.outerHTML)
+ .join("\n");
+
+ const fullHtml = `
+
+
+
+
Export Document
+ ${parentStyles}
+
+
+
+ ${tempElement.innerHTML}
+
+
+`;
+
+ return { fullHtml, tempElement };
}
+ },
- updatePdfProgress(progressState, 15, "Parsing markdown");
- await waitForPdfFrame(progressState);
- const markdown = markdownEditor.value;
- const html = marked.parse(markdown);
- const sanitizedHtml = DOMPurify.sanitize(html, {
- ADD_TAGS: ['mjx-container', 'svg', 'path', 'g', 'marker', 'defs', 'pattern', 'clipPath', 'input'],
- ADD_ATTR: ['id', 'class', 'style', 'align', 'viewBox', 'd', 'fill', 'stroke', 'transform', 'marker-end', 'marker-start', 'type', 'checked', 'disabled', 'data-original-code']
- });
- throwIfPdfExportAborted(progressState.signal);
-
- updatePdfProgress(progressState, 24, "Preparing document");
- await waitForPdfFrame(progressState);
- const tempElement = document.createElement("div");
- progressState.tempElement = tempElement;
- tempElement.className = "markdown-body pdf-export";
- tempElement.innerHTML = sanitizedHtml;
- enhanceGitHubAlerts(tempElement);
- tempElement.style.padding = "20px";
- tempElement.style.width = "210mm";
- tempElement.style.margin = "0 auto";
- tempElement.style.fontSize = "14px";
- tempElement.style.position = "fixed";
- tempElement.style.left = "-9999px";
- tempElement.style.top = "0";
-
- const currentTheme = document.documentElement.getAttribute("data-theme");
- tempElement.style.backgroundColor = currentTheme === "dark" ? "#0d1117" : "#ffffff";
- tempElement.style.color = currentTheme === "dark" ? "#c9d1d9" : "#24292e";
-
- document.body.appendChild(tempElement);
- await waitForPdfFrame(progressState);
-
- const mermaidNodes = tempElement.querySelectorAll('.mermaid');
- if (mermaidNodes.length > 0) {
- updatePdfProgress(progressState, 34, "Rendering diagrams");
- try {
- if (typeof mermaid === 'undefined') {
- await runPdfAbortable(progressState, loadScript(CDN.mermaid));
+ AssetReadinessGate: {
+ awaitReady: async function(tempElement, markdown, state) {
+ if (window.MathJax && markdownLikelyContainsMath(markdown)) {
+ updatePdfProgress(state, 30, "Rendering math");
+ try {
+ await runPdfAbortable(state, MathJax.typesetPromise([tempElement]));
+ } catch (err) {
+ console.warn("MathJax rendering issue:", err);
}
- throwIfPdfExportAborted(progressState.signal);
- initMermaid(true);
- await runPdfAbortable(progressState, mermaid.init(undefined, mermaidNodes));
- tempElement.querySelectorAll('.mermaid-container.is-loading').forEach(container => {
- container.classList.remove('is-loading');
- });
- } catch (mermaidError) {
- if (mermaidError instanceof PdfExportCancelledError) throw mermaidError;
- console.warn("Mermaid rendering issue:", mermaidError);
- tempElement.querySelectorAll('.mermaid-container.is-loading').forEach(container => {
- container.classList.remove('is-loading');
- });
+ throwIfPdfExportAborted(state.signal);
+
+ tempElement.querySelectorAll('mjx-assistive-mml').forEach(el => el.remove());
+ tempElement.querySelectorAll('script[type*="math"], script[type*="tex"]').forEach(el => el.remove());
}
- throwIfPdfExportAborted(progressState.signal);
- await waitForPdfFrame(progressState);
+
+ const mermaidNodes = tempElement.querySelectorAll('.mermaid');
+ if (mermaidNodes.length > 0) {
+ updatePdfProgress(state, 40, "Rendering diagrams");
+ try {
+ if (typeof mermaid === 'undefined') {
+ await runPdfAbortable(state, loadScript(CDN.mermaid));
+ }
+ throwIfPdfExportAborted(state.signal);
+ initMermaid(true);
+ await runPdfAbortable(state, mermaid.init(undefined, mermaidNodes));
+ tempElement.querySelectorAll('.mermaid-container.is-loading').forEach(container => {
+ container.classList.remove('is-loading');
+ });
+ } catch (err) {
+ console.warn("Mermaid rendering issue:", err);
+ tempElement.querySelectorAll('.mermaid-container.is-loading').forEach(container => {
+ container.classList.remove('is-loading');
+ });
+ }
+ throwIfPdfExportAborted(state.signal);
+ }
+
+ const images = Array.from(tempElement.querySelectorAll("img"));
+ if (images.length > 0) {
+ updatePdfProgress(state, 50, "Loading images");
+ await Promise.all(images.map(img => {
+ if (img.complete) return Promise.resolve();
+ return new Promise(resolve => {
+ img.addEventListener("load", resolve, { once: true });
+ img.addEventListener("error", resolve, { once: true });
+ });
+ }));
+ await Promise.all(images.map(img => {
+ if (typeof img.decode === "function") {
+ return img.decode().catch(() => {});
+ }
+ return Promise.resolve();
+ }));
+ throwIfPdfExportAborted(state.signal);
+ }
+
+ if (document.fonts && typeof document.fonts.ready === "object") {
+ updatePdfProgress(state, 55, "Loading fonts");
+ await document.fonts.ready;
+ throwIfPdfExportAborted(state.signal);
+ }
+
+ updatePdfProgress(state, 60, "Stabilizing layout");
+ await waitForPdfFrame(state);
+ await waitForPdfFrame(state);
}
+ },
- if (window.MathJax && markdownLikelyContainsMath(markdown)) {
- updatePdfProgress(progressState, 44, "Rendering math");
+ WebPrintBackend: {
+ print: function(fullHtml, state) {
+ return new Promise((resolve, reject) => {
+ try {
+ throwIfPdfExportAborted(state.signal);
+
+ const iframe = document.createElement("iframe");
+ iframe.style.position = "fixed";
+ iframe.style.left = "-9999px";
+ iframe.style.top = "0";
+ iframe.style.width = "100%";
+ iframe.style.height = "100%";
+ document.body.appendChild(iframe);
+
+ const doc = iframe.contentDocument || iframe.contentWindow.document;
+ doc.open();
+ doc.write(fullHtml);
+ doc.close();
+
+ const cleanup = () => {
+ if (iframe.parentNode) {
+ iframe.parentNode.removeChild(iframe);
+ }
+ };
+
+ iframe.contentWindow.focus();
+
+ iframe.contentWindow.addEventListener("afterprint", () => {
+ cleanup();
+ resolve();
+ }, { once: true });
+
+ iframe.contentWindow.print();
+
+ setTimeout(() => {
+ cleanup();
+ resolve();
+ }, 5000);
+ } catch (err) {
+ reject(err);
+ }
+ });
+ }
+ },
+
+ DesktopChromiumSidecarBackend: {
+ print: async function(fullHtml, state) {
+ throwIfPdfExportAborted(state.signal);
+
+ const outputPath = await Neutralino.os.showSaveDialog("Save PDF Document", {
+ filters: [{ name: "PDF files", extensions: ["pdf"] }]
+ });
+
+ if (!outputPath) {
+ throw new Error("Export cancelled by user.");
+ }
+
+ throwIfPdfExportAborted(state.signal);
+
+ let port;
try {
- await runPdfAbortable(progressState, MathJax.typesetPromise([tempElement]));
- } catch (mathJaxError) {
- if (mathJaxError instanceof PdfExportCancelledError) throw mathJaxError;
- console.warn("MathJax rendering issue:", mathJaxError);
+ port = await Neutralino.filesystem.readFile(".pdf_exporter_port");
+ port = port.trim();
+ } catch (err) {
+ throw new Error("Local PDF exporter extension is not running. Please make sure the desktop application is running properly.");
}
- throwIfPdfExportAborted(progressState.signal);
-
- // Hide MathJax assistive elements that cause duplicate text in PDF
- // These are screen reader elements that html2canvas captures as visible
- // Use multiple CSS properties to ensure html2canvas doesn't render them
- const assistiveElements = tempElement.querySelectorAll('mjx-assistive-mml');
- assistiveElements.forEach(el => {
- el.style.display = 'none';
- el.style.visibility = 'hidden';
- el.style.position = 'absolute';
- el.style.width = '0';
- el.style.height = '0';
- el.style.overflow = 'hidden';
- el.remove(); // Remove entirely from DOM
+
+ throwIfPdfExportAborted(state.signal);
+
+ const response = await fetch(`http://127.0.0.1:${port}/export`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ html: fullHtml,
+ outputPath: outputPath
+ }),
+ signal: state.signal
});
- // Also hide any MathJax script elements that might contain source
- const mathScripts = tempElement.querySelectorAll('script[type*="math"], script[type*="tex"]');
- mathScripts.forEach(el => el.remove());
+ if (!response.ok) {
+ const errData = await response.json().catch(() => ({}));
+ throw new Error(errData.error || `HTTP error ${response.status}`);
+ }
+
+ return outputPath;
}
+ },
- await waitForPdfFrame(progressState);
- fitExportElementToContent(tempElement);
- await waitForPdfFrame(progressState);
+ LegacyRasterBackend: {
+ print: async function(tempElement, state) {
+ throwIfPdfExportAborted(state.signal);
- // Analyze and apply page-breaks for graphics (Story 1.1 + 1.2)
- updatePdfProgress(progressState, 55, "Optimizing page breaks");
- const pageBreakAnalysis = applyPageBreaksWithCascade(tempElement, PAGE_CONFIG, 10, progressState.signal);
- throwIfPdfExportAborted(progressState.signal);
+ if (typeof jspdf === 'undefined' || typeof html2canvas === 'undefined') {
+ updatePdfProgress(state, 62, "Loading PDF libraries");
+ await runPdfAbortable(state, Promise.all([loadScript(CDN.jspdf), loadScript(CDN.html2canvas)]));
+ throwIfPdfExportAborted(state.signal);
+ }
- // Scale oversized graphics that can't fit on a single page (Story 1.3)
- if (pageBreakAnalysis.oversizedElements && pageBreakAnalysis.pageHeightPx) {
- handleOversizedElements(pageBreakAnalysis.oversizedElements, pageBreakAnalysis.pageHeightPx, progressState.signal);
- }
- await waitForPdfFrame(progressState);
+ updatePdfProgress(state, 65, "Calculating legacy page breaks");
+ const pageBreakAnalysis = applyPageBreaksWithCascade(tempElement, PAGE_CONFIG, 10, state.signal);
+ throwIfPdfExportAborted(state.signal);
- const pdfOptions = {
- orientation: 'portrait',
- unit: 'mm',
- format: 'a4',
- compress: true,
- hotfixes: ["px_scaling"]
- };
+ if (pageBreakAnalysis.oversizedElements && pageBreakAnalysis.pageHeightPx) {
+ handleOversizedElements(pageBreakAnalysis.oversizedElements, pageBreakAnalysis.pageHeightPx, state.signal);
+ }
+ await waitForPdfFrame(state);
+
+ const pdfOptions = {
+ orientation: 'portrait',
+ unit: 'mm',
+ format: 'a4',
+ compress: true,
+ hotfixes: ["px_scaling"]
+ };
- const pdf = new jspdf.jsPDF(pdfOptions);
- const pageWidth = pdf.internal.pageSize.getWidth();
- const pageHeight = pdf.internal.pageSize.getHeight();
- const margin = 15;
- const contentWidth = pageWidth - (margin * 2);
- const captureScale = choosePdfCanvasScale(tempElement);
-
- updatePdfProgress(progressState, 65, "Capturing document");
- const canvas = await runPdfAbortable(progressState, html2canvas(tempElement, {
- scale: captureScale,
- useCORS: true,
- allowTaint: false,
- logging: false,
- windowWidth: Math.max(PAGE_CONFIG.windowWidth, Math.ceil(tempElement.getBoundingClientRect().width)),
- windowHeight: tempElement.scrollHeight
- }));
- await waitForPdfFrame(progressState);
- throwIfPdfExportAborted(progressState.signal);
-
- const scaleFactor = canvas.width / contentWidth;
- const imgHeight = canvas.height / scaleFactor;
- const pagesCount = Math.ceil(imgHeight / (pageHeight - margin * 2));
-
- updatePdfProgress(progressState, 76, "Rendering pages");
- for (let page = 0; page < pagesCount; page++) {
- throwIfPdfExportAborted(progressState.signal);
- const pageProgress = 76 + ((page + 1) / pagesCount) * 18;
- updatePdfProgress(progressState, pageProgress, `Rendering page ${page + 1} of ${pagesCount}`);
-
- if (page > 0) pdf.addPage();
-
- const sourceY = page * (pageHeight - margin * 2) * scaleFactor;
- const sourceHeight = Math.min(canvas.height - sourceY, (pageHeight - margin * 2) * scaleFactor);
- const destHeight = sourceHeight / scaleFactor;
-
- const pageCanvas = document.createElement('canvas');
- pageCanvas.width = canvas.width;
- pageCanvas.height = sourceHeight;
-
- const ctx = pageCanvas.getContext('2d');
- ctx.drawImage(canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight);
-
- const imgData = pageCanvas.toDataURL('image/png');
- pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, destHeight);
- await waitForPdfFrame(progressState);
- }
-
- throwIfPdfExportAborted(progressState.signal);
- updatePdfProgress(progressState, 98, "Preparing download");
- pdf.save("document.pdf");
- updatePdfProgress(progressState, 100, "Complete");
+ const pdf = new jspdf.jsPDF(pdfOptions);
+ const pageWidth = pdf.internal.pageSize.getWidth();
+ const pageHeight = pdf.internal.pageSize.getHeight();
+ const margin = 15;
+ const contentWidth = pageWidth - (margin * 2);
+ const captureScale = choosePdfCanvasScale(tempElement);
+
+ updatePdfProgress(state, 75, "Capturing document");
+ const canvas = await runPdfAbortable(state, html2canvas(tempElement, {
+ scale: captureScale,
+ useCORS: true,
+ allowTaint: false,
+ logging: false,
+ windowWidth: Math.max(PAGE_CONFIG.windowWidth, Math.ceil(tempElement.getBoundingClientRect().width)),
+ windowHeight: tempElement.scrollHeight
+ }));
+ await waitForPdfFrame(state);
+ throwIfPdfExportAborted(state.signal);
- } catch (error) {
- if (error instanceof PdfExportCancelledError || progressState.signal.aborted) {
- console.info("PDF export cancelled");
- } else {
- console.error("PDF export failed:", error);
- alert("PDF export failed: " + error.message);
+ const scaleFactor = canvas.width / contentWidth;
+ const imgHeight = canvas.height / scaleFactor;
+ const pagesCount = Math.ceil(imgHeight / (pageHeight - margin * 2));
+
+ updatePdfProgress(state, 80, "Rendering pages");
+ for (let page = 0; page < pagesCount; page++) {
+ throwIfPdfExportAborted(state.signal);
+ const pageProgress = 80 + ((page + 1) / pagesCount) * 18;
+ updatePdfProgress(state, pageProgress, `Rendering page ${page + 1} of ${pagesCount}`);
+
+ if (page > 0) pdf.addPage();
+
+ const sourceY = page * (pageHeight - margin * 2) * scaleFactor;
+ const sourceHeight = Math.min(canvas.height - sourceY, (pageHeight - margin * 2) * scaleFactor);
+ const destHeight = sourceHeight / scaleFactor;
+
+ const pageCanvas = document.createElement('canvas');
+ pageCanvas.width = canvas.width;
+ pageCanvas.height = sourceHeight;
+
+ const ctx = pageCanvas.getContext('2d');
+ ctx.drawImage(canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight);
+
+ const imgData = pageCanvas.toDataURL('image/png');
+ pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, destHeight);
+ await waitForPdfFrame(state);
+ }
+
+ throwIfPdfExportAborted(state.signal);
+ updatePdfProgress(state, 98, "Saving document");
+ pdf.save("document.pdf");
}
- } finally {
- cleanupPdfExport(progressState);
}
+ };
+
+ // PDF Export Modal Elements
+ const pdfExportModal = document.getElementById("pdf-export-modal");
+ const pdfExportConfirmBtn = document.getElementById("pdf-export-modal-confirm");
+ const pdfExportCancelBtn = document.getElementById("pdf-export-modal-close");
+ const pdfExportCloseIcon = document.getElementById("pdf-export-modal-close-icon");
+ const pdfCardPrint = document.getElementById("pdf-card-print");
+ const pdfCardLegacy = document.getElementById("pdf-card-legacy");
+ const pdfEnginePrintInput = document.getElementById("pdf-engine-print");
+ const pdfEngineLegacyInput = document.getElementById("pdf-engine-legacy");
+
+ function syncPdfCardStyles() {
+ if (pdfEnginePrintInput.checked) {
+ pdfCardPrint.classList.add("is-selected");
+ pdfCardLegacy.classList.remove("is-selected");
+ } else {
+ pdfCardLegacy.classList.add("is-selected");
+ pdfCardPrint.classList.remove("is-selected");
+ }
+ }
+
+ if (pdfEnginePrintInput && pdfEngineLegacyInput) {
+ pdfEnginePrintInput.addEventListener("change", syncPdfCardStyles);
+ pdfEngineLegacyInput.addEventListener("change", syncPdfCardStyles);
+ }
+
+ function openPdfExportModal() {
+ pdfEnginePrintInput.checked = true;
+ syncPdfCardStyles();
+ pdfExportModal.style.display = "";
+ requestAnimationFrame(() => {
+ pdfExportModal.classList.add("is-visible");
+ pdfExportModal.setAttribute("aria-hidden", "false");
+ });
+ }
+
+ function closePdfExportModal() {
+ pdfExportModal.classList.remove("is-visible");
+ pdfExportModal.setAttribute("aria-hidden", "true");
+ pdfExportModal.addEventListener("transitionend", function handler() {
+ pdfExportModal.style.display = "none";
+ pdfExportModal.removeEventListener("transitionend", handler);
+ });
+ }
+
+ if (pdfExportCancelBtn) pdfExportCancelBtn.addEventListener("click", closePdfExportModal);
+ if (pdfExportCloseIcon) pdfExportCloseIcon.addEventListener("click", closePdfExportModal);
+ if (pdfExportModal) {
+ pdfExportModal.addEventListener("click", function (e) {
+ if (e.target === pdfExportModal) closePdfExportModal();
+ });
+ }
+
+ if (pdfExportConfirmBtn) {
+ pdfExportConfirmBtn.addEventListener("click", async function() {
+ closePdfExportModal();
+
+ if (activePdfExport) return;
+
+ const progressState = createPdfProgressState();
+ activePdfExport = progressState;
+ setPdfExportTriggersBusy(progressState, true);
+ document.body.appendChild(progressState.overlay);
+ updatePdfProgress(progressState, 3, "Starting");
+ progressState.overlay.querySelector(".pdf-progress-cancel")?.focus();
+
+ try {
+ const isLegacy = pdfEngineLegacyInput.checked;
+ const markdown = markdownEditor.value;
+
+ const { fullHtml, tempElement } = await PdfExportEngine.ExportDocumentBuilder.build(markdown, progressState);
+
+ if (isLegacy) {
+ await PdfExportEngine.LegacyRasterBackend.print(tempElement, progressState);
+ } else {
+ updatePdfProgress(progressState, 75, "Generating PDF");
+ if (typeof Neutralino !== 'undefined') {
+ await PdfExportEngine.DesktopChromiumSidecarBackend.print(fullHtml, progressState);
+ } else {
+ await PdfExportEngine.WebPrintBackend.print(fullHtml, progressState);
+ }
+ }
+
+ updatePdfProgress(progressState, 100, "Complete");
+ } catch (error) {
+ if (error instanceof PdfExportCancelledError || progressState.signal.aborted) {
+ console.info("PDF export cancelled");
+ } else {
+ console.error("PDF export failed:", error);
+ alert("PDF export failed: " + error.message);
+ }
+ } finally {
+ cleanupPdfExport(progressState);
+ }
+ });
+ }
+
+ exportPdf.addEventListener("click", async function (event) {
+ event.preventDefault();
+ openPdfExportModal();
});
copyMarkdownButton.addEventListener("click", function () {
diff --git a/styles.css b/styles.css
index f080608..34c6d77 100644
--- a/styles.css
+++ b/styles.css
@@ -1,3848 +1,3962 @@
-:root {
- --bg-color: #ffffff;
- --editor-bg: #f6f8fa;
- --preview-bg: #ffffff; /* Preview background for light mode */
- --text-color: #24292e;
- --text-secondary: #57606a;
- --font-mono: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
- --color-danger-fg: #d73a49;
- --preview-text-color: #24292e; /* Text color for preview in light mode */
- --border-color: #e1e4e8;
- --header-bg: #f6f8fa;
- --button-bg: #f6f8fa;
- --button-hover: #e1e4e8;
- --button-active: #d1d5da;
- --scrollbar-thumb: #c1c1c1;
- --scrollbar-track: #f1f1f1;
- --accent-color: #0366d6;
- --table-bg: #ffffff; /* Table background for light mode */
- --code-bg: #f6f8fa; /* Code block background for light mode */
- --skeleton-bg: #e2e8f0;
- --skeleton-glow: rgba(255, 255, 255, 0.65);
-
- /* Find & Replace Panel custom properties (PERF-010 consolidated) */
- --fr-bg: rgba(255, 255, 255, 0.95);
- --fr-border: #d0d7de;
- --fr-shadow: 0 8px 24px rgba(140, 149, 159, 0.2);
- --fr-btn-active: #0969da;
- --fr-btn-active-bg: #ddf4ff;
- --fr-match-highlight: #ffdf5d;
- --fr-match-active: #ff9b30;
- --fr-error-bg: #ffebe9;
- --fr-error-border: #ff8577;
- --fr-text-danger: #cf222e;
-}
-
-[data-theme="dark"] {
- --bg-color: #0d1117;
- --editor-bg: #161b22;
- --preview-bg: #0d1117; /* Preview background for dark mode */
- --text-color: #c9d1d9;
- --text-secondary: #8b949e;
- --color-danger-fg: #f85149;
- --preview-text-color: #c9d1d9; /* Text color for preview in dark mode */
- --border-color: #30363d;
- --header-bg: #161b22;
- --button-bg: #21262d;
- --button-hover: #30363d;
- --button-active: #3b434b;
- --scrollbar-thumb: #484f58;
- --scrollbar-track: #21262d;
- --accent-color: #58a6ff;
- --table-bg: #161b22; /* Table background for dark mode */
- --code-bg: #161b22; /* Code block background for dark mode */
- --skeleton-bg: #2d3139;
- --skeleton-glow: rgba(255, 255, 255, 0.08);
-
- /* Find & Replace Panel custom properties for dark mode */
- --fr-bg: rgba(28, 33, 40, 0.98);
- --fr-border: #444c56;
- --fr-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
- --fr-btn-active: #2f81f7;
- --fr-btn-active-bg: rgba(56, 139, 253, 0.15);
- --fr-match-highlight: rgba(187, 128, 9, 0.4);
- --fr-match-active: #f1e05a;
- --fr-error-bg: rgba(248, 81, 73, 0.1);
- --fr-error-border: rgba(248, 81, 73, 0.4);
- --fr-text-danger: #ff7b72;
-}
-
-* {
- box-sizing: border-box;
- margin: 0;
- padding: 0;
-}
-
-@media (min-width: 768px) {
- html,
- body {
- height: 100%;
- overflow: hidden;
- }
-}
-
-body {
- background-color: var(--bg-color);
- color: var(--text-color);
- /* PERF-021: Removed background-color transition to avoid full-viewport repaint on theme toggle */
- transition: color 0.15s ease;
- min-height: 100vh;
- font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Hiragino Kaku Gothic ProN", Meiryo, "Malgun Gothic", "Apple SD Gothic Neo", "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
-}
-
-.app-header {
- background-color: var(--header-bg);
- border-bottom: 1px solid var(--border-color);
- padding: 0.35rem 0.75rem;
- transition: background-color 0.3s ease;
- position: relative;
- z-index: 100;
- flex-shrink: 0;
-}
-
-.app-container {
- height: 100vh;
- display: flex;
- flex-direction: column;
- overflow: hidden;
-}
-
-.content-container {
- display: flex;
- flex: 1;
- overflow: hidden;
-}
-
-.editor-pane, .preview-pane {
- flex: 1;
- padding: 20px;
- overflow-y: auto;
- position: relative;
- /* PERF-025: Shortened transition and scoped to background-color only */
- transition: background-color 0.15s ease;
-}
-
-.editor-pane {
- background-color: var(--editor-bg);
- border-right: 1px solid var(--border-color);
- padding-right: 0px;
- --line-number-gutter: 0px;
-}
-
-.preview-pane {
- background-color: var(--preview-bg); /* Using the new variable for preview background */
-}
-
-/* Custom scrollbar */
-.editor-pane::-webkit-scrollbar,
-.preview-pane::-webkit-scrollbar,
-#markdown-editor::-webkit-scrollbar {
- width: 8px;
- height: 8px;
-}
-
-.editor-pane::-webkit-scrollbar-track,
-.preview-pane::-webkit-scrollbar-track,
-#markdown-editor::-webkit-scrollbar-track {
- background: var(--scrollbar-track);
-}
-
-.editor-pane::-webkit-scrollbar-thumb,
-.preview-pane::-webkit-scrollbar-thumb,
-#markdown-editor::-webkit-scrollbar-thumb {
- background: var(--scrollbar-thumb);
- border-radius: 4px;
-}
-
-.editor-pane::-webkit-scrollbar-thumb:hover,
-.preview-pane::-webkit-scrollbar-thumb:hover,
-#markdown-editor::-webkit-scrollbar-thumb:hover {
- background: var(--button-active);
-}
-
-#markdown-editor {
- width: 100%;
- height: 100%;
- border: none;
- background-color: transparent;
- color: var(--text-color);
- resize: none;
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
- font-size: 14px;
- line-height: 1.5;
- padding: 10px;
- padding-left: calc(10px + var(--line-number-gutter));
- transition: background-color 0.3s ease, color 0.3s ease;
- overflow-y: auto;
- position: relative;
- z-index: 3;
-}
-
-#markdown-editor:focus {
- outline: none;
-}
-
-.preview-pane {
- padding: 20px;
-}
-
-.markdown-body {
- padding: 20px;
- width: 100%;
- background-color: var(--preview-bg); /* Ensuring the markdown content matches preview background */
- color: var(--preview-text-color); /* Using specific text color for preview content */
-}
-
-.markdown-body a.reference-link {
- font-size: 0.75em;
- letter-spacing: -0.02em;
- line-height: 1;
- vertical-align: super;
- position: relative;
- top: 0.08em;
-}
-
-/* Style tables in light mode */
-.markdown-body table {
- background-color: var(--table-bg);
- border-color: var(--border-color);
-}
-
-.markdown-body table tr {
- background-color: var(--table-bg);
- border-top: 1px solid var(--border-color);
-}
-
-.markdown-body table tr:nth-child(2n) {
- background-color: var(--bg-color);
-}
-
-/* Style code blocks in light mode */
-.markdown-body pre {
- background-color: var(--code-bg);
- border-radius: 6px;
-}
-
-.markdown-body code {
- background-color: var(--code-bg);
- border-radius: 3px;
- padding: 0.2em 0.4em;
-}
-
-.markdown-body img.emoji-inline {
- width: 1em;
- height: 1em;
- vertical-align: -0.1em;
-}
-
-.markdown-body ul,
-.markdown-body ol {
- padding-left: 2em;
- margin: 0.4em 0;
-}
-
-.markdown-body ul ul,
-.markdown-body ul ol,
-.markdown-body ol ul,
-.markdown-body ol ol {
- margin-top: 0.2em;
- margin-bottom: 0.2em;
-}
-
-.markdown-body ul.contains-task-list,
-.markdown-body li.task-list-item {
- list-style: none;
-}
-
-.markdown-body ul.contains-task-list {
- padding-left: 2em;
-}
-
-.markdown-body li.task-list-item input[type="checkbox"] {
- margin: 0 0.5em 0.2em 0;
- vertical-align: middle;
- pointer-events: none;
-}
-
-.markdown-body li.task-list-item::marker {
- content: "";
-}
-
-.markdown-body li:has(> input[type="checkbox"]) {
- list-style: none;
-}
-
-.markdown-body li:has(> input[type="checkbox"])::marker {
- content: "";
-}
-
-.markdown-body ul:has(> li > input[type="checkbox"]) {
- list-style: none;
- padding-left: 2em;
-}
-
-.markdown-body .footnotes {
- margin-top: 1.5rem;
- font-size: 0.9em;
-}
-
-.markdown-body .footnotes ol {
- padding-left: 1.5em;
-}
-
-.markdown-body .footnotes ol > li::marker {
- content: "[" counter(list-item) "] ";
- font-weight: 600;
-}
-
-.markdown-body .footnotes li > p {
- margin: 0.2em 0;
-}
-
-.markdown-body .footnote-ref a,
-.markdown-body .footnote-backref {
- text-decoration: none;
-}
-
-.markdown-body .footnote-backref {
- margin-left: 0.4em;
-}
-
-.markdown-body .markdown-alert {
- padding: 0.5rem 1rem;
- margin-bottom: 16px;
- border-left: 0.25em solid;
- border-radius: 0.375rem;
-}
-
-.markdown-body .markdown-alert > :last-child {
- margin-bottom: 0;
-}
-
-.markdown-body .markdown-alert-title {
- margin: 0 0 8px;
- font-weight: 600;
- line-height: 1.25;
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.markdown-body .markdown-alert-icon {
- display: inline-flex;
- width: 16px;
- height: 16px;
-}
-
-.markdown-body .markdown-alert-icon svg {
- width: 16px;
- height: 16px;
- fill: currentColor;
-}
-
-.markdown-body .markdown-alert-note {
- color: #0969da;
- border-left-color: #0969da;
- background-color: #ddf4ff;
-}
-
-.markdown-body .markdown-alert-tip {
- color: #1a7f37;
- border-left-color: #1a7f37;
- background-color: #dafbe1;
-}
-
-.markdown-body .markdown-alert-important {
- color: #8250df;
- border-left-color: #8250df;
- background-color: #fbefff;
-}
-
-.markdown-body .markdown-alert-warning {
- color: #9a6700;
- border-left-color: #9a6700;
- background-color: #fff8c5;
-}
-
-.markdown-body .markdown-alert-caution {
- color: #cf222e;
- border-left-color: #cf222e;
- background-color: #ffebe9;
-}
-
-.markdown-body .markdown-alert > *:not(.markdown-alert-title) {
- color: var(--preview-text-color);
-}
-
-[data-theme="dark"] .markdown-body .markdown-alert-note {
- color: #4493f8;
- background-color: rgba(31, 111, 235, 0.15);
- border-left-color: #4493f8;
-}
-
-[data-theme="dark"] .markdown-body .markdown-alert-tip {
- color: #3fb950;
- background-color: rgba(35, 134, 54, 0.15);
- border-left-color: #3fb950;
-}
-
-[data-theme="dark"] .markdown-body .markdown-alert-important {
- color: #ab7df8;
- background-color: rgba(137, 87, 229, 0.15);
- border-left-color: #ab7df8;
-}
-
-[data-theme="dark"] .markdown-body .markdown-alert-warning {
- color: #d29922;
- background-color: rgba(210, 153, 34, 0.18);
- border-left-color: #d29922;
-}
-
-[data-theme="dark"] .markdown-body .markdown-alert-caution {
- color: #f85149;
- background-color: rgba(248, 81, 73, 0.18);
- border-left-color: #f85149;
-}
-
-.toolbar {
- display: flex;
- gap: 8px;
- align-items: center;
-}
-
-.toolbar-group {
- display: inline-flex;
- align-items: center;
- gap: 6px;
-}
-
-.toolbar-divider {
- width: 1px;
- height: 20px;
- background-color: var(--border-color);
- opacity: 0.7;
-}
-
-.tool-button {
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- color: var(--text-color);
- border-radius: 5px;
- padding: 4px 8px;
- font-size: 13px;
- cursor: pointer;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 4px;
- /* PERF-016: Specific transition properties instead of 'all' to avoid animating layout-triggering properties */
- transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
-}
-
-.tool-button:hover {
- background-color: var(--button-hover);
-}
-
-.tool-button:active {
- background-color: var(--button-active);
-}
-
-.tool-button:disabled,
-.tool-button[aria-disabled="true"] {
- cursor: not-allowed;
- opacity: 0.5;
-}
-
-.tool-button i {
- font-size: 15px;
-}
-
-.tool-button.is-active,
-.tool-button.is-active:hover {
- border-color: var(--accent-color);
- color: var(--accent-color);
- background-color: rgba(3, 102, 214, 0.08);
-}
-
-.btn-text {
- display: none;
-}
-
-.toolbar .tool-button {
- height: 28px;
- min-width: 28px;
-}
-
-.toolbar .tool-button.sync-active {
- border-color: var(--accent-color);
- color: var(--accent-color);
-}
-
-.file-input {
- display: none;
-}
-
-/* Drag overlay: full-screen drop target shown when user drags a file over the window */
-.drag-overlay {
- display: none;
- position: fixed;
- inset: 0;
- z-index: 9999;
- background-color: rgba(0, 0, 0, 0.45);
- pointer-events: none;
- align-items: center;
- justify-content: center;
-}
-
-.drag-overlay.active {
- display: flex;
- pointer-events: auto;
-}
-
-.drag-overlay-inner {
- border: 3px dashed var(--accent-color);
- border-radius: 12px;
- padding: 48px 64px;
- text-align: center;
- color: #ffffff;
- background-color: rgba(3, 102, 214, 0.15);
- animation: overlayPulse 1.4s ease-in-out infinite;
-}
-
-.drag-overlay-icon {
- display: block;
- font-size: 3rem;
- margin-bottom: 12px;
- color: var(--accent-color);
-}
-
-.drag-overlay-text {
- font-size: 1.4rem;
- font-weight: 600;
- margin-bottom: 4px;
-}
-
-.drag-overlay-sub {
- font-size: 0.85rem;
- opacity: 0.75;
- margin-bottom: 0;
-}
-
-@keyframes overlayPulse {
- 0%, 100% { transform: scale(1); }
- 50% { transform: scale(1.015); }
-}
-
-/* Editor drop hint: subtle text at bottom of editor pane, shown only when empty */
-.drop-hint {
- position: absolute;
- bottom: 14px;
- left: 0;
- right: 0;
- text-align: center;
- font-size: 0.75rem;
- color: var(--text-color);
- opacity: 0.35;
- pointer-events: none;
- user-select: none;
- z-index: 3;
-}
-
-.editor-pane:has(#markdown-editor:not(:placeholder-shown)) .drop-hint {
- display: none;
-}
-
-.line-numbers {
- position: absolute;
- top: 20px;
- bottom: 20px;
- left: 20px;
- width: var(--line-number-gutter);
- padding: 10px 8px 10px 0;
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
- font-size: 14px;
- line-height: 1.5;
- text-align: right;
- color: var(--text-secondary);
- background-color: var(--editor-bg);
- border-right: 1px solid var(--border-color);
- box-sizing: border-box;
- overflow: hidden;
- pointer-events: none;
- user-select: none;
- z-index: 2;
- font-variant-numeric: tabular-nums;
-}
-
-.line-numbers .line-number {
- display: block;
- height: auto;
-}
-
-.editor-highlight-layer {
- position: absolute;
- inset: 20px 0 20px calc(20px + var(--line-number-gutter));
- padding: 10px;
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
- font-size: 14px;
- line-height: 1.5;
- white-space: pre-wrap;
- word-wrap: break-word;
- color: transparent;
- pointer-events: none;
- overflow: auto;
- background-color: var(--editor-bg);
- border-radius: 4px;
- z-index: 1;
-}
-
-.editor-highlight-layer::-webkit-scrollbar {
- width: 8px;
- height: 8px;
-}
-
-.editor-highlight-layer::-webkit-scrollbar-thumb {
- background: transparent;
-}
-
-.editor-highlight-layer::-webkit-scrollbar-track {
- background: transparent;
-}
-
-.find-highlight {
- background-color: var(--fr-match-highlight, rgba(255, 223, 93, 0.4)) !important;
- border-radius: 2px;
- color: transparent !important;
-}
-
-.find-highlight.active {
- background-color: var(--fr-match-active, #ff9b30) !important;
- color: transparent !important;
-}
-
-/* Dropdown improvements */
-.dropdown-menu {
- background-color: var(--bg-color);
- border-color: var(--border-color);
-}
-
-.dropdown-item {
- color: var(--text-color);
-}
-
-.dropdown-item:hover, .dropdown-item:focus {
- background-color: var(--button-hover);
- color: var(--text-color);
-}
-
-/* Markdown formatting toolbar */
-.markdown-format-toolbar {
- display: flex;
- align-items: center;
- height: 34px;
- padding: 0 6px;
- background-color: var(--header-bg);
- border-bottom: 1px solid var(--border-color);
- overflow-x: auto;
- overflow-y: hidden;
- flex-shrink: 0;
- scrollbar-width: none;
- -ms-overflow-style: none;
-}
-
-.markdown-format-toolbar::-webkit-scrollbar {
- display: none;
-}
-
-.markdown-toolbar-group {
- display: flex;
- align-items: center;
- gap: 2px;
- height: 100%;
- padding: 0 6px;
- border-right: 1px solid var(--border-color);
- flex-shrink: 0;
-}
-
-.markdown-toolbar-group:first-child {
- padding-left: 0;
-}
-
-.markdown-toolbar-group:last-child {
- border-right: none;
- padding-right: 0;
-}
-
-.markdown-tool-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 26px;
- height: 26px;
- border: 1px solid transparent;
- border-radius: 4px;
- background: transparent;
- color: var(--text-color);
- cursor: pointer;
- font-size: 14px;
- line-height: 1;
- padding: 0;
- transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
-}
-
-.markdown-tool-btn:hover,
-.markdown-tool-btn:focus-visible {
- background-color: var(--button-hover);
- border-color: var(--border-color);
- color: var(--accent-color);
-}
-
-.markdown-tool-btn:active {
- background-color: var(--button-active);
-}
-
-.markdown-tool-btn:disabled,
-.markdown-tool-btn.disabled {
- opacity: 0.4;
- cursor: not-allowed;
- pointer-events: none;
-}
-
-.markdown-tool-btn i {
- font-size: 15px;
-}
-
-.markdown-tool-btn[data-md-action="reference"] i::before {
- content: "[ ]";
- font-style: normal;
- font-size: 12px;
- letter-spacing: -0.12em;
-}
-
-.markdown-tool-btn.text-tool {
- width: auto;
- min-width: 26px;
- padding: 0 5px;
- font-weight: 600;
- font-family: Georgia, "Times New Roman", serif;
-}
-
-.heading-group .markdown-tool-btn {
- min-width: 30px;
-}
-
-
-
-/* Loading indicators */
-.loading {
- opacity: 0.6;
- pointer-events: none;
-}
-
-/* Focus outline for accessibility */
-button:focus,
-a:focus {
- outline: 2px solid var(--accent-color);
- outline-offset: 2px;
-}
-
-/* Animation for copied message */
-@keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
-}
-
-/* Tooltip styles */
-.tooltip {
- position: absolute;
- background: var(--button-bg);
- border: 1px solid var(--border-color);
- padding: 5px 8px;
- border-radius: 4px;
- font-size: 12px;
- z-index: 1000;
- animation: fadeIn 0.2s ease;
-}
-
-/* Styles for GitHub markdown preview light mode */
-.markdown-body {
- color-scheme: light;
- --color-prettylights-syntax-comment: #6a737d;
- --color-prettylights-syntax-constant: #005cc5;
- --color-prettylights-syntax-entity: #6f42c1;
- --color-prettylights-syntax-storage-modifier-import: #24292e;
- --color-prettylights-syntax-entity-tag: #22863a;
- --color-prettylights-syntax-keyword: #cf222e;
- --color-prettylights-syntax-string: #032f62;
- --color-prettylights-syntax-variable: #e36209;
- --color-prettylights-syntax-brackethighlighter-unmatched: #b31d28;
- --color-prettylights-syntax-invalid-illegal-text: #fafbfc;
- --color-prettylights-syntax-invalid-illegal-bg: #b31d28;
- --color-prettylights-syntax-carriage-return-text: #fafbfc;
- --color-prettylights-syntax-carriage-return-bg: #d73a49;
- --color-prettylights-syntax-string-regexp: #22863a;
- --color-prettylights-syntax-markup-list: #735c0f;
- --color-prettylights-syntax-markup-heading: #005cc5;
- --color-prettylights-syntax-markup-italic: #24292e;
- --color-prettylights-syntax-markup-bold: #24292e;
- --color-prettylights-syntax-markup-deleted-text: #b31d28;
- --color-prettylights-syntax-markup-deleted-bg: #ffeef0;
- --color-prettylights-syntax-markup-inserted-text: #22863a;
- --color-prettylights-syntax-markup-inserted-bg: #f0fff4;
- --color-prettylights-syntax-markup-changed-text: #e36209;
- --color-prettylights-syntax-markup-changed-bg: #ffebda;
- --color-prettylights-syntax-markup-ignored-text: #f6f8fa;
- --color-prettylights-syntax-markup-ignored-bg: #005cc5;
- --color-prettylights-syntax-meta-diff-range: #6f42c1;
- --color-prettylights-syntax-brackethighlighter-angle: #586069;
- --color-prettylights-syntax-sublimelinter-gutter-mark: #e1e4e8;
- --color-prettylights-syntax-constant-other-reference-link: #032f62;
- --color-fg-default: #24292e;
- --color-fg-muted: #586069;
- --color-fg-subtle: #6a737d;
- --color-canvas-default: #ffffff;
- --color-canvas-subtle: #f6f8fa;
- --color-border-default: #e1e4e8;
- --color-border-muted: #eaecef;
- --color-neutral-muted: rgba(175,184,193,0.2);
- --color-accent-fg: #0366d6;
- --color-accent-emphasis: #0366d6;
- --color-attention-subtle: #fff5b1;
- --color-danger-fg: #d73a49;
-}
-
-/* Styles for GitHub markdown preview dark mode */
-[data-theme="dark"] .markdown-body {
- color-scheme: dark;
- --color-prettylights-syntax-comment: #8b949e;
- --color-prettylights-syntax-constant: #79c0ff;
- --color-prettylights-syntax-entity: #d2a8ff;
- --color-prettylights-syntax-storage-modifier-import: #c9d1d9;
- --color-prettylights-syntax-entity-tag: #7ee787;
- --color-prettylights-syntax-keyword: #ff7b72;
- --color-prettylights-syntax-string: #a5d6ff;
- --color-prettylights-syntax-variable: #ffa657;
- --color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
- --color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
- --color-prettylights-syntax-invalid-illegal-bg: #8e1519;
- --color-prettylights-syntax-carriage-return-text: #f0f6fc;
- --color-prettylights-syntax-carriage-return-bg: #b62324;
- --color-prettylights-syntax-string-regexp: #7ee787;
- --color-prettylights-syntax-markup-list: #f2cc60;
- --color-prettylights-syntax-markup-heading: #1f6feb;
- --color-prettylights-syntax-markup-italic: #c9d1d9;
- --color-prettylights-syntax-markup-bold: #c9d1d9;
- --color-prettylights-syntax-markup-deleted-text: #ffdcd7;
- --color-prettylights-syntax-markup-deleted-bg: #67060c;
- --color-prettylights-syntax-markup-inserted-text: #aff5b4;
- --color-prettylights-syntax-markup-inserted-bg: #033a16;
- --color-prettylights-syntax-markup-changed-text: #ffdfb6;
- --color-prettylights-syntax-markup-changed-bg: #5a1e02;
- --color-prettylights-syntax-markup-ignored-text: #c9d1d9;
- --color-prettylights-syntax-markup-ignored-bg: #1158c7;
- --color-prettylights-syntax-meta-diff-range: #d2a8ff;
- --color-prettylights-syntax-brackethighlighter-angle: #8b949e;
- --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
- --color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
- --color-fg-default: #c9d1d9;
- --color-fg-muted: #8b949e;
- --color-fg-subtle: #484f58;
- --color-canvas-default: #0d1117;
- --color-canvas-subtle: #161b22;
- --color-border-default: #30363d;
- --color-border-muted: #21262d;
- --color-neutral-muted: rgba(110,118,129,0.4);
- --color-accent-fg: #58a6ff;
- --color-accent-emphasis: #1f6feb;
- --color-attention-subtle: rgba(187,128,9,0.15);
- --color-danger-fg: #f85149;
-}
-
-/* Override specific styles for dark mode tables and code */
-[data-theme="dark"] .markdown-body table tr {
- background-color: var(--table-bg);
-}
-
-[data-theme="dark"] .markdown-body table tr:nth-child(2n) {
- background-color: #1c2128; /* Slightly lighter than base dark background */
-}
-
-[data-theme="dark"] .markdown-body pre {
- background-color: var(--code-bg);
-}
-
-[data-theme="dark"] .markdown-body code {
- background-color: var(--code-bg);
-}
-
-/* Syntax Highlighting Mapping to GitHub Variables */
-.hljs {
- color: var(--color-fg-default);
-}
-.hljs-doctag,
-.hljs-keyword,
-.hljs-meta .hljs-keyword,
-.hljs-template-tag,
-.hljs-template-variable,
-.hljs-type,
-.hljs-variable.language_ {
- color: var(--color-prettylights-syntax-keyword);
-}
-.hljs-title,
-.hljs-title.class_,
-.hljs-title.class_.inherited__,
-.hljs-title.function_ {
- color: var(--color-prettylights-syntax-entity);
-}
-.hljs-attr,
-.hljs-attribute,
-.hljs-literal,
-.hljs-meta,
-.hljs-number,
-.hljs-operator,
-.hljs-variable,
-.hljs-selector-attr,
-.hljs-selector-class,
-.hljs-selector-id {
- color: var(--color-prettylights-syntax-constant);
-}
-.hljs-regexp,
-.hljs-string,
-.hljs-meta .hljs-string {
- color: var(--color-prettylights-syntax-string);
-}
-.hljs-built_in,
-.hljs-symbol {
- color: var(--color-prettylights-syntax-variable);
-}
-.hljs-comment,
-.hljs-code,
-.hljs-formula {
- color: var(--color-prettylights-syntax-comment);
-}
-.hljs-name,
-.hljs-quote,
-.hljs-selector-tag,
-.hljs-selector-pseudo {
- color: var(--color-prettylights-syntax-entity-tag);
-}
-.hljs-subst {
- color: var(--color-fg-default);
-}
-.hljs-section {
- color: var(--color-prettylights-syntax-markup-heading);
- font-weight: bold;
-}
-.hljs-bullet {
- color: var(--color-prettylights-syntax-constant);
-}
-.hljs-emphasis {
- color: var(--color-fg-default);
- font-style: italic;
-}
-.hljs-strong {
- color: var(--color-fg-default);
- font-weight: bold;
-}
-.hljs-addition {
- color: var(--color-prettylights-syntax-markup-inserted-text);
- background-color: var(--color-prettylights-syntax-markup-inserted-bg);
-}
-.hljs-deletion {
- color: var(--color-prettylights-syntax-markup-deleted-text);
- background-color: var(--color-prettylights-syntax-markup-deleted-bg);
-}
-
-.stats-container {
- font-size: 0.8rem;
- color: var(--text-color);
-}
-
-.stat-item {
- align-items: center;
-}
-
-.stat-item i {
- font-size: 0.9rem;
- opacity: 0.8;
-}
-
-#importDropdown,
-#exportDropdown,
-#languageDropdown {
- font-size: 0.8rem;
-}
-
-#importDropdown i,
-#exportDropdown i,
-#languageDropdown i {
- font-size: 0.9rem;
-}
-
-/* Ensure desktop dropdown menu options match the stats-container font size */
-[aria-labelledby="importDropdown"] .dropdown-item,
-[aria-labelledby="exportDropdown"] .dropdown-item,
-[aria-labelledby="languageDropdown"] .dropdown-item {
- font-size: 0.8rem;
-}
-
-/* Ensure mobile menu import, export, and language dropdown triggers/options match the mobile stats-container font size */
-#mobile-import-button,
-#mobile-import-github-button,
-#mobile-export-md,
-#mobile-export-html,
-#mobile-export-pdf,
-#mobileLanguageDropdown {
- font-size: 0.9rem !important;
-}
-
-[aria-labelledby="mobileLanguageDropdown"] .dropdown-item {
- font-size: 0.9rem;
-}
-
-.editor-pane {
- overflow: hidden;
-}
-
-/* Mobile Menu Styles */
-.mobile-menu {
- display: none;
- position: relative;
- z-index: 1001;
-}
-
-
-
-/* slideāin panel */
-.mobile-menu-panel {
- position: fixed;
- top: 0;
- right: -300px;
- width: 280px;
- height: 100vh;
- background-color: var(--bg-color);
- box-shadow: -2px 0 10px rgba(0, 0, 0, 0.2);
- transition: right 0.3s ease;
- overflow-y: auto;
- padding: 1rem;
- display: flex;
- flex-direction: column;
- z-index: 1002;
-}
-
-.mobile-menu-panel.active {
- right: 0;
-}
-
-/* translucent overlay behind panel */
-.mobile-menu-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100vh;
- background-color: rgba(0, 0, 0, 0.5);
- opacity: 0;
- visibility: hidden;
- pointer-events: none;
- transition: opacity 0.3s ease, visibility 0.3s ease;
- z-index: 1000;
-}
-
-.mobile-menu-overlay.active {
- opacity: 1;
- visibility: visible;
- pointer-events: auto;
-}
-
-/* header inside mobile menu */
-.mobile-menu-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 1rem;
-}
-
-.mobile-menu-header h5 {
- margin: 0;
- font-size: 1.25rem;
- color: var(--text-color);
-}
-
-/* stats section in mobile menu */
-.mobile-stats-container {
- border-bottom: 1px solid var(--border-color);
- padding-bottom: 0.75rem;
- margin-bottom: 1rem;
-}
-
-.mobile-stats-container .stat-item {
- font-size: 0.9rem;
- color: var(--text-color);
- display: flex;
- align-items: center;
-}
-
-.mobile-stats-container .stat-item i {
- margin-right: 0.5em;
- opacity: 0.8;
-}
-
-/* menu buttons list */
-.mobile-menu-items {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- flex-grow: 1;
-}
-
-/* each menu item */
-.mobile-menu-item {
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- color: var(--text-color);
- border-radius: 6px;
- padding: 0.6rem 1rem;
- font-size: 1rem;
- text-align: left;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- transition: background-color 0.2s ease;
- cursor: pointer;
-}
-
-.mobile-menu-item:hover {
- background-color: var(--button-hover);
-}
-
-.mobile-menu-item:active {
- background-color: var(--button-active);
-}
-
-/* close button override */
-#close-mobile-menu.tool-button {
- padding: 0.25rem 0.5rem;
- font-size: 1rem;
-}
-
-/* Mobile document tabs section */
-.mobile-tabs-section {
- border-bottom: 1px solid var(--border-color);
- padding-bottom: 0.75rem;
-}
-
-.mobile-tabs-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 0.5rem;
-}
-
-.mobile-tabs-label {
- font-size: 0.85rem;
- font-weight: 600;
- color: var(--text-color);
- opacity: 0.8;
- text-transform: uppercase;
- letter-spacing: 0.04em;
-}
-
-.mobile-new-tab-btn {
- background: none;
- border: 1px solid var(--border-color);
- border-radius: 4px;
- color: var(--text-color);
- padding: 2px 7px;
- font-size: 0.9rem;
- cursor: pointer;
- display: flex;
- align-items: center;
- transition: background-color 0.15s ease;
-}
-
-.mobile-new-tab-btn:hover {
- background-color: var(--button-hover);
-}
-
-.mobile-tab-list {
- display: flex;
- flex-direction: column;
- gap: 4px;
- max-height: 180px;
- overflow-y: auto;
-}
-
-.mobile-tab-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- border-radius: 6px;
- padding: 0.45rem 0.75rem;
- font-size: 0.9rem;
- color: var(--text-color);
- cursor: pointer;
- transition: background-color 0.15s ease;
- gap: 0.5rem;
-}
-
-.mobile-tab-item:hover {
- background-color: var(--button-hover);
-}
-
-.mobile-tab-item.active {
- border-color: var(--accent-color);
- color: var(--accent-color);
- background-color: var(--bg-color);
-}
-
-.mobile-tab-title {
- flex: 1;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- min-width: 0;
-}
-
-.mobile-tab-item .tab-menu-btn {
- opacity: 0.6;
-}
-
-.mobile-tab-item:hover .tab-menu-btn,
-.mobile-tab-item.active .tab-menu-btn {
- opacity: 0.8;
-}
-
-#mobile-tab-reset-btn {
- margin-left: 0;
- height: auto;
- padding: 0.45rem 0.75rem;
- justify-content: center;
- font-size: 0.9rem;
-}
-
-/* ==========================================
- NAVBAR RESPONSIVE BREAKPOINTS
- >= 1080px : full desktop navbar
- < 1080px : mobile hamburger + stacked panes
- ========================================== */
-
-/* Mobile / tablet (< 1080px): switch to hamburger, stack panes */
-@media (max-width: 1079px) {
- /* Override Bootstrap d-md-flex / d-md-none so the breakpoint is 1080px */
- .stats-container,
- .toolbar {
- display: none !important;
- }
-
- /* Expand touch target sizes to meet WCAG mobile guidelines */
- .markdown-tool-btn {
- width: 36px !important;
- height: 36px !important;
- font-size: 16px !important;
- }
- .markdown-format-toolbar {
- height: 44px !important;
- }
- .tab-close-btn {
- width: 28px !important;
- height: 28px !important;
- font-size: 14px !important;
- }
- .tab-menu-btn {
- width: 28px !important;
- height: 28px !important;
- font-size: 14px !important;
- }
-
- .mobile-menu {
- display: block !important;
- }
-
- /* Stack editor and preview vertically */
- .content-container {
- flex-direction: column;
- }
-
- .editor-pane,
- .preview-pane {
- flex: none;
- height: 50%;
- border-right: none;
- }
-
- .editor-pane {
- border-bottom: 1px solid var(--border-color);
- }
-
- /* Hide drag-resize divider (touch devices don't use it) */
- .resize-divider {
- display: none;
- }
-
- /* Single-pane view modes: occupy full height */
- .content-container.view-editor-only .editor-pane,
- .content-container.view-preview-only .preview-pane {
- height: 100%;
- }
-
- .content-container.view-split .editor-pane,
- .content-container.view-split .preview-pane {
- height: 50%;
- }
-}
-
-.github-link {
- color: var(--text-color);
- text-decoration: none;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: transform 0.2s ease, color 0.2s ease;
- margin-right: 2rem;
-}
-
-.github-link:hover {
- color: var(--accent-color);
- transform: scale(1.1);
-}
-
-.github-link i {
- font-size: 1.25rem;
-}
-
-/* ========================================
- HEADER LAYOUT
- ======================================== */
-.header-container {
- position: relative;
- min-height: 30px;
-}
-
-.app-header h1 {
- font-size: 1.05rem;
- line-height: 1.1;
-}
-
-.header-left {
- flex: 1 0 auto;
- justify-content: flex-start;
- white-space: nowrap;
-}
-
-.header-right {
- flex: 1 0 auto;
- justify-content: flex-end;
- white-space: nowrap;
-}
-
-/* Pane View States */
-.content-container.view-editor-only .preview-pane {
- display: none;
-}
-
-.content-container.view-editor-only .editor-pane {
- flex: 1;
- border-right: none;
-}
-
-.content-container.view-preview-only .editor-pane {
- display: none;
-}
-
-.content-container.view-preview-only .preview-pane {
- flex: 1;
-}
-
-.content-container.view-split .editor-pane,
-.content-container.view-split .preview-pane {
- flex: 1;
-}
-
-/* Compact desktop (< 1280px): compact toolbar */
-@media (max-width: 1280px) {
- /* Compact toolbar at medium widths */
- .toolbar {
- gap: 4px;
- }
-}
-
-
-
-/* ========================================
- RESIZE DIVIDER - Story 1.3
- ======================================== */
-
-.resize-divider {
- width: 8px;
- background-color: transparent;
- cursor: col-resize;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- position: relative;
- z-index: 10;
- transition: background-color 0.2s ease;
-}
-
-.resize-divider:hover {
- background-color: var(--button-hover);
-}
-
-.resize-divider.dragging {
- background-color: var(--accent-color);
-}
-
-.resize-divider-handle {
- width: 2px;
- height: 40px;
- background-color: var(--border-color);
- border-radius: 2px;
- transition: background-color 0.2s ease, width 0.2s ease;
-}
-
-.resize-divider:hover .resize-divider-handle,
-.resize-divider.dragging .resize-divider-handle {
- background-color: var(--accent-color);
- width: 3px;
-}
-
-/* Hide divider in single-pane modes */
-.content-container.view-editor-only .resize-divider,
-.content-container.view-preview-only .resize-divider {
- display: none;
-}
-
-
-
-/* Prevent text selection during drag */
-.resizing {
- user-select: none;
- cursor: col-resize !important;
-}
-
-.resizing * {
- cursor: col-resize !important;
-}
-
-.resizing #markdown-preview,
-.resizing #markdown-editor,
-.resizing .line-numbers {
- pointer-events: none !important;
-}
-
-/* ========================================
- MOBILE VIEW MODE CONTROLS - Story 1.4
- ======================================== */
-
-.mobile-view-mode-group {
- display: flex;
- gap: 0;
- border-bottom: 1px solid var(--border-color);
- padding-bottom: 0.75rem;
-}
-
-.mobile-view-mode-btn {
- flex: 1;
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- color: var(--text-color);
- padding: 8px 12px;
- font-size: 14px;
- cursor: pointer;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 4px;
- transition: all 0.2s ease;
-}
-
-.mobile-view-mode-btn:first-child {
- border-radius: 6px 0 0 6px;
-}
-
-.mobile-view-mode-btn:last-child {
- border-radius: 0 6px 6px 0;
-}
-
-.mobile-view-mode-btn:not(:last-child) {
- border-right: none;
-}
-
-.mobile-view-mode-btn:hover,
-.mobile-view-mode-btn:active {
- background-color: var(--button-hover);
-}
-
-.mobile-view-mode-btn.active {
- background-color: var(--button-bg);
- border-color: var(--accent-color);
- color: var(--accent-color);
- border-width: 2px;
- padding: 7px 11px;
-}
-
-.mobile-view-mode-btn.active:not(:last-child) {
- border-right: 2px solid var(--accent-color);
-}
-
-.mobile-view-mode-btn i {
- font-size: 18px;
-}
-
-.mobile-view-mode-btn span {
- font-size: 12px;
-}
-
-/* ========================================
- RESPONSIVE VIEW MODE FIXES - Story 1.5
- ======================================== */
-
-
-
-/* ========================================
- PDF EXPORT TABLE FIX - Rowspan/Colspan
- ======================================== */
-
-/* Fix for html2canvas not properly rendering rowspan/colspan cells.
- Apply backgrounds to cells instead of rows to prevent row backgrounds
- from painting over rowspan cells during canvas capture. */
-.pdf-export table tr {
- background-color: transparent !important;
-}
-
-.pdf-export table th,
-.pdf-export table td {
- background-color: var(--table-bg, #ffffff);
- position: relative;
-}
-
-.pdf-export table tr:nth-child(2n) th,
-.pdf-export table tr:nth-child(2n) td {
- background-color: var(--bg-color, #f6f8fa);
-}
-
-/* Ensure rowspan cells render correctly */
-.pdf-export table th[rowspan],
-.pdf-export table td[rowspan] {
- vertical-align: middle;
- background-color: var(--table-bg, #ffffff) !important;
-}
-
-/* Ensure colspan cells render correctly */
-.pdf-export table th[colspan],
-.pdf-export table td[colspan] {
- text-align: center;
-}
-
-/* Dark mode PDF export table fix */
-[data-theme="dark"] .pdf-export table th,
-[data-theme="dark"] .pdf-export table td {
- background-color: var(--table-bg, #161b22);
-}
-
-[data-theme="dark"] .pdf-export table tr:nth-child(2n) th,
-[data-theme="dark"] .pdf-export table tr:nth-child(2n) td {
- background-color: #1c2128;
-}
-
-[data-theme="dark"] .pdf-export table th[rowspan],
-[data-theme="dark"] .pdf-export table td[rowspan] {
- background-color: var(--table-bg, #161b22) !important;
-}
-
-/* ========================================
- MERMAID DIAGRAM TOOLBAR
- ======================================== */
-
-.mermaid-container {
- position: relative;
-}
-
-.mermaid-toolbar {
- position: absolute;
- top: 8px;
- right: 8px;
- display: flex;
- gap: 4px;
- opacity: 0;
- transition: opacity 0.2s ease;
- z-index: 10;
-}
-
-.mermaid-container:hover .mermaid-toolbar {
- opacity: 1;
-}
-
-.mermaid-toolbar-btn {
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- color: var(--text-color);
- border-radius: 4px;
- padding: 4px 7px;
- font-size: 13px;
- cursor: pointer;
- display: flex;
- align-items: center;
- gap: 3px;
- transition: background-color 0.2s ease, color 0.2s ease;
- white-space: nowrap;
-}
-
-.mermaid-toolbar-btn:hover {
- background-color: var(--button-hover);
- color: var(--accent-color);
-}
-
-.mermaid-toolbar-btn:active {
- background-color: var(--button-active);
-}
-
-.mermaid-toolbar-btn i {
- font-size: 14px;
-}
-
-/* ========================================
- MERMAID ZOOM MODAL
- ======================================== */
-
-#mermaid-zoom-modal {
- display: none;
- position: fixed;
- inset: 0;
- z-index: 2000;
- background-color: rgba(0, 0, 0, 0.75);
- align-items: center;
- justify-content: center;
-}
-
-#mermaid-zoom-modal.active {
- display: flex;
-}
-
-.mermaid-modal-content {
- background-color: var(--bg-color);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- padding: 16px;
- width: 85vw;
- height: 85vh;
- max-width: 85vw;
- max-height: 85vh;
- display: flex;
- flex-direction: column;
- gap: 12px;
-}
-
-@media (max-width: 576px) {
- .mermaid-modal-content {
- width: 95vw;
- height: 90vh;
- max-width: 95vw;
- max-height: 90vh;
- padding: 10px;
- }
-}
-
-.mermaid-modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.mermaid-modal-header span {
- font-weight: 600;
- font-size: 15px;
- color: var(--text-color);
-}
-
-.mermaid-modal-close {
- background: none;
- border: none;
- color: var(--text-color);
- font-size: 1.2rem;
- cursor: pointer;
- padding: 2px 6px;
- border-radius: 4px;
- display: flex;
- align-items: center;
- transition: background-color 0.2s ease;
-}
-
-.mermaid-modal-close:hover {
- background-color: var(--button-hover);
-}
-
-.mermaid-modal-diagram {
- overflow: auto;
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 200px;
- cursor: grab;
-}
-
-.mermaid-modal-diagram.dragging {
- cursor: grabbing;
-}
-
-.mermaid-modal-diagram svg {
- transform-origin: center;
- transition: transform 0.1s ease;
- max-width: none;
-}
-
-.mermaid-modal-controls {
- display: flex;
- justify-content: center;
- gap: 8px;
- flex-wrap: wrap;
-}
-
-.mermaid-modal-controls .mermaid-toolbar-btn {
- opacity: 1;
-}
-
-/* ========================================
- DOCUMENT TABS & SESSION MANAGEMENT
- ======================================== */
-
-.tab-bar {
- display: flex;
- align-items: center;
- background-color: var(--header-bg);
- border-bottom: 1px solid var(--border-color);
- height: 32px;
- overflow: visible; /* ā was: overflow: hidden */
- flex-shrink: 0;
- padding: 0 4px;
- gap: 0;
- user-select: none;
- position: relative;
- z-index: 10;
-}
-
-.tab-list {
- display: flex;
- align-items: flex-end;
- overflow-x: auto;
- overflow-y: visible; /* ā was: overflow-y: hidden */
- flex: 1;
- height: 100%;
- scrollbar-width: none;
- -ms-overflow-style: none;
-}
-
-.tab-list::-webkit-scrollbar {
- display: none;
-}
-
-.tab-item {
- display: flex;
- align-items: center;
- gap: 6px;
- height: 32px;
- padding: 0 8px 0 10px;
- min-width: 100px;
- max-width: 180px;
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- border-bottom: 1px solid transparent;
- border-radius: 6px 6px 0 0;
- cursor: pointer;
- font-size: 13px;
- color: var(--text-color);
- white-space: nowrap;
- /* overflow: hidden; <-- REMOVE THIS */
- position: relative;
- transition: background-color 0.15s ease, color 0.15s ease;
- flex-shrink: 0;
- margin-right: 2px;
- opacity: 0.7;
-}
-
-.tab-item:hover {
- background-color: var(--button-hover);
- opacity: 0.9;
-}
-
-.tab-item.active {
- background-color: var(--bg-color);
- border-color: var(--border-color);
- color: var(--accent-color);
- border-bottom: 1px solid var(--bg-color);
- opacity: 1;
- z-index: 2;
-}
-
-.tab-item.unsaved::after {
- content: '';
- display: inline-block;
- width: 6px;
- height: 6px;
- background-color: var(--accent-color);
- border-radius: 50%;
- flex-shrink: 0;
- margin-left: 2px;
-}
-
-.tab-title {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- flex: 1;
- min-width: 0;
-}
-
-.tab-close-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 16px;
- height: 16px;
- border-radius: 3px;
- background: none;
- border: none;
- color: var(--text-color);
- cursor: pointer;
- padding: 0;
- font-size: 11px;
- opacity: 0;
- flex-shrink: 0;
- transition: background-color 0.15s ease, opacity 0.15s ease;
-}
-
-.tab-item:hover .tab-close-btn,
-.tab-item.active .tab-close-btn {
- opacity: 0.6;
-}
-
-.tab-close-btn:hover {
- background-color: var(--button-active);
- opacity: 1 !important;
- color: var(--color-danger-fg, #d73a49);
-}
-
-.tab-new-btn {
- display: flex;
- align-items: center;
- gap: 4px;
- height: 24px;
- padding: 0 8px;
- border-radius: 5px;
- background: none;
- border: 1px solid var(--border-color);
- color: var(--text-color);
- cursor: pointer;
- font-size: 12px;
- flex-shrink: 0;
- margin-left: 6px;
- align-self: center;
- transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
-}
-
-.tab-new-btn:hover {
- background-color: rgba(46, 160, 67, 0.1);
- border-color: var(--accent-color, #2ea043);
- color: var(--accent-color, #2ea043);
-}
-
-.tab-new-btn:active {
- background-color: rgba(46, 160, 67, 0.2);
-}
-
-/* Drag-and-drop visual feedback */
-.tab-item.dragging {
- opacity: 0.4;
-}
-
-.tab-item.drag-over {
- border-left: 2px solid var(--accent-color);
-}
-
-/* Tab enter animation */
-@keyframes tabSlideIn {
- from { opacity: 0; transform: translateY(4px); }
- to { opacity: 0.7; transform: translateY(0); }
-}
-
-.tab-item {
- animation: tabSlideIn 0.12s ease forwards;
-}
-
-.tab-item.active {
- animation: none;
-}
-
-/* Hide tab bar on very small screens ā single-file use */
-@media (max-width: 480px) {
- .tab-bar {
- display: none;
- }
-}
-
-/* ========================================
- TAB OVERFLOW ā Scroll Buttons & Fade Indicators
- ======================================== */
-
-.tab-scroll-btn {
- display: none;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- border-radius: 4px;
- background: none;
- border: 1px solid transparent;
- color: var(--text-color);
- cursor: pointer;
- font-size: 14px;
- flex-shrink: 0;
- padding: 0;
- transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
- z-index: 2;
- opacity: 0.6;
-}
-
-.tab-scroll-btn:hover {
- background-color: var(--button-hover);
- border-color: var(--border-color);
- opacity: 1;
-}
-
-.tab-scroll-btn:active {
- background-color: var(--button-active);
-}
-
-/* Show scroll buttons only when overflow exists */
-.tab-bar.has-overflow-left .tab-scroll-left,
-.tab-bar.has-overflow-right .tab-scroll-right {
- display: flex;
-}
-
-/* Overflow fade indicators ā subtle gradient at clipped edges */
-.tab-list::before,
-.tab-list::after {
- content: '';
- position: sticky;
- top: 0;
- bottom: 0;
- width: 0;
- flex-shrink: 0;
- pointer-events: none;
- z-index: 3;
- transition: box-shadow 0.2s ease;
-}
-
-.tab-list::before {
- left: 0;
-}
-
-.tab-list::after {
- right: 0;
-}
-
-.tab-bar.has-overflow-left .tab-list::before {
- box-shadow: 8px 0 12px -4px rgba(0, 0, 0, 0.12);
-}
-
-.tab-bar.has-overflow-right .tab-list::after {
- box-shadow: -8px 0 12px -4px rgba(0, 0, 0, 0.12);
-}
-
-[data-theme="dark"] .tab-bar.has-overflow-left .tab-list::before {
- box-shadow: 8px 0 12px -4px rgba(0, 0, 0, 0.35);
-}
-
-[data-theme="dark"] .tab-bar.has-overflow-right .tab-list::after {
- box-shadow: -8px 0 12px -4px rgba(0, 0, 0, 0.35);
-}
-
-/* ========================================
- THREE-DOT TAB MENU
- ======================================== */
-
-.tab-menu-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 22px;
- height: 22px;
- border-radius: 3px;
- background: none;
- border: none;
- color: var(--text-color);
- cursor: pointer;
- padding: 0;
- font-size: 14px;
- font-weight: bold;
- letter-spacing: 1px;
- opacity: 0.65;
- flex-shrink: 0;
- transition: background-color 0.15s ease, opacity 0.15s ease;
- position: relative;
-}
-
-/* Touch Hitbox Expansion for Tab Menu Button */
-.tab-menu-btn::before {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 48px;
- height: 48px;
-}
-
-/* Touch-optimized styling for coarser pointers (e.g., smartphones & tablets) */
-@media (pointer: coarse) {
- .tab-bar {
- height: 40px !important;
- }
- .tab-item {
- height: 40px !important;
- font-size: 14px !important;
- padding: 0 10px 0 12px !important;
- gap: 8px !important;
- }
- .tab-new-btn,
- .tab-reset-btn {
- height: 32px !important;
- font-size: 14px !important;
- padding: 0 12px !important;
- }
- .tab-scroll-btn {
- width: 32px !important;
- height: 32px !important;
- font-size: 18px !important;
- }
- .tab-menu-btn {
- width: 30px !important;
- height: 30px !important;
- font-size: 18px !important;
- }
- .tab-close-btn {
- width: 20px !important;
- height: 20px !important;
- font-size: 13px !important;
- opacity: 0.8 !important;
- }
-}
-
-.tab-item:hover .tab-menu-btn,
-.tab-item.active .tab-menu-btn {
- opacity: 0.65;
-}
-
-.tab-menu-btn:hover {
- background-color: var(--button-active);
- opacity: 1 !important;
-}
-
-.tab-menu-dropdown {
- display: none;
- position: fixed;
- min-width: 130px;
- background-color: var(--header-bg);
- border: 1px solid var(--border-color);
- border-radius: 6px;
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
- z-index: 99999;
- overflow: hidden;
- flex-direction: column;
-}
-
-.tab-menu-dropdown.open {
- display: flex;
-}
-
-.tab-menu-item {
- display: flex;
- align-items: center;
- gap: 7px;
- padding: 7px 12px;
- background: none;
- border: none;
- color: var(--text-color);
- font-size: 12px;
- cursor: pointer;
- text-align: left;
- transition: background-color 0.12s ease;
- white-space: nowrap;
-}
-
-.tab-menu-item:hover {
- background-color: var(--button-hover);
-}
-
-.tab-menu-item-danger {
- color: var(--color-danger-fg, #d73a49);
-}
-
-.tab-menu-item-danger:hover {
- background-color: rgba(215, 58, 73, 0.1);
-}
-
-/* ========================================
- RESET BUTTON
- ======================================== */
-
-.tab-reset-btn {
- display: flex;
- align-items: center;
- gap: 4px;
- height: 24px;
- padding: 0 8px;
- border-radius: 5px;
- background: none;
- border: 1px solid var(--border-color);
- color: var(--text-color);
- cursor: pointer;
- font-size: 12px;
- flex-shrink: 0;
- margin-left: 6px;
- transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
-}
-
-.tab-reset-btn:hover {
- background-color: rgba(215, 58, 73, 0.1);
- border-color: var(--color-danger-fg, #d73a49);
- color: var(--color-danger-fg, #d73a49);
-}
-
-/* ========================================
- RESET & RENAME CONFIRMATION MODALS
- ======================================== */
-
-.reset-modal-overlay {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.45);
- z-index: 2000;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.reset-modal-overlay.modal-overlay {
- opacity: 0;
- visibility: hidden;
- transition: opacity 0.2s ease, visibility 0.2s ease;
-}
-
-.reset-modal-overlay.modal-overlay.is-visible {
- opacity: 1;
- visibility: visible;
-}
-
-.reset-modal-box {
- background: var(--header-bg);
- border: 1px solid var(--border-color);
- border-radius: 10px;
- padding: 24px 28px;
- min-width: 280px;
- max-width: 360px;
- box-shadow: 0 8px 32px rgba(0,0,0,0.25);
- display: flex;
- flex-direction: column;
- gap: 16px;
-}
-
-.modal-box {
- max-height: min(85vh, 760px);
- opacity: 0;
- transform: translateY(8px);
- transition: transform 0.2s ease, opacity 0.2s ease;
-}
-
-.reset-modal-overlay.modal-overlay.is-visible .modal-box {
- opacity: 1;
- transform: translateY(0);
-}
-
-.modal-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
-}
-
-.modal-header .reset-modal-message {
- text-align: left;
- flex: 1;
-}
-
-.modal-close-btn {
- border: 1px solid var(--border-color);
- background: var(--button-bg);
- color: var(--text-color);
- border-radius: 6px;
- width: 28px;
- height: 28px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- transition: background-color 0.15s ease;
-}
-
-.modal-close-btn:hover {
- background-color: var(--button-hover);
-}
-
-.modal-body {
- display: flex;
- flex-direction: column;
- gap: 16px;
- max-height: min(60vh, 520px);
- overflow: auto;
- padding-right: 4px;
-}
-
-.modal-section {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.modal-section-title {
- margin: 0;
- font-size: 0.95rem;
- font-weight: 600;
-}
-
-.modal-list {
- margin: 0;
- padding-left: 1.1rem;
- display: flex;
- flex-direction: column;
- gap: 6px;
- font-size: 0.85rem;
-}
-
-.modal-list a {
- color: var(--accent-color);
- text-decoration: none;
-}
-
-.modal-list a:hover {
- text-decoration: underline;
-}
-
-.modal-subtext {
- margin: 0;
- font-size: 12px;
- color: var(--text-secondary, #57606a);
- line-height: 1.4;
-}
-
-.find-replace-meta {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
-}
-
-.find-match-count {
- font-size: 12px;
- color: var(--text-secondary, #57606a);
-}
-
-.find-replace-nav {
- display: inline-flex;
- gap: 6px;
-}
-
-.find-nav-btn {
- width: 28px;
- height: 28px;
- padding: 0;
-}
-
-.about-header {
- display: flex;
- align-items: center;
- gap: 16px;
- flex-wrap: wrap;
-}
-
-.about-logo {
- width: 64px;
- height: 64px;
- border-radius: 12px;
- border: 1px solid var(--border-color);
- object-fit: cover;
-}
-
-.about-details {
- display: flex;
- flex-direction: column;
- gap: 6px;
-}
-
-.about-title {
- margin: 0;
- font-size: 1.05rem;
- font-weight: 600;
-}
-
-.about-description {
- margin: 0;
- font-size: 0.85rem;
- color: var(--text-secondary, #57606a);
-}
-
-.about-meta {
- margin: 0;
- font-size: 0.78rem;
- color: var(--text-secondary, #57606a);
-}
-
-.modal-body kbd {
- padding: 2px 6px;
- border-radius: 4px;
- background-color: var(--button-bg);
- border: 1px solid var(--border-color);
- font-size: 0.75rem;
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
-}
-
-.reset-modal-box--wide {
- width: min(92vw, 640px);
- max-width: 640px;
-}
-
-.reset-modal-message {
- margin: 0;
- font-size: 14px;
- color: var(--text-color);
- font-weight: 500;
- text-align: center;
-}
-
-.reset-modal-actions {
- display: flex;
- gap: 10px;
- justify-content: flex-end;
-}
-
-.reset-modal-btn {
- padding: 6px 16px;
- border-radius: 6px;
- border: 1px solid var(--border-color);
- background: var(--button-bg);
- color: var(--text-color);
- font-size: 13px;
- cursor: pointer;
- transition: background-color 0.15s ease;
-}
-
-.reset-modal-btn:hover {
- background-color: var(--button-hover);
-}
-
-.reset-modal-confirm {
- background-color: var(--color-danger-fg, #d73a49);
- border-color: var(--color-danger-fg, #d73a49);
- color: #fff;
-}
-
-.reset-modal-confirm:hover {
- background-color: #b02a37;
- border-color: #b02a37;
-}
-
+:root {
+ --bg-color: #ffffff;
+ --editor-bg: #f6f8fa;
+ --preview-bg: #ffffff; /* Preview background for light mode */
+ --text-color: #24292e;
+ --text-secondary: #57606a;
+ --font-mono: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
+ --color-danger-fg: #d73a49;
+ --preview-text-color: #24292e; /* Text color for preview in light mode */
+ --border-color: #e1e4e8;
+ --header-bg: #f6f8fa;
+ --button-bg: #f6f8fa;
+ --button-hover: #e1e4e8;
+ --button-active: #d1d5da;
+ --scrollbar-thumb: #c1c1c1;
+ --scrollbar-track: #f1f1f1;
+ --accent-color: #0366d6;
+ --table-bg: #ffffff; /* Table background for light mode */
+ --code-bg: #f6f8fa; /* Code block background for light mode */
+ --skeleton-bg: #e2e8f0;
+ --skeleton-glow: rgba(255, 255, 255, 0.65);
+
+ /* Find & Replace Panel custom properties (PERF-010 consolidated) */
+ --fr-bg: rgba(255, 255, 255, 0.95);
+ --fr-border: #d0d7de;
+ --fr-shadow: 0 8px 24px rgba(140, 149, 159, 0.2);
+ --fr-btn-active: #0969da;
+ --fr-btn-active-bg: #ddf4ff;
+ --fr-match-highlight: #ffdf5d;
+ --fr-match-active: #ff9b30;
+ --fr-error-bg: #ffebe9;
+ --fr-error-border: #ff8577;
+ --fr-text-danger: #cf222e;
+}
+
+[data-theme="dark"] {
+ --bg-color: #0d1117;
+ --editor-bg: #161b22;
+ --preview-bg: #0d1117; /* Preview background for dark mode */
+ --text-color: #c9d1d9;
+ --text-secondary: #8b949e;
+ --color-danger-fg: #f85149;
+ --preview-text-color: #c9d1d9; /* Text color for preview in dark mode */
+ --border-color: #30363d;
+ --header-bg: #161b22;
+ --button-bg: #21262d;
+ --button-hover: #30363d;
+ --button-active: #3b434b;
+ --scrollbar-thumb: #484f58;
+ --scrollbar-track: #21262d;
+ --accent-color: #58a6ff;
+ --table-bg: #161b22; /* Table background for dark mode */
+ --code-bg: #161b22; /* Code block background for dark mode */
+ --skeleton-bg: #2d3139;
+ --skeleton-glow: rgba(255, 255, 255, 0.08);
+
+ /* Find & Replace Panel custom properties for dark mode */
+ --fr-bg: rgba(28, 33, 40, 0.98);
+ --fr-border: #444c56;
+ --fr-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
+ --fr-btn-active: #2f81f7;
+ --fr-btn-active-bg: rgba(56, 139, 253, 0.15);
+ --fr-match-highlight: rgba(187, 128, 9, 0.4);
+ --fr-match-active: #f1e05a;
+ --fr-error-bg: rgba(248, 81, 73, 0.1);
+ --fr-error-border: rgba(248, 81, 73, 0.4);
+ --fr-text-danger: #ff7b72;
+}
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+@media (min-width: 768px) {
+ html,
+ body {
+ height: 100%;
+ overflow: hidden;
+ }
+}
+
+body {
+ background-color: var(--bg-color);
+ color: var(--text-color);
+ /* PERF-021: Removed background-color transition to avoid full-viewport repaint on theme toggle */
+ transition: color 0.15s ease;
+ min-height: 100vh;
+ font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Hiragino Kaku Gothic ProN", Meiryo, "Malgun Gothic", "Apple SD Gothic Neo", "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
+}
+
+.app-header {
+ background-color: var(--header-bg);
+ border-bottom: 1px solid var(--border-color);
+ padding: 0.35rem 0.75rem;
+ transition: background-color 0.3s ease;
+ position: relative;
+ z-index: 100;
+ flex-shrink: 0;
+}
+
+.app-container {
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.content-container {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+}
+
+.editor-pane, .preview-pane {
+ flex: 1;
+ padding: 20px;
+ overflow-y: auto;
+ position: relative;
+ /* PERF-025: Shortened transition and scoped to background-color only */
+ transition: background-color 0.15s ease;
+}
+
+.editor-pane {
+ background-color: var(--editor-bg);
+ border-right: 1px solid var(--border-color);
+ padding-right: 0px;
+ --line-number-gutter: 0px;
+}
+
+.preview-pane {
+ background-color: var(--preview-bg); /* Using the new variable for preview background */
+}
+
+/* Custom scrollbar */
+.editor-pane::-webkit-scrollbar,
+.preview-pane::-webkit-scrollbar,
+#markdown-editor::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+.editor-pane::-webkit-scrollbar-track,
+.preview-pane::-webkit-scrollbar-track,
+#markdown-editor::-webkit-scrollbar-track {
+ background: var(--scrollbar-track);
+}
+
+.editor-pane::-webkit-scrollbar-thumb,
+.preview-pane::-webkit-scrollbar-thumb,
+#markdown-editor::-webkit-scrollbar-thumb {
+ background: var(--scrollbar-thumb);
+ border-radius: 4px;
+}
+
+.editor-pane::-webkit-scrollbar-thumb:hover,
+.preview-pane::-webkit-scrollbar-thumb:hover,
+#markdown-editor::-webkit-scrollbar-thumb:hover {
+ background: var(--button-active);
+}
+
+#markdown-editor {
+ width: 100%;
+ height: 100%;
+ border: none;
+ background-color: transparent;
+ color: var(--text-color);
+ resize: none;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+ font-size: 14px;
+ line-height: 1.5;
+ padding: 10px;
+ padding-left: calc(10px + var(--line-number-gutter));
+ transition: background-color 0.3s ease, color 0.3s ease;
+ overflow-y: auto;
+ position: relative;
+ z-index: 3;
+}
+
+#markdown-editor:focus {
+ outline: none;
+}
+
+.preview-pane {
+ padding: 20px;
+}
+
+.markdown-body {
+ padding: 20px;
+ width: 100%;
+ background-color: var(--preview-bg); /* Ensuring the markdown content matches preview background */
+ color: var(--preview-text-color); /* Using specific text color for preview content */
+}
+
+.markdown-body a.reference-link {
+ font-size: 0.75em;
+ letter-spacing: -0.02em;
+ line-height: 1;
+ vertical-align: super;
+ position: relative;
+ top: 0.08em;
+}
+
+/* Style tables in light mode */
+.markdown-body table {
+ background-color: var(--table-bg);
+ border-color: var(--border-color);
+}
+
+.markdown-body table tr {
+ background-color: var(--table-bg);
+ border-top: 1px solid var(--border-color);
+}
+
+.markdown-body table tr:nth-child(2n) {
+ background-color: var(--bg-color);
+}
+
+/* Style code blocks in light mode */
+.markdown-body pre {
+ background-color: var(--code-bg);
+ border-radius: 6px;
+}
+
+.markdown-body code {
+ background-color: var(--code-bg);
+ border-radius: 3px;
+ padding: 0.2em 0.4em;
+}
+
+.markdown-body img.emoji-inline {
+ width: 1em;
+ height: 1em;
+ vertical-align: -0.1em;
+}
+
+.markdown-body ul,
+.markdown-body ol {
+ padding-left: 2em;
+ margin: 0.4em 0;
+}
+
+.markdown-body ul ul,
+.markdown-body ul ol,
+.markdown-body ol ul,
+.markdown-body ol ol {
+ margin-top: 0.2em;
+ margin-bottom: 0.2em;
+}
+
+.markdown-body ul.contains-task-list,
+.markdown-body li.task-list-item {
+ list-style: none;
+}
+
+.markdown-body ul.contains-task-list {
+ padding-left: 2em;
+}
+
+.markdown-body li.task-list-item input[type="checkbox"] {
+ margin: 0 0.5em 0.2em 0;
+ vertical-align: middle;
+ pointer-events: none;
+}
+
+.markdown-body li.task-list-item::marker {
+ content: "";
+}
+
+.markdown-body li:has(> input[type="checkbox"]) {
+ list-style: none;
+}
+
+.markdown-body li:has(> input[type="checkbox"])::marker {
+ content: "";
+}
+
+.markdown-body ul:has(> li > input[type="checkbox"]) {
+ list-style: none;
+ padding-left: 2em;
+}
+
+.markdown-body .footnotes {
+ margin-top: 1.5rem;
+ font-size: 0.9em;
+}
+
+.markdown-body .footnotes ol {
+ padding-left: 1.5em;
+}
+
+.markdown-body .footnotes ol > li::marker {
+ content: "[" counter(list-item) "] ";
+ font-weight: 600;
+}
+
+.markdown-body .footnotes li > p {
+ margin: 0.2em 0;
+}
+
+.markdown-body .footnote-ref a,
+.markdown-body .footnote-backref {
+ text-decoration: none;
+}
+
+.markdown-body .footnote-backref {
+ margin-left: 0.4em;
+}
+
+.markdown-body .markdown-alert {
+ padding: 0.5rem 1rem;
+ margin-bottom: 16px;
+ border-left: 0.25em solid;
+ border-radius: 0.375rem;
+}
+
+.markdown-body .markdown-alert > :last-child {
+ margin-bottom: 0;
+}
+
+.markdown-body .markdown-alert-title {
+ margin: 0 0 8px;
+ font-weight: 600;
+ line-height: 1.25;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.markdown-body .markdown-alert-icon {
+ display: inline-flex;
+ width: 16px;
+ height: 16px;
+}
+
+.markdown-body .markdown-alert-icon svg {
+ width: 16px;
+ height: 16px;
+ fill: currentColor;
+}
+
+.markdown-body .markdown-alert-note {
+ color: #0969da;
+ border-left-color: #0969da;
+ background-color: #ddf4ff;
+}
+
+.markdown-body .markdown-alert-tip {
+ color: #1a7f37;
+ border-left-color: #1a7f37;
+ background-color: #dafbe1;
+}
+
+.markdown-body .markdown-alert-important {
+ color: #8250df;
+ border-left-color: #8250df;
+ background-color: #fbefff;
+}
+
+.markdown-body .markdown-alert-warning {
+ color: #9a6700;
+ border-left-color: #9a6700;
+ background-color: #fff8c5;
+}
+
+.markdown-body .markdown-alert-caution {
+ color: #cf222e;
+ border-left-color: #cf222e;
+ background-color: #ffebe9;
+}
+
+.markdown-body .markdown-alert > *:not(.markdown-alert-title) {
+ color: var(--preview-text-color);
+}
+
+[data-theme="dark"] .markdown-body .markdown-alert-note {
+ color: #4493f8;
+ background-color: rgba(31, 111, 235, 0.15);
+ border-left-color: #4493f8;
+}
+
+[data-theme="dark"] .markdown-body .markdown-alert-tip {
+ color: #3fb950;
+ background-color: rgba(35, 134, 54, 0.15);
+ border-left-color: #3fb950;
+}
+
+[data-theme="dark"] .markdown-body .markdown-alert-important {
+ color: #ab7df8;
+ background-color: rgba(137, 87, 229, 0.15);
+ border-left-color: #ab7df8;
+}
+
+[data-theme="dark"] .markdown-body .markdown-alert-warning {
+ color: #d29922;
+ background-color: rgba(210, 153, 34, 0.18);
+ border-left-color: #d29922;
+}
+
+[data-theme="dark"] .markdown-body .markdown-alert-caution {
+ color: #f85149;
+ background-color: rgba(248, 81, 73, 0.18);
+ border-left-color: #f85149;
+}
+
+.toolbar {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.toolbar-group {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.toolbar-divider {
+ width: 1px;
+ height: 20px;
+ background-color: var(--border-color);
+ opacity: 0.7;
+}
+
+.tool-button {
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ color: var(--text-color);
+ border-radius: 5px;
+ padding: 4px 8px;
+ font-size: 13px;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ /* PERF-016: Specific transition properties instead of 'all' to avoid animating layout-triggering properties */
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
+}
+
+.tool-button:hover {
+ background-color: var(--button-hover);
+}
+
+.tool-button:active {
+ background-color: var(--button-active);
+}
+
+.tool-button:disabled,
+.tool-button[aria-disabled="true"] {
+ cursor: not-allowed;
+ opacity: 0.5;
+}
+
+.tool-button i {
+ font-size: 15px;
+}
+
+.tool-button.is-active,
+.tool-button.is-active:hover {
+ border-color: var(--accent-color);
+ color: var(--accent-color);
+ background-color: rgba(3, 102, 214, 0.08);
+}
+
+.btn-text {
+ display: none;
+}
+
+.toolbar .tool-button {
+ height: 28px;
+ min-width: 28px;
+}
+
+.toolbar .tool-button.sync-active {
+ border-color: var(--accent-color);
+ color: var(--accent-color);
+}
+
+.file-input {
+ display: none;
+}
+
+/* Drag overlay: full-screen drop target shown when user drags a file over the window */
+.drag-overlay {
+ display: none;
+ position: fixed;
+ inset: 0;
+ z-index: 9999;
+ background-color: rgba(0, 0, 0, 0.45);
+ pointer-events: none;
+ align-items: center;
+ justify-content: center;
+}
+
+.drag-overlay.active {
+ display: flex;
+ pointer-events: auto;
+}
+
+.drag-overlay-inner {
+ border: 3px dashed var(--accent-color);
+ border-radius: 12px;
+ padding: 48px 64px;
+ text-align: center;
+ color: #ffffff;
+ background-color: rgba(3, 102, 214, 0.15);
+ animation: overlayPulse 1.4s ease-in-out infinite;
+}
+
+.drag-overlay-icon {
+ display: block;
+ font-size: 3rem;
+ margin-bottom: 12px;
+ color: var(--accent-color);
+}
+
+.drag-overlay-text {
+ font-size: 1.4rem;
+ font-weight: 600;
+ margin-bottom: 4px;
+}
+
+.drag-overlay-sub {
+ font-size: 0.85rem;
+ opacity: 0.75;
+ margin-bottom: 0;
+}
+
+@keyframes overlayPulse {
+ 0%, 100% { transform: scale(1); }
+ 50% { transform: scale(1.015); }
+}
+
+/* Editor drop hint: subtle text at bottom of editor pane, shown only when empty */
+.drop-hint {
+ position: absolute;
+ bottom: 14px;
+ left: 0;
+ right: 0;
+ text-align: center;
+ font-size: 0.75rem;
+ color: var(--text-color);
+ opacity: 0.35;
+ pointer-events: none;
+ user-select: none;
+ z-index: 3;
+}
+
+.editor-pane:has(#markdown-editor:not(:placeholder-shown)) .drop-hint {
+ display: none;
+}
+
+.line-numbers {
+ position: absolute;
+ top: 20px;
+ bottom: 20px;
+ left: 20px;
+ width: var(--line-number-gutter);
+ padding: 10px 8px 10px 0;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+ font-size: 14px;
+ line-height: 1.5;
+ text-align: right;
+ color: var(--text-secondary);
+ background-color: var(--editor-bg);
+ border-right: 1px solid var(--border-color);
+ box-sizing: border-box;
+ overflow: hidden;
+ pointer-events: none;
+ user-select: none;
+ z-index: 2;
+ font-variant-numeric: tabular-nums;
+}
+
+.line-numbers .line-number {
+ display: block;
+ height: auto;
+}
+
+.editor-highlight-layer {
+ position: absolute;
+ inset: 20px 0 20px calc(20px + var(--line-number-gutter));
+ padding: 10px;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+ font-size: 14px;
+ line-height: 1.5;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ color: transparent;
+ pointer-events: none;
+ overflow: auto;
+ background-color: var(--editor-bg);
+ border-radius: 4px;
+ z-index: 1;
+}
+
+.editor-highlight-layer::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+.editor-highlight-layer::-webkit-scrollbar-thumb {
+ background: transparent;
+}
+
+.editor-highlight-layer::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.find-highlight {
+ background-color: var(--fr-match-highlight, rgba(255, 223, 93, 0.4)) !important;
+ border-radius: 2px;
+ color: transparent !important;
+}
+
+.find-highlight.active {
+ background-color: var(--fr-match-active, #ff9b30) !important;
+ color: transparent !important;
+}
+
+/* Dropdown improvements */
+.dropdown-menu {
+ background-color: var(--bg-color);
+ border-color: var(--border-color);
+}
+
+.dropdown-item {
+ color: var(--text-color);
+}
+
+.dropdown-item:hover, .dropdown-item:focus {
+ background-color: var(--button-hover);
+ color: var(--text-color);
+}
+
+/* Markdown formatting toolbar */
+.markdown-format-toolbar {
+ display: flex;
+ align-items: center;
+ height: 34px;
+ padding: 0 6px;
+ background-color: var(--header-bg);
+ border-bottom: 1px solid var(--border-color);
+ overflow-x: auto;
+ overflow-y: hidden;
+ flex-shrink: 0;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+
+.markdown-format-toolbar::-webkit-scrollbar {
+ display: none;
+}
+
+.markdown-toolbar-group {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ height: 100%;
+ padding: 0 6px;
+ border-right: 1px solid var(--border-color);
+ flex-shrink: 0;
+}
+
+.markdown-toolbar-group:first-child {
+ padding-left: 0;
+}
+
+.markdown-toolbar-group:last-child {
+ border-right: none;
+ padding-right: 0;
+}
+
+.markdown-tool-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 26px;
+ height: 26px;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ background: transparent;
+ color: var(--text-color);
+ cursor: pointer;
+ font-size: 14px;
+ line-height: 1;
+ padding: 0;
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
+}
+
+.markdown-tool-btn:hover,
+.markdown-tool-btn:focus-visible {
+ background-color: var(--button-hover);
+ border-color: var(--border-color);
+ color: var(--accent-color);
+}
+
+.markdown-tool-btn:active {
+ background-color: var(--button-active);
+}
+
+.markdown-tool-btn:disabled,
+.markdown-tool-btn.disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+ pointer-events: none;
+}
+
+.markdown-tool-btn i {
+ font-size: 15px;
+}
+
+.markdown-tool-btn[data-md-action="reference"] i::before {
+ content: "[ ]";
+ font-style: normal;
+ font-size: 12px;
+ letter-spacing: -0.12em;
+}
+
+.markdown-tool-btn.text-tool {
+ width: auto;
+ min-width: 26px;
+ padding: 0 5px;
+ font-weight: 600;
+ font-family: Georgia, "Times New Roman", serif;
+}
+
+.heading-group .markdown-tool-btn {
+ min-width: 30px;
+}
+
+
+
+/* Loading indicators */
+.loading {
+ opacity: 0.6;
+ pointer-events: none;
+}
+
+/* Focus outline for accessibility */
+button:focus,
+a:focus {
+ outline: 2px solid var(--accent-color);
+ outline-offset: 2px;
+}
+
+/* Animation for copied message */
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+/* Tooltip styles */
+.tooltip {
+ position: absolute;
+ background: var(--button-bg);
+ border: 1px solid var(--border-color);
+ padding: 5px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ z-index: 1000;
+ animation: fadeIn 0.2s ease;
+}
+
+/* Styles for GitHub markdown preview light mode */
+.markdown-body {
+ color-scheme: light;
+ --color-prettylights-syntax-comment: #6a737d;
+ --color-prettylights-syntax-constant: #005cc5;
+ --color-prettylights-syntax-entity: #6f42c1;
+ --color-prettylights-syntax-storage-modifier-import: #24292e;
+ --color-prettylights-syntax-entity-tag: #22863a;
+ --color-prettylights-syntax-keyword: #cf222e;
+ --color-prettylights-syntax-string: #032f62;
+ --color-prettylights-syntax-variable: #e36209;
+ --color-prettylights-syntax-brackethighlighter-unmatched: #b31d28;
+ --color-prettylights-syntax-invalid-illegal-text: #fafbfc;
+ --color-prettylights-syntax-invalid-illegal-bg: #b31d28;
+ --color-prettylights-syntax-carriage-return-text: #fafbfc;
+ --color-prettylights-syntax-carriage-return-bg: #d73a49;
+ --color-prettylights-syntax-string-regexp: #22863a;
+ --color-prettylights-syntax-markup-list: #735c0f;
+ --color-prettylights-syntax-markup-heading: #005cc5;
+ --color-prettylights-syntax-markup-italic: #24292e;
+ --color-prettylights-syntax-markup-bold: #24292e;
+ --color-prettylights-syntax-markup-deleted-text: #b31d28;
+ --color-prettylights-syntax-markup-deleted-bg: #ffeef0;
+ --color-prettylights-syntax-markup-inserted-text: #22863a;
+ --color-prettylights-syntax-markup-inserted-bg: #f0fff4;
+ --color-prettylights-syntax-markup-changed-text: #e36209;
+ --color-prettylights-syntax-markup-changed-bg: #ffebda;
+ --color-prettylights-syntax-markup-ignored-text: #f6f8fa;
+ --color-prettylights-syntax-markup-ignored-bg: #005cc5;
+ --color-prettylights-syntax-meta-diff-range: #6f42c1;
+ --color-prettylights-syntax-brackethighlighter-angle: #586069;
+ --color-prettylights-syntax-sublimelinter-gutter-mark: #e1e4e8;
+ --color-prettylights-syntax-constant-other-reference-link: #032f62;
+ --color-fg-default: #24292e;
+ --color-fg-muted: #586069;
+ --color-fg-subtle: #6a737d;
+ --color-canvas-default: #ffffff;
+ --color-canvas-subtle: #f6f8fa;
+ --color-border-default: #e1e4e8;
+ --color-border-muted: #eaecef;
+ --color-neutral-muted: rgba(175,184,193,0.2);
+ --color-accent-fg: #0366d6;
+ --color-accent-emphasis: #0366d6;
+ --color-attention-subtle: #fff5b1;
+ --color-danger-fg: #d73a49;
+}
+
+/* Styles for GitHub markdown preview dark mode */
+[data-theme="dark"] .markdown-body {
+ color-scheme: dark;
+ --color-prettylights-syntax-comment: #8b949e;
+ --color-prettylights-syntax-constant: #79c0ff;
+ --color-prettylights-syntax-entity: #d2a8ff;
+ --color-prettylights-syntax-storage-modifier-import: #c9d1d9;
+ --color-prettylights-syntax-entity-tag: #7ee787;
+ --color-prettylights-syntax-keyword: #ff7b72;
+ --color-prettylights-syntax-string: #a5d6ff;
+ --color-prettylights-syntax-variable: #ffa657;
+ --color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
+ --color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
+ --color-prettylights-syntax-invalid-illegal-bg: #8e1519;
+ --color-prettylights-syntax-carriage-return-text: #f0f6fc;
+ --color-prettylights-syntax-carriage-return-bg: #b62324;
+ --color-prettylights-syntax-string-regexp: #7ee787;
+ --color-prettylights-syntax-markup-list: #f2cc60;
+ --color-prettylights-syntax-markup-heading: #1f6feb;
+ --color-prettylights-syntax-markup-italic: #c9d1d9;
+ --color-prettylights-syntax-markup-bold: #c9d1d9;
+ --color-prettylights-syntax-markup-deleted-text: #ffdcd7;
+ --color-prettylights-syntax-markup-deleted-bg: #67060c;
+ --color-prettylights-syntax-markup-inserted-text: #aff5b4;
+ --color-prettylights-syntax-markup-inserted-bg: #033a16;
+ --color-prettylights-syntax-markup-changed-text: #ffdfb6;
+ --color-prettylights-syntax-markup-changed-bg: #5a1e02;
+ --color-prettylights-syntax-markup-ignored-text: #c9d1d9;
+ --color-prettylights-syntax-markup-ignored-bg: #1158c7;
+ --color-prettylights-syntax-meta-diff-range: #d2a8ff;
+ --color-prettylights-syntax-brackethighlighter-angle: #8b949e;
+ --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
+ --color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
+ --color-fg-default: #c9d1d9;
+ --color-fg-muted: #8b949e;
+ --color-fg-subtle: #484f58;
+ --color-canvas-default: #0d1117;
+ --color-canvas-subtle: #161b22;
+ --color-border-default: #30363d;
+ --color-border-muted: #21262d;
+ --color-neutral-muted: rgba(110,118,129,0.4);
+ --color-accent-fg: #58a6ff;
+ --color-accent-emphasis: #1f6feb;
+ --color-attention-subtle: rgba(187,128,9,0.15);
+ --color-danger-fg: #f85149;
+}
+
+/* Override specific styles for dark mode tables and code */
+[data-theme="dark"] .markdown-body table tr {
+ background-color: var(--table-bg);
+}
+
+[data-theme="dark"] .markdown-body table tr:nth-child(2n) {
+ background-color: #1c2128; /* Slightly lighter than base dark background */
+}
+
+[data-theme="dark"] .markdown-body pre {
+ background-color: var(--code-bg);
+}
+
+[data-theme="dark"] .markdown-body code {
+ background-color: var(--code-bg);
+}
+
+/* Syntax Highlighting Mapping to GitHub Variables */
+.hljs {
+ color: var(--color-fg-default);
+}
+.hljs-doctag,
+.hljs-keyword,
+.hljs-meta .hljs-keyword,
+.hljs-template-tag,
+.hljs-template-variable,
+.hljs-type,
+.hljs-variable.language_ {
+ color: var(--color-prettylights-syntax-keyword);
+}
+.hljs-title,
+.hljs-title.class_,
+.hljs-title.class_.inherited__,
+.hljs-title.function_ {
+ color: var(--color-prettylights-syntax-entity);
+}
+.hljs-attr,
+.hljs-attribute,
+.hljs-literal,
+.hljs-meta,
+.hljs-number,
+.hljs-operator,
+.hljs-variable,
+.hljs-selector-attr,
+.hljs-selector-class,
+.hljs-selector-id {
+ color: var(--color-prettylights-syntax-constant);
+}
+.hljs-regexp,
+.hljs-string,
+.hljs-meta .hljs-string {
+ color: var(--color-prettylights-syntax-string);
+}
+.hljs-built_in,
+.hljs-symbol {
+ color: var(--color-prettylights-syntax-variable);
+}
+.hljs-comment,
+.hljs-code,
+.hljs-formula {
+ color: var(--color-prettylights-syntax-comment);
+}
+.hljs-name,
+.hljs-quote,
+.hljs-selector-tag,
+.hljs-selector-pseudo {
+ color: var(--color-prettylights-syntax-entity-tag);
+}
+.hljs-subst {
+ color: var(--color-fg-default);
+}
+.hljs-section {
+ color: var(--color-prettylights-syntax-markup-heading);
+ font-weight: bold;
+}
+.hljs-bullet {
+ color: var(--color-prettylights-syntax-constant);
+}
+.hljs-emphasis {
+ color: var(--color-fg-default);
+ font-style: italic;
+}
+.hljs-strong {
+ color: var(--color-fg-default);
+ font-weight: bold;
+}
+.hljs-addition {
+ color: var(--color-prettylights-syntax-markup-inserted-text);
+ background-color: var(--color-prettylights-syntax-markup-inserted-bg);
+}
+.hljs-deletion {
+ color: var(--color-prettylights-syntax-markup-deleted-text);
+ background-color: var(--color-prettylights-syntax-markup-deleted-bg);
+}
+
+.stats-container {
+ font-size: 0.8rem;
+ color: var(--text-color);
+}
+
+.stat-item {
+ align-items: center;
+}
+
+.stat-item i {
+ font-size: 0.9rem;
+ opacity: 0.8;
+}
+
+#importDropdown,
+#exportDropdown,
+#languageDropdown {
+ font-size: 0.8rem;
+}
+
+#importDropdown i,
+#exportDropdown i,
+#languageDropdown i {
+ font-size: 0.9rem;
+}
+
+/* Ensure desktop dropdown menu options match the stats-container font size */
+[aria-labelledby="importDropdown"] .dropdown-item,
+[aria-labelledby="exportDropdown"] .dropdown-item,
+[aria-labelledby="languageDropdown"] .dropdown-item {
+ font-size: 0.8rem;
+}
+
+/* Ensure mobile menu import, export, and language dropdown triggers/options match the mobile stats-container font size */
+#mobile-import-button,
+#mobile-import-github-button,
+#mobile-export-md,
+#mobile-export-html,
+#mobile-export-pdf,
+#mobileLanguageDropdown {
+ font-size: 0.9rem !important;
+}
+
+[aria-labelledby="mobileLanguageDropdown"] .dropdown-item {
+ font-size: 0.9rem;
+}
+
+.editor-pane {
+ overflow: hidden;
+}
+
+/* Mobile Menu Styles */
+.mobile-menu {
+ display: none;
+ position: relative;
+ z-index: 1001;
+}
+
+
+
+/* slideāin panel */
+.mobile-menu-panel {
+ position: fixed;
+ top: 0;
+ right: -300px;
+ width: 280px;
+ height: 100vh;
+ background-color: var(--bg-color);
+ box-shadow: -2px 0 10px rgba(0, 0, 0, 0.2);
+ transition: right 0.3s ease;
+ overflow-y: auto;
+ padding: 1rem;
+ display: flex;
+ flex-direction: column;
+ z-index: 1002;
+}
+
+.mobile-menu-panel.active {
+ right: 0;
+}
+
+/* translucent overlay behind panel */
+.mobile-menu-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100vh;
+ background-color: rgba(0, 0, 0, 0.5);
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity 0.3s ease, visibility 0.3s ease;
+ z-index: 1000;
+}
+
+.mobile-menu-overlay.active {
+ opacity: 1;
+ visibility: visible;
+ pointer-events: auto;
+}
+
+/* header inside mobile menu */
+.mobile-menu-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1rem;
+}
+
+.mobile-menu-header h5 {
+ margin: 0;
+ font-size: 1.25rem;
+ color: var(--text-color);
+}
+
+/* stats section in mobile menu */
+.mobile-stats-container {
+ border-bottom: 1px solid var(--border-color);
+ padding-bottom: 0.75rem;
+ margin-bottom: 1rem;
+}
+
+.mobile-stats-container .stat-item {
+ font-size: 0.9rem;
+ color: var(--text-color);
+ display: flex;
+ align-items: center;
+}
+
+.mobile-stats-container .stat-item i {
+ margin-right: 0.5em;
+ opacity: 0.8;
+}
+
+/* menu buttons list */
+.mobile-menu-items {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ flex-grow: 1;
+}
+
+/* each menu item */
+.mobile-menu-item {
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ color: var(--text-color);
+ border-radius: 6px;
+ padding: 0.6rem 1rem;
+ font-size: 1rem;
+ text-align: left;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ transition: background-color 0.2s ease;
+ cursor: pointer;
+}
+
+.mobile-menu-item:hover {
+ background-color: var(--button-hover);
+}
+
+.mobile-menu-item:active {
+ background-color: var(--button-active);
+}
+
+/* close button override */
+#close-mobile-menu.tool-button {
+ padding: 0.25rem 0.5rem;
+ font-size: 1rem;
+}
+
+/* Mobile document tabs section */
+.mobile-tabs-section {
+ border-bottom: 1px solid var(--border-color);
+ padding-bottom: 0.75rem;
+}
+
+.mobile-tabs-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 0.5rem;
+}
+
+.mobile-tabs-label {
+ font-size: 0.85rem;
+ font-weight: 600;
+ color: var(--text-color);
+ opacity: 0.8;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+}
+
+.mobile-new-tab-btn {
+ background: none;
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ color: var(--text-color);
+ padding: 2px 7px;
+ font-size: 0.9rem;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ transition: background-color 0.15s ease;
+}
+
+.mobile-new-tab-btn:hover {
+ background-color: var(--button-hover);
+}
+
+.mobile-tab-list {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ max-height: 180px;
+ overflow-y: auto;
+}
+
+.mobile-tab-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ padding: 0.45rem 0.75rem;
+ font-size: 0.9rem;
+ color: var(--text-color);
+ cursor: pointer;
+ transition: background-color 0.15s ease;
+ gap: 0.5rem;
+}
+
+.mobile-tab-item:hover {
+ background-color: var(--button-hover);
+}
+
+.mobile-tab-item.active {
+ border-color: var(--accent-color);
+ color: var(--accent-color);
+ background-color: var(--bg-color);
+}
+
+.mobile-tab-title {
+ flex: 1;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 0;
+}
+
+.mobile-tab-item .tab-menu-btn {
+ opacity: 0.6;
+}
+
+.mobile-tab-item:hover .tab-menu-btn,
+.mobile-tab-item.active .tab-menu-btn {
+ opacity: 0.8;
+}
+
+#mobile-tab-reset-btn {
+ margin-left: 0;
+ height: auto;
+ padding: 0.45rem 0.75rem;
+ justify-content: center;
+ font-size: 0.9rem;
+}
+
+/* ==========================================
+ NAVBAR RESPONSIVE BREAKPOINTS
+ >= 1080px : full desktop navbar
+ < 1080px : mobile hamburger + stacked panes
+ ========================================== */
+
+/* Mobile / tablet (< 1080px): switch to hamburger, stack panes */
+@media (max-width: 1079px) {
+ /* Override Bootstrap d-md-flex / d-md-none so the breakpoint is 1080px */
+ .stats-container,
+ .toolbar {
+ display: none !important;
+ }
+
+ /* Expand touch target sizes to meet WCAG mobile guidelines */
+ .markdown-tool-btn {
+ width: 36px !important;
+ height: 36px !important;
+ font-size: 16px !important;
+ }
+ .markdown-format-toolbar {
+ height: 44px !important;
+ }
+ .tab-close-btn {
+ width: 28px !important;
+ height: 28px !important;
+ font-size: 14px !important;
+ }
+ .tab-menu-btn {
+ width: 28px !important;
+ height: 28px !important;
+ font-size: 14px !important;
+ }
+
+ .mobile-menu {
+ display: block !important;
+ }
+
+ /* Stack editor and preview vertically */
+ .content-container {
+ flex-direction: column;
+ }
+
+ .editor-pane,
+ .preview-pane {
+ flex: none;
+ height: 50%;
+ border-right: none;
+ }
+
+ .editor-pane {
+ border-bottom: 1px solid var(--border-color);
+ }
+
+ /* Hide drag-resize divider (touch devices don't use it) */
+ .resize-divider {
+ display: none;
+ }
+
+ /* Single-pane view modes: occupy full height */
+ .content-container.view-editor-only .editor-pane,
+ .content-container.view-preview-only .preview-pane {
+ height: 100%;
+ }
+
+ .content-container.view-split .editor-pane,
+ .content-container.view-split .preview-pane {
+ height: 50%;
+ }
+}
+
+.github-link {
+ color: var(--text-color);
+ text-decoration: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: transform 0.2s ease, color 0.2s ease;
+ margin-right: 2rem;
+}
+
+.github-link:hover {
+ color: var(--accent-color);
+ transform: scale(1.1);
+}
+
+.github-link i {
+ font-size: 1.25rem;
+}
+
+/* ========================================
+ HEADER LAYOUT
+ ======================================== */
+.header-container {
+ position: relative;
+ min-height: 30px;
+}
+
+.app-header h1 {
+ font-size: 1.05rem;
+ line-height: 1.1;
+}
+
+.header-left {
+ flex: 1 0 auto;
+ justify-content: flex-start;
+ white-space: nowrap;
+}
+
+.header-right {
+ flex: 1 0 auto;
+ justify-content: flex-end;
+ white-space: nowrap;
+}
+
+/* Pane View States */
+.content-container.view-editor-only .preview-pane {
+ display: none;
+}
+
+.content-container.view-editor-only .editor-pane {
+ flex: 1;
+ border-right: none;
+}
+
+.content-container.view-preview-only .editor-pane {
+ display: none;
+}
+
+.content-container.view-preview-only .preview-pane {
+ flex: 1;
+}
+
+.content-container.view-split .editor-pane,
+.content-container.view-split .preview-pane {
+ flex: 1;
+}
+
+/* Compact desktop (< 1280px): compact toolbar */
+@media (max-width: 1280px) {
+ /* Compact toolbar at medium widths */
+ .toolbar {
+ gap: 4px;
+ }
+}
+
+
+
+/* ========================================
+ RESIZE DIVIDER - Story 1.3
+ ======================================== */
+
+.resize-divider {
+ width: 8px;
+ background-color: transparent;
+ cursor: col-resize;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ position: relative;
+ z-index: 10;
+ transition: background-color 0.2s ease;
+}
+
+.resize-divider:hover {
+ background-color: var(--button-hover);
+}
+
+.resize-divider.dragging {
+ background-color: var(--accent-color);
+}
+
+.resize-divider-handle {
+ width: 2px;
+ height: 40px;
+ background-color: var(--border-color);
+ border-radius: 2px;
+ transition: background-color 0.2s ease, width 0.2s ease;
+}
+
+.resize-divider:hover .resize-divider-handle,
+.resize-divider.dragging .resize-divider-handle {
+ background-color: var(--accent-color);
+ width: 3px;
+}
+
+/* Hide divider in single-pane modes */
+.content-container.view-editor-only .resize-divider,
+.content-container.view-preview-only .resize-divider {
+ display: none;
+}
+
+
+
+/* Prevent text selection during drag */
+.resizing {
+ user-select: none;
+ cursor: col-resize !important;
+}
+
+.resizing * {
+ cursor: col-resize !important;
+}
+
+.resizing #markdown-preview,
+.resizing #markdown-editor,
+.resizing .line-numbers {
+ pointer-events: none !important;
+}
+
+/* ========================================
+ MOBILE VIEW MODE CONTROLS - Story 1.4
+ ======================================== */
+
+.mobile-view-mode-group {
+ display: flex;
+ gap: 0;
+ border-bottom: 1px solid var(--border-color);
+ padding-bottom: 0.75rem;
+}
+
+.mobile-view-mode-btn {
+ flex: 1;
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ color: var(--text-color);
+ padding: 8px 12px;
+ font-size: 14px;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ transition: all 0.2s ease;
+}
+
+.mobile-view-mode-btn:first-child {
+ border-radius: 6px 0 0 6px;
+}
+
+.mobile-view-mode-btn:last-child {
+ border-radius: 0 6px 6px 0;
+}
+
+.mobile-view-mode-btn:not(:last-child) {
+ border-right: none;
+}
+
+.mobile-view-mode-btn:hover,
+.mobile-view-mode-btn:active {
+ background-color: var(--button-hover);
+}
+
+.mobile-view-mode-btn.active {
+ background-color: var(--button-bg);
+ border-color: var(--accent-color);
+ color: var(--accent-color);
+ border-width: 2px;
+ padding: 7px 11px;
+}
+
+.mobile-view-mode-btn.active:not(:last-child) {
+ border-right: 2px solid var(--accent-color);
+}
+
+.mobile-view-mode-btn i {
+ font-size: 18px;
+}
+
+.mobile-view-mode-btn span {
+ font-size: 12px;
+}
+
+/* ========================================
+ RESPONSIVE VIEW MODE FIXES - Story 1.5
+ ======================================== */
+
+
+
+/* ========================================
+ PDF EXPORT TABLE FIX - Rowspan/Colspan
+ ======================================== */
+
+/* Fix for html2canvas not properly rendering rowspan/colspan cells.
+ Apply backgrounds to cells instead of rows to prevent row backgrounds
+ from painting over rowspan cells during canvas capture. */
+.pdf-export table tr {
+ background-color: transparent !important;
+}
+
+.pdf-export table th,
+.pdf-export table td {
+ background-color: var(--table-bg, #ffffff);
+ position: relative;
+}
+
+.pdf-export table tr:nth-child(2n) th,
+.pdf-export table tr:nth-child(2n) td {
+ background-color: var(--bg-color, #f6f8fa);
+}
+
+/* Ensure rowspan cells render correctly */
+.pdf-export table th[rowspan],
+.pdf-export table td[rowspan] {
+ vertical-align: middle;
+ background-color: var(--table-bg, #ffffff) !important;
+}
+
+/* Ensure colspan cells render correctly */
+.pdf-export table th[colspan],
+.pdf-export table td[colspan] {
+ text-align: center;
+}
+
+/* Dark mode PDF export table fix */
+[data-theme="dark"] .pdf-export table th,
+[data-theme="dark"] .pdf-export table td {
+ background-color: var(--table-bg, #161b22);
+}
+
+[data-theme="dark"] .pdf-export table tr:nth-child(2n) th,
+[data-theme="dark"] .pdf-export table tr:nth-child(2n) td {
+ background-color: #1c2128;
+}
+
+[data-theme="dark"] .pdf-export table th[rowspan],
+[data-theme="dark"] .pdf-export table td[rowspan] {
+ background-color: var(--table-bg, #161b22) !important;
+}
+
+/* ========================================
+ MERMAID DIAGRAM TOOLBAR
+ ======================================== */
+
+.mermaid-container {
+ position: relative;
+}
+
+.mermaid-toolbar {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ display: flex;
+ gap: 4px;
+ opacity: 0;
+ transition: opacity 0.2s ease;
+ z-index: 10;
+}
+
+.mermaid-container:hover .mermaid-toolbar {
+ opacity: 1;
+}
+
+.mermaid-toolbar-btn {
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ color: var(--text-color);
+ border-radius: 4px;
+ padding: 4px 7px;
+ font-size: 13px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 3px;
+ transition: background-color 0.2s ease, color 0.2s ease;
+ white-space: nowrap;
+}
+
+.mermaid-toolbar-btn:hover {
+ background-color: var(--button-hover);
+ color: var(--accent-color);
+}
+
+.mermaid-toolbar-btn:active {
+ background-color: var(--button-active);
+}
+
+.mermaid-toolbar-btn i {
+ font-size: 14px;
+}
+
+/* ========================================
+ MERMAID ZOOM MODAL
+ ======================================== */
+
+#mermaid-zoom-modal {
+ display: none;
+ position: fixed;
+ inset: 0;
+ z-index: 2000;
+ background-color: rgba(0, 0, 0, 0.75);
+ align-items: center;
+ justify-content: center;
+}
+
+#mermaid-zoom-modal.active {
+ display: flex;
+}
+
+.mermaid-modal-content {
+ background-color: var(--bg-color);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 16px;
+ width: 85vw;
+ height: 85vh;
+ max-width: 85vw;
+ max-height: 85vh;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+@media (max-width: 576px) {
+ .mermaid-modal-content {
+ width: 95vw;
+ height: 90vh;
+ max-width: 95vw;
+ max-height: 90vh;
+ padding: 10px;
+ }
+}
+
+.mermaid-modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.mermaid-modal-header span {
+ font-weight: 600;
+ font-size: 15px;
+ color: var(--text-color);
+}
+
+.mermaid-modal-close {
+ background: none;
+ border: none;
+ color: var(--text-color);
+ font-size: 1.2rem;
+ cursor: pointer;
+ padding: 2px 6px;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ transition: background-color 0.2s ease;
+}
+
+.mermaid-modal-close:hover {
+ background-color: var(--button-hover);
+}
+
+.mermaid-modal-diagram {
+ overflow: auto;
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 200px;
+ cursor: grab;
+}
+
+.mermaid-modal-diagram.dragging {
+ cursor: grabbing;
+}
+
+.mermaid-modal-diagram svg {
+ transform-origin: center;
+ transition: transform 0.1s ease;
+ max-width: none;
+}
+
+.mermaid-modal-controls {
+ display: flex;
+ justify-content: center;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.mermaid-modal-controls .mermaid-toolbar-btn {
+ opacity: 1;
+}
+
+/* ========================================
+ DOCUMENT TABS & SESSION MANAGEMENT
+ ======================================== */
+
+.tab-bar {
+ display: flex;
+ align-items: center;
+ background-color: var(--header-bg);
+ border-bottom: 1px solid var(--border-color);
+ height: 32px;
+ overflow: visible; /* ā was: overflow: hidden */
+ flex-shrink: 0;
+ padding: 0 4px;
+ gap: 0;
+ user-select: none;
+ position: relative;
+ z-index: 10;
+}
+
+.tab-list {
+ display: flex;
+ align-items: flex-end;
+ overflow-x: auto;
+ overflow-y: visible; /* ā was: overflow-y: hidden */
+ flex: 1;
+ height: 100%;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+
+.tab-list::-webkit-scrollbar {
+ display: none;
+}
+
+.tab-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ height: 32px;
+ padding: 0 8px 0 10px;
+ min-width: 100px;
+ max-width: 180px;
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ border-bottom: 1px solid transparent;
+ border-radius: 6px 6px 0 0;
+ cursor: pointer;
+ font-size: 13px;
+ color: var(--text-color);
+ white-space: nowrap;
+ /* overflow: hidden; <-- REMOVE THIS */
+ position: relative;
+ transition: background-color 0.15s ease, color 0.15s ease;
+ flex-shrink: 0;
+ margin-right: 2px;
+ opacity: 0.7;
+}
+
+.tab-item:hover {
+ background-color: var(--button-hover);
+ opacity: 0.9;
+}
+
+.tab-item.active {
+ background-color: var(--bg-color);
+ border-color: var(--border-color);
+ color: var(--accent-color);
+ border-bottom: 1px solid var(--bg-color);
+ opacity: 1;
+ z-index: 2;
+}
+
+.tab-item.unsaved::after {
+ content: '';
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ background-color: var(--accent-color);
+ border-radius: 50%;
+ flex-shrink: 0;
+ margin-left: 2px;
+}
+
+.tab-title {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ flex: 1;
+ min-width: 0;
+}
+
+.tab-close-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 16px;
+ height: 16px;
+ border-radius: 3px;
+ background: none;
+ border: none;
+ color: var(--text-color);
+ cursor: pointer;
+ padding: 0;
+ font-size: 11px;
+ opacity: 0;
+ flex-shrink: 0;
+ transition: background-color 0.15s ease, opacity 0.15s ease;
+}
+
+.tab-item:hover .tab-close-btn,
+.tab-item.active .tab-close-btn {
+ opacity: 0.6;
+}
+
+.tab-close-btn:hover {
+ background-color: var(--button-active);
+ opacity: 1 !important;
+ color: var(--color-danger-fg, #d73a49);
+}
+
+.tab-new-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ height: 24px;
+ padding: 0 8px;
+ border-radius: 5px;
+ background: none;
+ border: 1px solid var(--border-color);
+ color: var(--text-color);
+ cursor: pointer;
+ font-size: 12px;
+ flex-shrink: 0;
+ margin-left: 6px;
+ align-self: center;
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
+}
+
+.tab-new-btn:hover {
+ background-color: rgba(46, 160, 67, 0.1);
+ border-color: var(--accent-color, #2ea043);
+ color: var(--accent-color, #2ea043);
+}
+
+.tab-new-btn:active {
+ background-color: rgba(46, 160, 67, 0.2);
+}
+
+/* Drag-and-drop visual feedback */
+.tab-item.dragging {
+ opacity: 0.4;
+}
+
+.tab-item.drag-over {
+ border-left: 2px solid var(--accent-color);
+}
+
+/* Tab enter animation */
+@keyframes tabSlideIn {
+ from { opacity: 0; transform: translateY(4px); }
+ to { opacity: 0.7; transform: translateY(0); }
+}
+
+.tab-item {
+ animation: tabSlideIn 0.12s ease forwards;
+}
+
+.tab-item.active {
+ animation: none;
+}
+
+/* Hide tab bar on very small screens ā single-file use */
+@media (max-width: 480px) {
+ .tab-bar {
+ display: none;
+ }
+}
+
+/* ========================================
+ TAB OVERFLOW ā Scroll Buttons & Fade Indicators
+ ======================================== */
+
+.tab-scroll-btn {
+ display: none;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ border-radius: 4px;
+ background: none;
+ border: 1px solid transparent;
+ color: var(--text-color);
+ cursor: pointer;
+ font-size: 14px;
+ flex-shrink: 0;
+ padding: 0;
+ transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
+ z-index: 2;
+ opacity: 0.6;
+}
+
+.tab-scroll-btn:hover {
+ background-color: var(--button-hover);
+ border-color: var(--border-color);
+ opacity: 1;
+}
+
+.tab-scroll-btn:active {
+ background-color: var(--button-active);
+}
+
+/* Show scroll buttons only when overflow exists */
+.tab-bar.has-overflow-left .tab-scroll-left,
+.tab-bar.has-overflow-right .tab-scroll-right {
+ display: flex;
+}
+
+/* Overflow fade indicators ā subtle gradient at clipped edges */
+.tab-list::before,
+.tab-list::after {
+ content: '';
+ position: sticky;
+ top: 0;
+ bottom: 0;
+ width: 0;
+ flex-shrink: 0;
+ pointer-events: none;
+ z-index: 3;
+ transition: box-shadow 0.2s ease;
+}
+
+.tab-list::before {
+ left: 0;
+}
+
+.tab-list::after {
+ right: 0;
+}
+
+.tab-bar.has-overflow-left .tab-list::before {
+ box-shadow: 8px 0 12px -4px rgba(0, 0, 0, 0.12);
+}
+
+.tab-bar.has-overflow-right .tab-list::after {
+ box-shadow: -8px 0 12px -4px rgba(0, 0, 0, 0.12);
+}
+
+[data-theme="dark"] .tab-bar.has-overflow-left .tab-list::before {
+ box-shadow: 8px 0 12px -4px rgba(0, 0, 0, 0.35);
+}
+
+[data-theme="dark"] .tab-bar.has-overflow-right .tab-list::after {
+ box-shadow: -8px 0 12px -4px rgba(0, 0, 0, 0.35);
+}
+
+/* ========================================
+ THREE-DOT TAB MENU
+ ======================================== */
+
+.tab-menu-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 22px;
+ height: 22px;
+ border-radius: 3px;
+ background: none;
+ border: none;
+ color: var(--text-color);
+ cursor: pointer;
+ padding: 0;
+ font-size: 14px;
+ font-weight: bold;
+ letter-spacing: 1px;
+ opacity: 0.65;
+ flex-shrink: 0;
+ transition: background-color 0.15s ease, opacity 0.15s ease;
+ position: relative;
+}
+
+/* Touch Hitbox Expansion for Tab Menu Button */
+.tab-menu-btn::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 48px;
+ height: 48px;
+}
+
+/* Touch-optimized styling for coarser pointers (e.g., smartphones & tablets) */
+@media (pointer: coarse) {
+ .tab-bar {
+ height: 40px !important;
+ }
+ .tab-item {
+ height: 40px !important;
+ font-size: 14px !important;
+ padding: 0 10px 0 12px !important;
+ gap: 8px !important;
+ }
+ .tab-new-btn,
+ .tab-reset-btn {
+ height: 32px !important;
+ font-size: 14px !important;
+ padding: 0 12px !important;
+ }
+ .tab-scroll-btn {
+ width: 32px !important;
+ height: 32px !important;
+ font-size: 18px !important;
+ }
+ .tab-menu-btn {
+ width: 30px !important;
+ height: 30px !important;
+ font-size: 18px !important;
+ }
+ .tab-close-btn {
+ width: 20px !important;
+ height: 20px !important;
+ font-size: 13px !important;
+ opacity: 0.8 !important;
+ }
+}
+
+.tab-item:hover .tab-menu-btn,
+.tab-item.active .tab-menu-btn {
+ opacity: 0.65;
+}
+
+.tab-menu-btn:hover {
+ background-color: var(--button-active);
+ opacity: 1 !important;
+}
+
+.tab-menu-dropdown {
+ display: none;
+ position: fixed;
+ min-width: 130px;
+ background-color: var(--header-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+ z-index: 99999;
+ overflow: hidden;
+ flex-direction: column;
+}
+
+.tab-menu-dropdown.open {
+ display: flex;
+}
+
+.tab-menu-item {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ padding: 7px 12px;
+ background: none;
+ border: none;
+ color: var(--text-color);
+ font-size: 12px;
+ cursor: pointer;
+ text-align: left;
+ transition: background-color 0.12s ease;
+ white-space: nowrap;
+}
+
+.tab-menu-item:hover {
+ background-color: var(--button-hover);
+}
+
+.tab-menu-item-danger {
+ color: var(--color-danger-fg, #d73a49);
+}
+
+.tab-menu-item-danger:hover {
+ background-color: rgba(215, 58, 73, 0.1);
+}
+
+/* ========================================
+ RESET BUTTON
+ ======================================== */
+
+.tab-reset-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ height: 24px;
+ padding: 0 8px;
+ border-radius: 5px;
+ background: none;
+ border: 1px solid var(--border-color);
+ color: var(--text-color);
+ cursor: pointer;
+ font-size: 12px;
+ flex-shrink: 0;
+ margin-left: 6px;
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
+}
+
+.tab-reset-btn:hover {
+ background-color: rgba(215, 58, 73, 0.1);
+ border-color: var(--color-danger-fg, #d73a49);
+ color: var(--color-danger-fg, #d73a49);
+}
+
+/* ========================================
+ RESET & RENAME CONFIRMATION MODALS
+ ======================================== */
+
+.reset-modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.45);
+ z-index: 2000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.reset-modal-overlay.modal-overlay {
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity 0.2s ease, visibility 0.2s ease;
+}
+
+.reset-modal-overlay.modal-overlay.is-visible {
+ opacity: 1;
+ visibility: visible;
+}
+
+.reset-modal-box {
+ background: var(--header-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 10px;
+ padding: 24px 28px;
+ min-width: 280px;
+ max-width: 360px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.25);
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.modal-box {
+ max-height: min(85vh, 760px);
+ opacity: 0;
+ transform: translateY(8px);
+ transition: transform 0.2s ease, opacity 0.2s ease;
+}
+
+.reset-modal-overlay.modal-overlay.is-visible .modal-box {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.modal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.modal-header .reset-modal-message {
+ text-align: left;
+ flex: 1;
+}
+
+.modal-close-btn {
+ border: 1px solid var(--border-color);
+ background: var(--button-bg);
+ color: var(--text-color);
+ border-radius: 6px;
+ width: 28px;
+ height: 28px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ transition: background-color 0.15s ease;
+}
+
+.modal-close-btn:hover {
+ background-color: var(--button-hover);
+}
+
+.modal-body {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ max-height: min(60vh, 520px);
+ overflow: auto;
+ padding-right: 4px;
+}
+
+.modal-section {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.modal-section-title {
+ margin: 0;
+ font-size: 0.95rem;
+ font-weight: 600;
+}
+
+.modal-list {
+ margin: 0;
+ padding-left: 1.1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ font-size: 0.85rem;
+}
+
+.modal-list a {
+ color: var(--accent-color);
+ text-decoration: none;
+}
+
+.modal-list a:hover {
+ text-decoration: underline;
+}
+
+.modal-subtext {
+ margin: 0;
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+ line-height: 1.4;
+}
+
+.find-replace-meta {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.find-match-count {
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+}
+
+.find-replace-nav {
+ display: inline-flex;
+ gap: 6px;
+}
+
+.find-nav-btn {
+ width: 28px;
+ height: 28px;
+ padding: 0;
+}
+
+.about-header {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ flex-wrap: wrap;
+}
+
+.about-logo {
+ width: 64px;
+ height: 64px;
+ border-radius: 12px;
+ border: 1px solid var(--border-color);
+ object-fit: cover;
+}
+
+.about-details {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.about-title {
+ margin: 0;
+ font-size: 1.05rem;
+ font-weight: 600;
+}
+
+.about-description {
+ margin: 0;
+ font-size: 0.85rem;
+ color: var(--text-secondary, #57606a);
+}
+
+.about-meta {
+ margin: 0;
+ font-size: 0.78rem;
+ color: var(--text-secondary, #57606a);
+}
+
+.modal-body kbd {
+ padding: 2px 6px;
+ border-radius: 4px;
+ background-color: var(--button-bg);
+ border: 1px solid var(--border-color);
+ font-size: 0.75rem;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+}
+
+.reset-modal-box--wide {
+ width: min(92vw, 640px);
+ max-width: 640px;
+}
+
+.reset-modal-message {
+ margin: 0;
+ font-size: 14px;
+ color: var(--text-color);
+ font-weight: 500;
+ text-align: center;
+}
+
+.reset-modal-actions {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+}
+
+.reset-modal-btn {
+ padding: 6px 16px;
+ border-radius: 6px;
+ border: 1px solid var(--border-color);
+ background: var(--button-bg);
+ color: var(--text-color);
+ font-size: 13px;
+ cursor: pointer;
+ transition: background-color 0.15s ease;
+}
+
+.reset-modal-btn:hover {
+ background-color: var(--button-hover);
+}
+
+.reset-modal-confirm {
+ background-color: var(--color-danger-fg, #d73a49);
+ border-color: var(--color-danger-fg, #d73a49);
+ color: #fff;
+}
+
+.reset-modal-confirm:hover {
+ background-color: #b02a37;
+ border-color: #b02a37;
+}
+
+/* ========================================
+ PDF EXPORT PROGRESS MODAL
+ ======================================== */
+
+.pdf-progress-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 2600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.45);
+ padding: 20px;
+}
+
+.pdf-progress-modal {
+ width: min(92vw, 420px);
+ background: var(--header-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.28);
+ color: var(--text-color);
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ padding: 20px;
+}
+
+.pdf-progress-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.pdf-progress-title {
+ margin: 0;
+ font-size: 15px;
+ font-weight: 600;
+}
+
+.pdf-progress-percent {
+ font-size: 28px;
+ font-weight: 600;
+ line-height: 1;
+}
+
+.pdf-progress-track {
+ width: 100%;
+ height: 10px;
+ background: var(--button-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 999px;
+ overflow: hidden;
+}
+
+.pdf-progress-fill {
+ width: 0%;
+ height: 100%;
+ background: var(--accent-color);
+ border-radius: inherit;
+ transition: width 0.18s ease;
+}
+
+.pdf-progress-details {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ font-size: 12px;
+ color: var(--text-secondary);
+}
+
+.pdf-progress-detail {
+ display: flex;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.pdf-progress-detail strong {
+ color: var(--text-color);
+ font-weight: 600;
+}
+
+.pdf-progress-actions {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.tool-button.pdf-export-loading,
+.mobile-menu-item.pdf-export-loading {
+ pointer-events: none;
+}
+/* ========================================
+ RESET MODAL FORM FIELDS
+ ======================================== */
+
+.reset-modal-field {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ text-align: left;
+}
+
+.reset-modal-field-group {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.reset-modal-label {
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+ font-weight: 600;
+}
+
+.reset-modal-toggle-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.reset-modal-option {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 13px;
+ color: var(--text-color);
+}
+
+.reset-modal-option input {
+ margin: 0;
+}
+
+/* ========================================
+ RENAME MODAL INPUT
+ ======================================== */
+
+.rename-modal-input {
+ width: 100%;
+ padding: 7px 10px;
+ border-radius: 6px;
+ border: 1px solid var(--border-color);
+ background: var(--bg-color);
+ color: var(--text-color);
+ font-size: 13px;
+ outline: none;
+ box-sizing: border-box;
+}
+
+.rename-modal-input:focus {
+ border-color: var(--accent-color);
+}
+
+/* ========================================
+ TOOLBAR POPUP PANELS
+ ======================================== */
+
+.reset-modal-box--xl {
+ width: min(94vw, 980px);
+ max-width: 980px;
+}
+
+.modal-empty {
+ margin: 0;
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+ text-align: center;
+}
+
+.emoji-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+ gap: 12px;
+ max-height: min(52vh, 440px);
+ overflow: auto;
+ padding: 4px;
+}
+
+.symbol-grid {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ max-height: min(52vh, 440px);
+ overflow: auto;
+ padding: 4px 2px;
+}
+
+.symbol-section-title {
+ margin: 0;
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--text-secondary, #57606a);
+}
+
+.symbol-section-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ gap: 12px;
+}
+
+.alert-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+ gap: 12px;
+ max-height: min(45vh, 360px);
+ overflow: auto;
+ padding: 2px;
+}
+
+.emoji-item,
+.symbol-item,
+.alert-option {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ align-items: center;
+ border: 1px solid var(--border-color);
+ border-radius: 10px;
+ padding: 10px;
+ background: var(--bg-color);
+ color: var(--text-color);
+ cursor: pointer;
+ transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
+}
+
+.emoji-item:focus-visible,
+.symbol-item:focus-visible,
+.alert-option:focus-visible {
+ outline: 2px solid var(--accent-color);
+ outline-offset: 2px;
+}
+
+.emoji-item.is-selected,
+.symbol-item.is-selected,
+.alert-option.is-selected {
+ border-color: var(--accent-color);
+ box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2);
+ background-color: rgba(88, 166, 255, 0.08);
+}
+
+.emoji-preview {
+ width: 36px;
+ height: 36px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.emoji-preview img {
+ width: 32px;
+ height: 32px;
+}
+
+.emoji-shortcode {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+ text-align: center;
+}
+
+.emoji-copy-btn,
+.symbol-copy-btn {
+ border: none;
+ background: transparent;
+ color: var(--text-secondary, #57606a);
+ cursor: pointer;
+ padding: 2px;
+ border-radius: 4px;
+}
+
+.emoji-copy-btn:hover,
+.symbol-copy-btn:hover {
+ color: var(--text-color);
+ background: var(--button-hover);
+}
+
+.emoji-copy-btn.is-copied,
+.symbol-copy-btn.is-copied {
+ color: var(--accent-color);
+}
+
+.symbol-preview {
+ font-size: 28px;
+ line-height: 1;
+}
+
+.symbol-code {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+}
+
+.alert-option {
+ align-items: stretch;
+ text-align: left;
+ padding: 12px;
+}
+
+.alert-preview {
+ margin: 0;
+}
+
+.alert-preview .markdown-alert {
+ padding: 0.5rem 0.9rem;
+ border-left: 0.25em solid;
+ border-radius: 0.375rem;
+}
+
+.alert-preview .markdown-alert-title {
+ margin: 0 0 6px;
+ font-weight: 600;
+ line-height: 1.25;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.alert-preview .markdown-alert-icon {
+ display: inline-flex;
+ width: 16px;
+ height: 16px;
+}
+
+.alert-preview .markdown-alert-icon svg {
+ width: 16px;
+ height: 16px;
+ fill: currentColor;
+}
+
+.alert-preview .markdown-alert > *:not(.markdown-alert-title) {
+ color: var(--text-color);
+}
+
+.alert-preview .markdown-alert-note {
+ color: #0969da;
+ border-left-color: #0969da;
+ background-color: #ddf4ff;
+}
+
+.alert-preview .markdown-alert-tip {
+ color: #1a7f37;
+ border-left-color: #1a7f37;
+ background-color: #dafbe1;
+}
+
+.alert-preview .markdown-alert-important {
+ color: #8250df;
+ border-left-color: #8250df;
+ background-color: #fbefff;
+}
+
+.alert-preview .markdown-alert-warning {
+ color: #9a6700;
+ border-left-color: #9a6700;
+ background-color: #fff8c5;
+}
+
+.alert-preview .markdown-alert-caution {
+ color: #cf222e;
+ border-left-color: #cf222e;
+ background-color: #ffebe9;
+}
+
+[data-theme="dark"] .alert-preview .markdown-alert-note {
+ color: #4493f8;
+ border-left-color: #4493f8;
+ background-color: rgba(31, 111, 235, 0.15);
+}
+
+[data-theme="dark"] .alert-preview .markdown-alert-tip {
+ color: #3fb950;
+ border-left-color: #3fb950;
+ background-color: rgba(35, 134, 54, 0.15);
+}
+
+[data-theme="dark"] .alert-preview .markdown-alert-important {
+ color: #ab7df8;
+ border-left-color: #ab7df8;
+ background-color: rgba(137, 87, 229, 0.15);
+}
+
+[data-theme="dark"] .alert-preview .markdown-alert-warning {
+ color: #d29922;
+ border-left-color: #d29922;
+ background-color: rgba(210, 153, 34, 0.18);
+}
+
+[data-theme="dark"] .alert-preview .markdown-alert-caution {
+ color: #f85149;
+ border-left-color: #f85149;
+ background-color: rgba(248, 81, 73, 0.18);
+}
+
+.github-import-error {
+ margin: 0;
+ font-size: 12px;
+ color: var(--color-danger-fg, #d73a49);
+ text-align: left;
+ line-height: 1.5;
+}
+
+.github-import-error.is-info {
+ color: var(--text-secondary, #57606a);
+}
+
+#github-import-modal .reset-modal-box {
+ width: 60vw;
+ max-width: 60vw;
+ min-width: 340px;
+ padding: 30px 34px;
+ gap: 16px;
+ box-shadow: 0 20px 48px rgba(0, 0, 0, 0.22);
+}
+
+#github-import-modal .reset-modal-message {
+ font-size: 18px;
+ line-height: 1.35;
+ text-align: left;
+}
+
+#github-import-url,
+#github-import-file-select {
+ min-height: 46px;
+ padding: 10px 12px;
+ font-size: 15px;
+}
+
+#github-import-file-select {
+ min-height: 180px;
+}
+
+.github-import-tree {
+ max-height: 420px;
+ overflow: auto;
+ border: 1px solid var(--border-color);
+ border-radius: 10px;
+ padding: 12px;
+ background: var(--bg-color);
+}
+
+.github-import-selection-toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 10px 12px;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ background: var(--button-bg);
+}
+
+.github-import-selected-count {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--text-color);
+}
+
+.github-import-tree ul {
+ list-style: none;
+ margin: 0;
+ padding-left: 18px;
+}
+
+.github-import-tree > ul {
+ padding-left: 4px;
+}
+
+.github-import-tree li {
+ margin: 2px 0;
+}
+
+.github-tree-folder-label {
+ display: inline-block;
+ font-size: 14px;
+ color: var(--text-secondary, #57606a);
+ margin-bottom: 4px;
+}
+
+.github-tree-file-btn {
+ border: 0;
+ background: transparent;
+ color: var(--text-color);
+ cursor: pointer;
+ padding: 6px 8px;
+ border-radius: 6px;
+ text-align: left;
+ width: 100%;
+ font-size: 14px;
+}
+
+.github-tree-file-btn:hover,
+.github-tree-file-btn:focus-visible {
+ background: var(--button-hover);
+ outline: none;
+}
+
+.github-tree-file-btn.is-selected {
+ background: rgba(56, 139, 253, 0.14);
+ color: var(--accent-color);
+}
+
+#github-import-modal .reset-modal-actions {
+ gap: 12px;
+}
+
+#github-import-modal .reset-modal-btn {
+ min-height: 42px;
+ padding: 9px 18px;
+ font-size: 14px;
+}
+
+@media (max-width: 576px) {
+ #github-import-modal .reset-modal-box {
+ width: 95vw;
+ max-width: 95vw;
+ min-width: 0;
+ padding: 20px;
+ gap: 14px;
+ }
+
+ .github-import-selection-toolbar {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ #github-import-modal .reset-modal-message {
+ font-size: 16px;
+ }
+
+ #github-import-modal .reset-modal-actions {
+ flex-direction: column-reverse;
+ }
+
+ #github-import-modal .reset-modal-btn {
+ width: 100%;
+ }
+}
+
+.frontmatter-table {
+ border-collapse: collapse;
+ margin-bottom: 1.5em;
+ font-size: 0.9em;
+ width: auto;
+ max-width: 100%;
+}
+
+.frontmatter-table th,
+.frontmatter-table td {
+ border: 1px solid var(--border-color);
+ padding: 6px 13px;
+ vertical-align: top;
+ color: var(--text-color);
+}
+
+.frontmatter-table tr:nth-child(odd) th,
+.frontmatter-table tr:nth-child(odd) td {
+ background-color: var(--table-bg);
+}
+
+.frontmatter-table tr:nth-child(even) th,
+.frontmatter-table tr:nth-child(even) td {
+ background-color: var(--editor-bg);
+}
+
+.frontmatter-table th {
+ font-weight: 600;
+ text-align: right;
+ white-space: nowrap;
+ vertical-align: middle;
+}
+
+.frontmatter-table td {
+ text-align: left;
+}
+
+.fm-complex {
+ margin: 0;
+ padding: 4px 6px;
+ font-size: 0.8em;
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
+ white-space: pre-wrap;
+ word-break: break-word;
+ background: transparent;
+ border: none;
+ color: var(--text-color);
+}
+
+.fm-tag {
+ display: inline-block;
+ padding: 2px 8px;
+ margin: 2px 3px 2px 0;
+ border: 1px solid var(--border-color);
+ border-radius: 2em;
+ font-size: 0.8em;
+ font-weight: 500;
+ color: var(--accent-color);
+ background-color: var(--button-bg);
+ white-space: nowrap;
+}
+
/* ========================================
- PDF EXPORT PROGRESS MODAL
+ RTL SUPPORT
+ ======================================== */
+
+[dir="rtl"] body {
+ direction: rtl;
+}
+
+[dir="rtl"] .editor-pane {
+ padding-left: 0px;
+ padding-right: 20px;
+ border-right: none;
+ border-left: 1px solid var(--border-color);
+}
+
+[dir="rtl"] #markdown-editor,
+[dir="rtl"] .markdown-body {
+ direction: rtl;
+ text-align: right;
+}
+
+[dir="rtl"] .markdown-body pre,
+[dir="rtl"] .markdown-body code,
+[dir="rtl"] .fm-complex {
+ direction: ltr;
+ text-align: left;
+}
+
+[dir="rtl"] .line-numbers {
+ left: auto;
+ right: 20px;
+ padding: 10px 0 10px 8px;
+ text-align: left;
+ border-right: none;
+ border-left: 1px solid var(--border-color);
+}
+
+[dir="rtl"] #markdown-editor {
+ padding-left: 10px;
+ padding-right: calc(10px + var(--line-number-gutter));
+}
+
+[dir="rtl"] .editor-highlight-layer {
+ inset: 20px calc(20px + var(--line-number-gutter)) 20px 0;
+}
+
+[dir="rtl"] .mobile-menu-item,
+[dir="rtl"] .tab-menu-item,
+[dir="rtl"] .modal-header .reset-modal-message,
+[dir="rtl"] .reset-modal-field,
+[dir="rtl"] .alert-option,
+[dir="rtl"] .github-import-error,
+[dir="rtl"] #github-import-modal .reset-modal-message,
+[dir="rtl"] .github-tree-file-btn,
+[dir="rtl"] .frontmatter-table td {
+ text-align: right;
+}
+
+[dir="rtl"] .github-import-tree ul {
+ padding-left: 0;
+ padding-right: 18px;
+}
+
+[dir="rtl"] .github-import-tree > ul {
+ padding-right: 4px;
+}
+
+[dir="rtl"] .markdown-body .markdown-alert,
+[dir="rtl"] .alert-preview .markdown-alert {
+ border-left: 0;
+ border-right: 0.25em solid currentColor;
+}
+
+/* ============================================
+ SHARE MODAL
+ ============================================ */
+
+.share-modal-description {
+ font-size: 13px;
+ color: var(--text-secondary, #57606a);
+ margin: 0;
+}
+
+.share-mode-cards {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.share-mode-card {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 14px;
+ border-radius: 8px;
+ border: 1px solid var(--border-color);
+ background: var(--bg-color);
+ cursor: pointer;
+ transition: border-color 0.15s ease, background-color 0.15s ease;
+ user-select: none;
+}
+
+.share-mode-card:hover {
+ border-color: var(--accent-color);
+ background: var(--button-hover);
+}
+
+.share-mode-card.is-selected {
+ border-color: var(--accent-color);
+ background: color-mix(in srgb, var(--accent-color) 8%, transparent);
+}
+
+.share-mode-card input[type="radio"] {
+ display: none;
+}
+
+.share-card-icon {
+ font-size: 18px;
+ width: 28px;
+ text-align: center;
+ color: var(--text-secondary, #57606a);
+ flex-shrink: 0;
+}
+
+.share-mode-card.is-selected .share-card-icon {
+ color: var(--accent-color);
+}
+
+.share-card-body {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ flex: 1;
+ min-width: 0;
+}
+
+.share-card-title {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-color);
+}
+
+.share-card-desc {
+ font-size: 12px;
+ color: var(--text-secondary, #57606a);
+}
+
+.share-card-check {
+ width: 18px;
+ text-align: center;
+ color: var(--accent-color);
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ flex-shrink: 0;
+}
+
+.share-mode-card.is-selected .share-card-check {
+ opacity: 1;
+}
+
+.share-url-row {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.share-url-input {
+ flex: 1;
+ font-size: 12px;
+ font-family: var(--font-mono, monospace);
+ color: var(--text-secondary, #57606a);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.share-copy-btn {
+ flex-shrink: 0;
+ padding: 6px 10px;
+}
+
+.share-copy-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.share-modal-notice {
+ font-size: 11px;
+ color: var(--text-secondary, #57606a);
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 5px;
+}
+
+/* ==========================================================================
+ Multilingual & CJK Optimization styles added by Aegis SEO agency
+ ========================================================================== */
+.lang-select-item {
+ display: flex !important;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+ transition: background-color 0.2s ease, padding-left 0.2s ease;
+}
+
+.lang-select-item:hover {
+ padding-left: 12px;
+}
+
+.lang-select-item.active {
+ background-color: var(--accent-color) !important;
+ color: #ffffff !important;
+ font-weight: 600;
+}
+
+/* Adjust CJK text layout for maximum readability inside the preview pane */
+html[lang="zh"] .markdown-body,
+html[lang="ja"] .markdown-body,
+html[lang="ko"] .markdown-body {
+ line-height: 1.75 !important;
+ letter-spacing: 0.03em;
+ word-break: keep-all;
+ overflow-wrap: break-word;
+ text-align: justify;
+}
+
+/* Specific heading spacing improvements for CJK characters */
+html[lang="zh"] .markdown-body h1, html[lang="zh"] .markdown-body h2, html[lang="zh"] .markdown-body h3,
+html[lang="ja"] .markdown-body h1, html[lang="ja"] .markdown-body h2, html[lang="ja"] .markdown-body h3,
+html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang="ko"] .markdown-body h3 {
+ font-weight: 700;
+ letter-spacing: 0.02em;
+ margin-top: 1.4em;
+ margin-bottom: 0.6em;
+}
+
+/* Smooth fade and scale transition for dropdown active states */
+.dropdown-menu {
+ opacity: 0;
+ transform: translateY(8px) scale(0.98);
+ display: block;
+ visibility: hidden;
+ transition: opacity 0.2s cubic-bezier(0.16, 1, 0.3, 1), transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), visibility 0.2s;
+}
+
+.dropdown-menu.show {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ visibility: visible;
+}
+
+/* ========================================
+ FIND & REPLACE FLOATING PANEL DESIGN
======================================== */
-.pdf-progress-overlay {
- position: fixed;
- inset: 0;
- z-index: 2600;
+.find-replace-panel {
+ position: fixed;
+ top: 100px;
+ right: 20px;
+ width: 340px;
+ background-color: var(--fr-bg);
+ border: 1px solid var(--fr-border);
+ border-radius: 12px;
+ box-shadow: var(--fr-shadow);
+ z-index: 1050;
+ display: flex;
+ flex-direction: column;
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ transition: opacity 0.2s ease, transform 0.2s ease;
+ user-select: none;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
+}
+
+.find-replace-panel.docked {
+ position: relative;
+ top: 0 !important;
+ right: 0 !important;
+ height: 100%;
+ border-radius: 0;
+ border-top: none;
+ border-bottom: none;
+ border-right: none;
+ border-left: 1px solid var(--fr-border);
+ box-shadow: none;
+ backdrop-filter: none;
+ z-index: 10;
+ flex-shrink: 0;
+}
+
+.find-replace-panel.docked #find-replace-reset,
+.find-replace-panel.docked #find-replace-reset-footer {
+ display: none !important;
+}
+
+.find-replace-header {
display: flex;
align-items: center;
- justify-content: center;
- background: rgba(0, 0, 0, 0.45);
- padding: 20px;
+ justify-content: space-between;
+ padding: 8px 12px;
+ border-bottom: 1px solid var(--fr-border);
+ cursor: move;
+ background-color: var(--header-bg);
+ border-top-left-radius: 11px;
+ border-top-right-radius: 11px;
}
-.pdf-progress-modal {
- width: min(92vw, 420px);
- background: var(--header-bg);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- box-shadow: 0 16px 48px rgba(0, 0, 0, 0.28);
+.find-replace-panel.docked .find-replace-header {
+ cursor: default;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.find-replace-title {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-color);
+}
+
+.find-replace-header-actions {
+ display: flex;
+ gap: 4px;
+}
+
+.panel-icon-btn {
+ background: none;
+ border: none;
color: var(--text-color);
+ font-size: 12px;
+ cursor: pointer;
+ padding: 2px 6px;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background-color 0.15s ease;
+}
+
+.panel-icon-btn:hover {
+ background-color: var(--button-hover);
+}
+
+.find-replace-body {
+ padding: 12px;
display: flex;
flex-direction: column;
- gap: 16px;
- padding: 20px;
+ gap: 8px;
}
-.pdf-progress-header {
+.find-replace-field-row {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+}
+
+.find-input-container, .replace-input-container {
display: flex;
align-items: center;
- justify-content: space-between;
- gap: 12px;
+ border: 1px solid var(--fr-border);
+ border-radius: 6px;
+ background-color: var(--bg-color);
+ padding: 2px 4px;
+ width: 100%;
}
-.pdf-progress-title {
- margin: 0;
- font-size: 15px;
+.find-input-container:focus-within, .replace-input-container:focus-within {
+ border-color: var(--accent-color);
+ box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.15);
+}
+
+.find-input-field {
+ flex: 1;
+ border: none;
+ background: transparent;
+ color: var(--text-color);
+ font-size: 13px;
+ padding: 4px 6px;
+ outline: none;
+ width: 50%;
+}
+
+.find-options-group {
+ display: flex;
+ gap: 2px;
+}
+
+.find-option-btn {
+ background: none;
+ border: none;
+ color: var(--text-secondary);
+ font-size: 11px;
font-weight: 600;
+ cursor: pointer;
+ padding: 2px 5px;
+ border-radius: 4px;
+ transition: background-color 0.12s ease, color 0.12s ease;
+ min-width: 22px;
+ height: 22px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
}
-.pdf-progress-percent {
- font-size: 28px;
+.find-option-btn:hover {
+ background-color: var(--button-hover);
+ color: var(--text-color);
+}
+
+.find-option-btn.active {
+ background-color: var(--fr-btn-active-bg);
+ color: var(--fr-btn-active);
+}
+
+.find-error-drawer {
+ background-color: var(--fr-error-bg);
+ border: 1px solid var(--fr-error-border);
+ border-radius: 6px;
+ padding: 6px 10px;
+ font-size: 11px;
+ color: var(--fr-text-danger);
+ line-height: 1.3;
+}
+
+.find-replace-meta-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 2px 4px;
+}
+
+.find-match-count {
+ font-size: 12px;
+ color: var(--text-secondary);
+}
+
+.find-nav-group {
+ display: flex;
+ gap: 4px;
+}
+
+.find-nav-arrow-btn {
+ background: none;
+ border: 1px solid var(--fr-border);
+ color: var(--text-color);
+ font-size: 12px;
+ cursor: pointer;
+ padding: 2px 8px;
+ border-radius: 4px;
+ transition: background-color 0.15s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.find-nav-arrow-btn:hover:not(:disabled) {
+ background-color: var(--button-hover);
+}
+
+.find-nav-arrow-btn:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
+.find-drawer-toggle-row {
+ border-top: 1px solid var(--fr-border);
+ margin-top: 4px;
+ padding-top: 6px;
+}
+
+.drawer-toggle-btn {
+ background: none;
+ border: none;
+ color: var(--text-secondary);
+ font-size: 12px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ padding: 2px 4px;
+ border-radius: 4px;
+ transition: color 0.15s ease;
+}
+
+.drawer-toggle-btn:hover {
+ color: var(--text-color);
+}
+
+.find-replace-drawer-content {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding: 4px 6px;
+ border-top: 1px dashed var(--fr-border);
+ margin-top: 2px;
+ padding-top: 8px;
+}
+
+.drawer-field {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.drawer-field.check-field {
+ flex-direction: row;
+ align-items: center;
+ gap: 6px;
+ padding: 2px 0;
+}
+
+.drawer-label {
+ font-size: 11px;
font-weight: 600;
- line-height: 1;
+ color: var(--text-secondary);
}
-.pdf-progress-track {
+.drawer-select {
width: 100%;
- height: 10px;
- background: var(--button-bg);
- border: 1px solid var(--border-color);
- border-radius: 999px;
- overflow: hidden;
+ padding: 4px 6px;
+ border-radius: 4px;
+ border: 1px solid var(--fr-border);
+ background-color: var(--bg-color);
+ color: var(--text-color);
+ font-size: 12px;
+ outline: none;
}
-.pdf-progress-fill {
- width: 0%;
- height: 100%;
- background: var(--accent-color);
- border-radius: inherit;
- transition: width 0.18s ease;
+.drawer-checkbox {
+ margin: 0;
+ cursor: pointer;
}
-.pdf-progress-details {
+.drawer-label-checkbox {
+ font-size: 12px;
+ color: var(--text-color);
+ cursor: pointer;
+}
+
+.find-replace-actions-footer {
display: flex;
- flex-direction: column;
gap: 6px;
+ padding: 8px 12px 12px 12px;
+ border-top: 1px solid var(--fr-border);
+ background-color: var(--header-bg);
+ border-bottom-left-radius: 11px;
+ border-bottom-right-radius: 11px;
+}
+
+.find-replace-panel.docked .find-replace-actions-footer {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.fr-action-btn {
+ flex: 1;
+ padding: 6px 8px;
font-size: 12px;
- color: var(--text-secondary);
+ font-weight: 500;
+ border-radius: 6px;
+ border: 1px solid var(--fr-border);
+ background-color: var(--button-bg);
+ color: var(--text-color);
+ cursor: pointer;
+ transition: background-color 0.15s ease, border-color 0.15s ease;
+ text-align: center;
}
-.pdf-progress-detail {
+.fr-action-btn:hover:not(:disabled) {
+ background-color: var(--button-hover);
+}
+
+.fr-action-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.fr-action-btn.secondary {
+ max-width: 60px;
+}
+
+/* ========================================
+ DIFF PREVIEW CONTAINER
+ ======================================== */
+.diff-preview-body {
+ padding: 16px;
display: flex;
- justify-content: space-between;
+ flex-direction: column;
gap: 12px;
}
-.pdf-progress-detail strong {
- color: var(--text-color);
- font-weight: 600;
+.diff-container {
+ border: 1px solid var(--fr-border);
+ border-radius: 8px;
+ background-color: var(--bg-color);
+ max-height: 400px;
+ overflow: auto;
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+ font-size: 12px;
+ line-height: 1.5;
}
-.pdf-progress-actions {
+.diff-line {
display: flex;
- justify-content: flex-end;
+ padding: 1px 8px;
}
-.tool-button.pdf-export-loading,
-.mobile-menu-item.pdf-export-loading {
+.diff-line.addition {
+ background-color: rgba(46, 160, 67, 0.15);
+ color: #3fb950;
+}
+
+.diff-line.deletion {
+ background-color: rgba(248, 81, 73, 0.15);
+ color: #f85149;
+}
+
+.diff-line.context {
+ color: var(--text-secondary);
+}
+
+.diff-line-num {
+ width: 40px;
+ flex-shrink: 0;
+ text-align: right;
+ padding-right: 12px;
+ border-right: 1px solid var(--fr-border);
+ user-select: none;
+ opacity: 0.5;
+}
+
+.diff-line-content {
+ padding-left: 12px;
+ white-space: pre-wrap;
+ word-break: break-all;
+}
+
+/* ========================================
+ DOCK LAYOUT CODES
+ ======================================== */
+.content-container {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+ position: relative;
+}
+
+.editor-dock-wrapper {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+ position: relative;
+}
+
+.editor-pane-inner {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ position: relative;
+ overflow: hidden;
+}
+
+/* ========================================
+ MOBILE & TABLET FIND PANEL RESPONSIVE FIXES
+ ======================================== */
+@media (max-width: 1079px) {
+ #find-replace-dock {
+ display: none !important;
+ }
+}
+
+@media (max-width: 768px) {
+ /* Prevent full screen expansion of floating panel on small mobile viewports */
+ .find-replace-panel {
+ width: calc(100% - 24px) !important;
+ right: 12px !important;
+ left: 12px !important;
+ top: 80px !important;
+ }
+}
+
+/* ========================================
+ SKELETON LOADING SHIMMER SYSTEM
+ ======================================== */
+.skeleton-placeholder {
+ display: block;
+ background-color: var(--skeleton-bg);
+ border-radius: 6px;
+ position: relative;
+ overflow: hidden;
+ /* PERF-017: Removed skeleton-pulse; shimmer-only is sufficient and halves GPU compositing layers */
+ /* animation: skeleton-pulse 2.2s cubic-bezier(0.4, 0, 0.2, 1) infinite alternate; */
+}
+
+.skeleton-placeholder::after {
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ transform: translateX(-100%);
+ background-image: linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0) 0%,
+ var(--skeleton-glow) 50%,
+ rgba(255, 255, 255, 0) 100%
+ );
+ animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
+}
+
+@keyframes skeleton-shimmer {
+ 100% {
+ transform: translateX(100%);
+ }
+}
+
+@keyframes skeleton-pulse {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.82;
+ }
+}
+
+.skeleton-circle {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ margin: 0 auto;
+}
+
+.skeleton-text {
+ height: 12px;
+ width: 80%;
+ margin: 4px auto;
+ border-radius: 3px;
+}
+
+.skeleton-tree-folder {
+ height: 16px;
+ width: 140px;
+ margin: 6px 0;
+ display: inline-block;
+}
+
+.skeleton-tree-file {
+ height: 14px;
+ width: 180px;
+ margin: 4px 0 4px 12px;
+ display: inline-block;
+}
+
+/* Screen reader accessibility utility */
+.visually-hidden {
+ position: absolute !important;
+ width: 1px !important;
+ height: 1px !important;
+ padding: 0 !important;
+ margin: -1px !important;
+ overflow: hidden !important;
+ clip: rect(0, 0, 0, 0) !important;
+ clip-path: inset(50%) !important;
+ white-space: nowrap !important;
+ border: 0 !important;
+}
+
+/* Article skeleton layout structures */
+.skeleton-title {
+ height: 28px;
+ width: 35%;
+ margin-bottom: 24px;
+ border-radius: 8px;
+}
+
+.skeleton-subtitle {
+ height: 20px;
+ width: 20%;
+ margin-bottom: 18px;
+ margin-top: 32px;
+ border-radius: 6px;
+}
+
+.skeleton-line {
+ height: 14px;
+ margin-bottom: 12px;
+ border-radius: 6px;
+}
+
+/* Symmetrical dynamic widths */
+.skeleton-w90 { width: 90%; }
+.skeleton-w92 { width: 92%; }
+.skeleton-w88 { width: 88%; }
+.skeleton-w85 { width: 85%; }
+.skeleton-w60 { width: 60%; }
+.skeleton-w45 { width: 45%; }
+
+/* Editor pane skeleton overlay */
+.editor-skeleton {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 30px 24px 24px calc(24px + var(--line-number-gutter));
+ z-index: 10;
pointer-events: none;
+ background: var(--editor-bg);
+ box-sizing: border-box;
+ overflow: hidden;
+ transition: opacity 0.3s ease;
+}
+
+.editor-pane:not(.is-loading) .editor-skeleton {
+ display: none;
+}
+
+.editor-pane.is-loading textarea {
+ opacity: 0; /* Completely hide editor content while initial bootstrap skeleton runs */
+}
+
+/* Preview pane skeleton container */
+.skeleton-preview-container {
+ display: block;
+ width: 100%;
+ box-sizing: border-box;
+ padding: 10px 4px;
+ background: transparent;
+ transition: opacity 0.3s ease;
+}
+
+/* Mermaid compilation loading states */
+.mermaid-container.is-loading {
+ min-height: 180px;
+ background-color: var(--skeleton-bg);
+ border-radius: 8px;
+ border: 1px solid var(--border-color);
+ position: relative;
+ overflow: hidden;
+ animation: skeleton-pulse 2.2s cubic-bezier(0.4, 0, 0.2, 1) infinite alternate;
+}
+
+.mermaid-container.is-loading .mermaid {
+ opacity: 0; /* Hide raw chart source code during compile */
+}
+
+.mermaid-container.is-loading::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ transform: translateX(-100%);
+ background-image: linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0) 0%,
+ var(--skeleton-glow) 50%,
+ rgba(255, 255, 255, 0) 100%
+ );
+ animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
+}
+
+/* Accessibility: respect user's motion preferences */
+@media (prefers-reduced-motion: reduce) {
+ .skeleton-placeholder,
+ .skeleton-placeholder::after,
+ .mermaid-container.is-loading,
+ .mermaid-container.is-loading::after {
+ animation: none;
+ }
+ .drag-overlay-inner {
+ animation: none;
+ }
+ .tab-item-new {
+ animation: none;
+ }
+ body,
+ .app-header,
+ .editor-pane,
+ .preview-pane,
+ .tool-button,
+ .markdown-tool-btn {
+ transition: none;
+ }
}
-/* ========================================
- RESET MODAL FORM FIELDS
- ======================================== */
-
-.reset-modal-field {
- display: flex;
- flex-direction: column;
- gap: 6px;
- text-align: left;
-}
-
-.reset-modal-field-group {
- display: flex;
- flex-direction: column;
- gap: 12px;
-}
-
-.reset-modal-label {
- font-size: 12px;
- color: var(--text-secondary, #57606a);
- font-weight: 600;
-}
-
-.reset-modal-toggle-group {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.reset-modal-option {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 13px;
- color: var(--text-color);
-}
-
-.reset-modal-option input {
- margin: 0;
-}
-
-/* ========================================
- RENAME MODAL INPUT
- ======================================== */
-
-.rename-modal-input {
- width: 100%;
- padding: 7px 10px;
- border-radius: 6px;
- border: 1px solid var(--border-color);
- background: var(--bg-color);
- color: var(--text-color);
- font-size: 13px;
- outline: none;
- box-sizing: border-box;
-}
-
-.rename-modal-input:focus {
- border-color: var(--accent-color);
-}
-
-/* ========================================
- TOOLBAR POPUP PANELS
- ======================================== */
-
-.reset-modal-box--xl {
- width: min(94vw, 980px);
- max-width: 980px;
-}
-
-.modal-empty {
- margin: 0;
- font-size: 12px;
- color: var(--text-secondary, #57606a);
- text-align: center;
-}
-
-.emoji-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
- gap: 12px;
- max-height: min(52vh, 440px);
- overflow: auto;
- padding: 4px;
-}
-
-.symbol-grid {
- display: flex;
- flex-direction: column;
- gap: 16px;
- max-height: min(52vh, 440px);
- overflow: auto;
- padding: 4px 2px;
-}
-
-.symbol-section-title {
- margin: 0;
- font-size: 11px;
- text-transform: uppercase;
- letter-spacing: 0.08em;
- color: var(--text-secondary, #57606a);
-}
-
-.symbol-section-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
- gap: 12px;
-}
-
-.alert-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
- gap: 12px;
- max-height: min(45vh, 360px);
- overflow: auto;
- padding: 2px;
-}
-
-.emoji-item,
-.symbol-item,
-.alert-option {
- display: flex;
- flex-direction: column;
- gap: 8px;
- align-items: center;
- border: 1px solid var(--border-color);
- border-radius: 10px;
- padding: 10px;
- background: var(--bg-color);
- color: var(--text-color);
- cursor: pointer;
- transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
-}
-
-.emoji-item:focus-visible,
-.symbol-item:focus-visible,
-.alert-option:focus-visible {
- outline: 2px solid var(--accent-color);
- outline-offset: 2px;
-}
-
-.emoji-item.is-selected,
-.symbol-item.is-selected,
-.alert-option.is-selected {
- border-color: var(--accent-color);
- box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2);
- background-color: rgba(88, 166, 255, 0.08);
-}
-
-.emoji-preview {
- width: 36px;
- height: 36px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.emoji-preview img {
- width: 32px;
- height: 32px;
-}
-
-.emoji-shortcode {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 12px;
- color: var(--text-secondary, #57606a);
- text-align: center;
-}
-
-.emoji-copy-btn,
-.symbol-copy-btn {
- border: none;
- background: transparent;
- color: var(--text-secondary, #57606a);
- cursor: pointer;
- padding: 2px;
- border-radius: 4px;
-}
-
-.emoji-copy-btn:hover,
-.symbol-copy-btn:hover {
- color: var(--text-color);
- background: var(--button-hover);
-}
-
-.emoji-copy-btn.is-copied,
-.symbol-copy-btn.is-copied {
- color: var(--accent-color);
-}
-
-.symbol-preview {
- font-size: 28px;
- line-height: 1;
-}
-
-.symbol-code {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 12px;
- color: var(--text-secondary, #57606a);
-}
-
-.alert-option {
- align-items: stretch;
- text-align: left;
- padding: 12px;
-}
-
-.alert-preview {
- margin: 0;
-}
-
-.alert-preview .markdown-alert {
- padding: 0.5rem 0.9rem;
- border-left: 0.25em solid;
- border-radius: 0.375rem;
-}
-
-.alert-preview .markdown-alert-title {
- margin: 0 0 6px;
- font-weight: 600;
- line-height: 1.25;
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.alert-preview .markdown-alert-icon {
- display: inline-flex;
- width: 16px;
- height: 16px;
-}
-
-.alert-preview .markdown-alert-icon svg {
- width: 16px;
- height: 16px;
- fill: currentColor;
-}
-
-.alert-preview .markdown-alert > *:not(.markdown-alert-title) {
- color: var(--text-color);
-}
-
-.alert-preview .markdown-alert-note {
- color: #0969da;
- border-left-color: #0969da;
- background-color: #ddf4ff;
-}
-
-.alert-preview .markdown-alert-tip {
- color: #1a7f37;
- border-left-color: #1a7f37;
- background-color: #dafbe1;
-}
-
-.alert-preview .markdown-alert-important {
- color: #8250df;
- border-left-color: #8250df;
- background-color: #fbefff;
-}
-
-.alert-preview .markdown-alert-warning {
- color: #9a6700;
- border-left-color: #9a6700;
- background-color: #fff8c5;
-}
-
-.alert-preview .markdown-alert-caution {
- color: #cf222e;
- border-left-color: #cf222e;
- background-color: #ffebe9;
-}
-
-[data-theme="dark"] .alert-preview .markdown-alert-note {
- color: #4493f8;
- border-left-color: #4493f8;
- background-color: rgba(31, 111, 235, 0.15);
-}
-
-[data-theme="dark"] .alert-preview .markdown-alert-tip {
- color: #3fb950;
- border-left-color: #3fb950;
- background-color: rgba(35, 134, 54, 0.15);
-}
-
-[data-theme="dark"] .alert-preview .markdown-alert-important {
- color: #ab7df8;
- border-left-color: #ab7df8;
- background-color: rgba(137, 87, 229, 0.15);
-}
-
-[data-theme="dark"] .alert-preview .markdown-alert-warning {
- color: #d29922;
- border-left-color: #d29922;
- background-color: rgba(210, 153, 34, 0.18);
-}
-
-[data-theme="dark"] .alert-preview .markdown-alert-caution {
- color: #f85149;
- border-left-color: #f85149;
- background-color: rgba(248, 81, 73, 0.18);
-}
-
-.github-import-error {
- margin: 0;
- font-size: 12px;
- color: var(--color-danger-fg, #d73a49);
- text-align: left;
- line-height: 1.5;
-}
-
-.github-import-error.is-info {
- color: var(--text-secondary, #57606a);
-}
-
-#github-import-modal .reset-modal-box {
- width: 60vw;
- max-width: 60vw;
- min-width: 340px;
- padding: 30px 34px;
- gap: 16px;
- box-shadow: 0 20px 48px rgba(0, 0, 0, 0.22);
-}
-
-#github-import-modal .reset-modal-message {
- font-size: 18px;
- line-height: 1.35;
- text-align: left;
-}
-
-#github-import-url,
-#github-import-file-select {
- min-height: 46px;
- padding: 10px 12px;
- font-size: 15px;
-}
-
-#github-import-file-select {
- min-height: 180px;
-}
-
-.github-import-tree {
- max-height: 420px;
- overflow: auto;
- border: 1px solid var(--border-color);
- border-radius: 10px;
- padding: 12px;
- background: var(--bg-color);
-}
-
-.github-import-selection-toolbar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- padding: 10px 12px;
- border: 1px solid var(--border-color);
- border-radius: 8px;
- background: var(--button-bg);
-}
-
-.github-import-selected-count {
- font-size: 14px;
- font-weight: 600;
- color: var(--text-color);
-}
-
-.github-import-tree ul {
- list-style: none;
- margin: 0;
- padding-left: 18px;
-}
-
-.github-import-tree > ul {
- padding-left: 4px;
-}
-
-.github-import-tree li {
- margin: 2px 0;
-}
-
-.github-tree-folder-label {
- display: inline-block;
- font-size: 14px;
- color: var(--text-secondary, #57606a);
- margin-bottom: 4px;
-}
-
-.github-tree-file-btn {
- border: 0;
- background: transparent;
- color: var(--text-color);
- cursor: pointer;
- padding: 6px 8px;
- border-radius: 6px;
- text-align: left;
- width: 100%;
- font-size: 14px;
-}
-
-.github-tree-file-btn:hover,
-.github-tree-file-btn:focus-visible {
- background: var(--button-hover);
- outline: none;
-}
-
-.github-tree-file-btn.is-selected {
- background: rgba(56, 139, 253, 0.14);
- color: var(--accent-color);
-}
-
-#github-import-modal .reset-modal-actions {
- gap: 12px;
-}
-
-#github-import-modal .reset-modal-btn {
- min-height: 42px;
- padding: 9px 18px;
- font-size: 14px;
-}
-
-@media (max-width: 576px) {
- #github-import-modal .reset-modal-box {
- width: 95vw;
- max-width: 95vw;
- min-width: 0;
- padding: 20px;
- gap: 14px;
- }
-
- .github-import-selection-toolbar {
- flex-direction: column;
- align-items: stretch;
- }
-
- #github-import-modal .reset-modal-message {
- font-size: 16px;
- }
-
- #github-import-modal .reset-modal-actions {
- flex-direction: column-reverse;
- }
-
- #github-import-modal .reset-modal-btn {
- width: 100%;
- }
-}
-
-.frontmatter-table {
- border-collapse: collapse;
- margin-bottom: 1.5em;
- font-size: 0.9em;
- width: auto;
- max-width: 100%;
-}
-
-.frontmatter-table th,
-.frontmatter-table td {
- border: 1px solid var(--border-color);
- padding: 6px 13px;
- vertical-align: top;
- color: var(--text-color);
-}
-
-.frontmatter-table tr:nth-child(odd) th,
-.frontmatter-table tr:nth-child(odd) td {
- background-color: var(--table-bg);
-}
-
-.frontmatter-table tr:nth-child(even) th,
-.frontmatter-table tr:nth-child(even) td {
- background-color: var(--editor-bg);
-}
-
-.frontmatter-table th {
- font-weight: 600;
- text-align: right;
- white-space: nowrap;
- vertical-align: middle;
-}
-
-.frontmatter-table td {
- text-align: left;
-}
-
-.fm-complex {
- margin: 0;
- padding: 4px 6px;
- font-size: 0.8em;
- font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
- white-space: pre-wrap;
- word-break: break-word;
- background: transparent;
- border: none;
- color: var(--text-color);
-}
-
-.fm-tag {
- display: inline-block;
- padding: 2px 8px;
- margin: 2px 3px 2px 0;
- border: 1px solid var(--border-color);
- border-radius: 2em;
- font-size: 0.8em;
- font-weight: 500;
- color: var(--accent-color);
- background-color: var(--button-bg);
- white-space: nowrap;
-}
-
-/* ========================================
- RTL SUPPORT
- ======================================== */
-
-[dir="rtl"] body {
- direction: rtl;
-}
-
-[dir="rtl"] .editor-pane {
- padding-left: 0px;
- padding-right: 20px;
- border-right: none;
- border-left: 1px solid var(--border-color);
-}
-
-[dir="rtl"] #markdown-editor,
-[dir="rtl"] .markdown-body {
- direction: rtl;
- text-align: right;
-}
-
-[dir="rtl"] .markdown-body pre,
-[dir="rtl"] .markdown-body code,
-[dir="rtl"] .fm-complex {
- direction: ltr;
- text-align: left;
-}
-
-[dir="rtl"] .line-numbers {
- left: auto;
- right: 20px;
- padding: 10px 0 10px 8px;
- text-align: left;
- border-right: none;
- border-left: 1px solid var(--border-color);
-}
-
-[dir="rtl"] #markdown-editor {
- padding-left: 10px;
- padding-right: calc(10px + var(--line-number-gutter));
-}
-
-[dir="rtl"] .editor-highlight-layer {
- inset: 20px calc(20px + var(--line-number-gutter)) 20px 0;
-}
-
-[dir="rtl"] .mobile-menu-item,
-[dir="rtl"] .tab-menu-item,
-[dir="rtl"] .modal-header .reset-modal-message,
-[dir="rtl"] .reset-modal-field,
-[dir="rtl"] .alert-option,
-[dir="rtl"] .github-import-error,
-[dir="rtl"] #github-import-modal .reset-modal-message,
-[dir="rtl"] .github-tree-file-btn,
-[dir="rtl"] .frontmatter-table td {
- text-align: right;
-}
-
-[dir="rtl"] .github-import-tree ul {
- padding-left: 0;
- padding-right: 18px;
-}
-
-[dir="rtl"] .github-import-tree > ul {
- padding-right: 4px;
-}
-
-[dir="rtl"] .markdown-body .markdown-alert,
-[dir="rtl"] .alert-preview .markdown-alert {
- border-left: 0;
- border-right: 0.25em solid currentColor;
-}
-
-/* ============================================
- SHARE MODAL
- ============================================ */
-
-.share-modal-description {
- font-size: 13px;
- color: var(--text-secondary, #57606a);
- margin: 0;
-}
-
-.share-mode-cards {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.share-mode-card {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 12px 14px;
- border-radius: 8px;
- border: 1px solid var(--border-color);
- background: var(--bg-color);
- cursor: pointer;
- transition: border-color 0.15s ease, background-color 0.15s ease;
- user-select: none;
-}
-
-.share-mode-card:hover {
- border-color: var(--accent-color);
- background: var(--button-hover);
-}
-
-.share-mode-card.is-selected {
- border-color: var(--accent-color);
- background: color-mix(in srgb, var(--accent-color) 8%, transparent);
-}
-
-.share-mode-card input[type="radio"] {
- display: none;
-}
-
-.share-card-icon {
- font-size: 18px;
- width: 28px;
- text-align: center;
- color: var(--text-secondary, #57606a);
- flex-shrink: 0;
-}
-
-.share-mode-card.is-selected .share-card-icon {
- color: var(--accent-color);
-}
-
-.share-card-body {
- display: flex;
- flex-direction: column;
- gap: 2px;
- flex: 1;
- min-width: 0;
-}
-
-.share-card-title {
- font-size: 13px;
- font-weight: 600;
- color: var(--text-color);
-}
-
-.share-card-desc {
- font-size: 12px;
- color: var(--text-secondary, #57606a);
-}
-
-.share-card-check {
- width: 18px;
- text-align: center;
- color: var(--accent-color);
- opacity: 0;
- transition: opacity 0.15s ease;
- flex-shrink: 0;
-}
-
-.share-mode-card.is-selected .share-card-check {
- opacity: 1;
-}
-
-.share-url-row {
- display: flex;
- gap: 8px;
- align-items: center;
-}
-
-.share-url-input {
- flex: 1;
- font-size: 12px;
- font-family: var(--font-mono, monospace);
- color: var(--text-secondary, #57606a);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.share-copy-btn {
- flex-shrink: 0;
- padding: 6px 10px;
-}
-
-.share-copy-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.share-modal-notice {
- font-size: 11px;
- color: var(--text-secondary, #57606a);
- margin: 0;
- display: flex;
- align-items: center;
- gap: 5px;
-}
-
-/* ==========================================================================
- Multilingual & CJK Optimization styles added by Aegis SEO agency
- ========================================================================== */
-.lang-select-item {
- display: flex !important;
- align-items: center;
- gap: 8px;
- cursor: pointer;
- transition: background-color 0.2s ease, padding-left 0.2s ease;
-}
-
-.lang-select-item:hover {
- padding-left: 12px;
-}
-
-.lang-select-item.active {
- background-color: var(--accent-color) !important;
- color: #ffffff !important;
- font-weight: 600;
-}
-
-/* Adjust CJK text layout for maximum readability inside the preview pane */
-html[lang="zh"] .markdown-body,
-html[lang="ja"] .markdown-body,
-html[lang="ko"] .markdown-body {
- line-height: 1.75 !important;
- letter-spacing: 0.03em;
- word-break: keep-all;
- overflow-wrap: break-word;
- text-align: justify;
-}
-
-/* Specific heading spacing improvements for CJK characters */
-html[lang="zh"] .markdown-body h1, html[lang="zh"] .markdown-body h2, html[lang="zh"] .markdown-body h3,
-html[lang="ja"] .markdown-body h1, html[lang="ja"] .markdown-body h2, html[lang="ja"] .markdown-body h3,
-html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang="ko"] .markdown-body h3 {
- font-weight: 700;
- letter-spacing: 0.02em;
- margin-top: 1.4em;
- margin-bottom: 0.6em;
-}
-
-/* Smooth fade and scale transition for dropdown active states */
-.dropdown-menu {
- opacity: 0;
- transform: translateY(8px) scale(0.98);
- display: block;
- visibility: hidden;
- transition: opacity 0.2s cubic-bezier(0.16, 1, 0.3, 1), transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), visibility 0.2s;
-}
-
-.dropdown-menu.show {
- opacity: 1;
- transform: translateY(0) scale(1);
- visibility: visible;
-}
-
-/* ========================================
- FIND & REPLACE FLOATING PANEL DESIGN
- ======================================== */
-
-.find-replace-panel {
- position: fixed;
- top: 100px;
- right: 20px;
- width: 340px;
- background-color: var(--fr-bg);
- border: 1px solid var(--fr-border);
- border-radius: 12px;
- box-shadow: var(--fr-shadow);
- z-index: 1050;
- display: flex;
- flex-direction: column;
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- transition: opacity 0.2s ease, transform 0.2s ease;
- user-select: none;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
-}
-
-.find-replace-panel.docked {
- position: relative;
- top: 0 !important;
- right: 0 !important;
- height: 100%;
- border-radius: 0;
- border-top: none;
- border-bottom: none;
- border-right: none;
- border-left: 1px solid var(--fr-border);
- box-shadow: none;
- backdrop-filter: none;
- z-index: 10;
- flex-shrink: 0;
-}
-
-.find-replace-panel.docked #find-replace-reset,
-.find-replace-panel.docked #find-replace-reset-footer {
- display: none !important;
-}
-
-.find-replace-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 8px 12px;
- border-bottom: 1px solid var(--fr-border);
- cursor: move;
- background-color: var(--header-bg);
- border-top-left-radius: 11px;
- border-top-right-radius: 11px;
-}
-
-.find-replace-panel.docked .find-replace-header {
- cursor: default;
- border-top-left-radius: 0;
- border-top-right-radius: 0;
-}
-
-.find-replace-title {
- font-size: 13px;
- font-weight: 600;
- color: var(--text-color);
-}
-
-.find-replace-header-actions {
- display: flex;
- gap: 4px;
-}
-
-.panel-icon-btn {
- background: none;
- border: none;
- color: var(--text-color);
- font-size: 12px;
- cursor: pointer;
- padding: 2px 6px;
- border-radius: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: background-color 0.15s ease;
-}
-
-.panel-icon-btn:hover {
- background-color: var(--button-hover);
-}
-
-.find-replace-body {
- padding: 12px;
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.find-replace-field-row {
- display: flex;
- flex-direction: column;
- position: relative;
-}
-
-.find-input-container, .replace-input-container {
- display: flex;
- align-items: center;
- border: 1px solid var(--fr-border);
- border-radius: 6px;
- background-color: var(--bg-color);
- padding: 2px 4px;
- width: 100%;
-}
-
-.find-input-container:focus-within, .replace-input-container:focus-within {
- border-color: var(--accent-color);
- box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.15);
-}
-
-.find-input-field {
- flex: 1;
- border: none;
- background: transparent;
- color: var(--text-color);
- font-size: 13px;
- padding: 4px 6px;
- outline: none;
- width: 50%;
-}
-
-.find-options-group {
- display: flex;
- gap: 2px;
-}
-
-.find-option-btn {
- background: none;
- border: none;
- color: var(--text-secondary);
- font-size: 11px;
- font-weight: 600;
- cursor: pointer;
- padding: 2px 5px;
- border-radius: 4px;
- transition: background-color 0.12s ease, color 0.12s ease;
- min-width: 22px;
- height: 22px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
-}
-
-.find-option-btn:hover {
- background-color: var(--button-hover);
- color: var(--text-color);
-}
-
-.find-option-btn.active {
- background-color: var(--fr-btn-active-bg);
- color: var(--fr-btn-active);
-}
-
-.find-error-drawer {
- background-color: var(--fr-error-bg);
- border: 1px solid var(--fr-error-border);
- border-radius: 6px;
- padding: 6px 10px;
- font-size: 11px;
- color: var(--fr-text-danger);
- line-height: 1.3;
-}
-
-.find-replace-meta-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 2px 4px;
-}
-
-.find-match-count {
- font-size: 12px;
- color: var(--text-secondary);
-}
-
-.find-nav-group {
- display: flex;
- gap: 4px;
-}
-
-.find-nav-arrow-btn {
- background: none;
- border: 1px solid var(--fr-border);
- color: var(--text-color);
- font-size: 12px;
- cursor: pointer;
- padding: 2px 8px;
- border-radius: 4px;
- transition: background-color 0.15s ease;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.find-nav-arrow-btn:hover:not(:disabled) {
- background-color: var(--button-hover);
-}
-
-.find-nav-arrow-btn:disabled {
- opacity: 0.4;
- cursor: not-allowed;
-}
-
-.find-drawer-toggle-row {
- border-top: 1px solid var(--fr-border);
- margin-top: 4px;
- padding-top: 6px;
-}
-
-.drawer-toggle-btn {
- background: none;
- border: none;
- color: var(--text-secondary);
- font-size: 12px;
- cursor: pointer;
- display: flex;
- align-items: center;
- padding: 2px 4px;
- border-radius: 4px;
- transition: color 0.15s ease;
-}
-
-.drawer-toggle-btn:hover {
- color: var(--text-color);
-}
-
-.find-replace-drawer-content {
- display: flex;
- flex-direction: column;
- gap: 8px;
- padding: 4px 6px;
- border-top: 1px dashed var(--fr-border);
- margin-top: 2px;
- padding-top: 8px;
-}
-
-.drawer-field {
- display: flex;
- flex-direction: column;
- gap: 4px;
-}
-
-.drawer-field.check-field {
- flex-direction: row;
- align-items: center;
- gap: 6px;
- padding: 2px 0;
-}
-
-.drawer-label {
- font-size: 11px;
- font-weight: 600;
- color: var(--text-secondary);
-}
-
-.drawer-select {
- width: 100%;
- padding: 4px 6px;
- border-radius: 4px;
- border: 1px solid var(--fr-border);
- background-color: var(--bg-color);
- color: var(--text-color);
- font-size: 12px;
- outline: none;
-}
-
-.drawer-checkbox {
- margin: 0;
- cursor: pointer;
-}
-
-.drawer-label-checkbox {
- font-size: 12px;
- color: var(--text-color);
- cursor: pointer;
-}
-
-.find-replace-actions-footer {
- display: flex;
- gap: 6px;
- padding: 8px 12px 12px 12px;
- border-top: 1px solid var(--fr-border);
- background-color: var(--header-bg);
- border-bottom-left-radius: 11px;
- border-bottom-right-radius: 11px;
-}
-
-.find-replace-panel.docked .find-replace-actions-footer {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
-}
-
-.fr-action-btn {
- flex: 1;
- padding: 6px 8px;
- font-size: 12px;
- font-weight: 500;
- border-radius: 6px;
- border: 1px solid var(--fr-border);
- background-color: var(--button-bg);
- color: var(--text-color);
- cursor: pointer;
- transition: background-color 0.15s ease, border-color 0.15s ease;
- text-align: center;
-}
-
-.fr-action-btn:hover:not(:disabled) {
- background-color: var(--button-hover);
-}
-
-.fr-action-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.fr-action-btn.secondary {
- max-width: 60px;
-}
-
-/* ========================================
- DIFF PREVIEW CONTAINER
- ======================================== */
-.diff-preview-body {
- padding: 16px;
- display: flex;
- flex-direction: column;
- gap: 12px;
-}
-
-.diff-container {
- border: 1px solid var(--fr-border);
- border-radius: 8px;
- background-color: var(--bg-color);
- max-height: 400px;
- overflow: auto;
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
- font-size: 12px;
- line-height: 1.5;
-}
-
-.diff-line {
- display: flex;
- padding: 1px 8px;
-}
-
-.diff-line.addition {
- background-color: rgba(46, 160, 67, 0.15);
- color: #3fb950;
-}
-
-.diff-line.deletion {
- background-color: rgba(248, 81, 73, 0.15);
- color: #f85149;
-}
-
-.diff-line.context {
- color: var(--text-secondary);
-}
-
-.diff-line-num {
- width: 40px;
- flex-shrink: 0;
- text-align: right;
- padding-right: 12px;
- border-right: 1px solid var(--fr-border);
- user-select: none;
- opacity: 0.5;
-}
-
-.diff-line-content {
- padding-left: 12px;
- white-space: pre-wrap;
- word-break: break-all;
-}
-
-/* ========================================
- DOCK LAYOUT CODES
- ======================================== */
-.content-container {
- display: flex;
- flex: 1;
- overflow: hidden;
- position: relative;
-}
-
-.editor-dock-wrapper {
- display: flex;
- flex: 1;
- overflow: hidden;
- position: relative;
-}
-
-.editor-pane-inner {
- display: flex;
- flex-direction: column;
- flex: 1;
- position: relative;
- overflow: hidden;
-}
-
-/* ========================================
- MOBILE & TABLET FIND PANEL RESPONSIVE FIXES
- ======================================== */
-@media (max-width: 1079px) {
- #find-replace-dock {
- display: none !important;
- }
-}
-
-@media (max-width: 768px) {
- /* Prevent full screen expansion of floating panel on small mobile viewports */
- .find-replace-panel {
- width: calc(100% - 24px) !important;
- right: 12px !important;
- left: 12px !important;
- top: 80px !important;
- }
-}
-
-/* ========================================
- SKELETON LOADING SHIMMER SYSTEM
- ======================================== */
-.skeleton-placeholder {
- display: block;
- background-color: var(--skeleton-bg);
- border-radius: 6px;
- position: relative;
- overflow: hidden;
- /* PERF-017: Removed skeleton-pulse; shimmer-only is sufficient and halves GPU compositing layers */
- /* animation: skeleton-pulse 2.2s cubic-bezier(0.4, 0, 0.2, 1) infinite alternate; */
-}
-
-.skeleton-placeholder::after {
- content: "";
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- transform: translateX(-100%);
- background-image: linear-gradient(
- 90deg,
- rgba(255, 255, 255, 0) 0%,
- var(--skeleton-glow) 50%,
- rgba(255, 255, 255, 0) 100%
- );
- animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
-}
-
-@keyframes skeleton-shimmer {
- 100% {
- transform: translateX(100%);
- }
-}
-
-@keyframes skeleton-pulse {
- 0%, 100% {
- opacity: 1;
- }
- 50% {
- opacity: 0.82;
- }
-}
-
-.skeleton-circle {
- width: 32px;
- height: 32px;
- border-radius: 50%;
- margin: 0 auto;
-}
-
-.skeleton-text {
- height: 12px;
- width: 80%;
- margin: 4px auto;
- border-radius: 3px;
-}
-
-.skeleton-tree-folder {
- height: 16px;
- width: 140px;
- margin: 6px 0;
- display: inline-block;
-}
-
-.skeleton-tree-file {
- height: 14px;
- width: 180px;
- margin: 4px 0 4px 12px;
- display: inline-block;
-}
-
-/* Screen reader accessibility utility */
-.visually-hidden {
- position: absolute !important;
- width: 1px !important;
- height: 1px !important;
- padding: 0 !important;
- margin: -1px !important;
- overflow: hidden !important;
- clip: rect(0, 0, 0, 0) !important;
- clip-path: inset(50%) !important;
- white-space: nowrap !important;
- border: 0 !important;
-}
-
-/* Article skeleton layout structures */
-.skeleton-title {
- height: 28px;
- width: 35%;
- margin-bottom: 24px;
- border-radius: 8px;
-}
-
-.skeleton-subtitle {
- height: 20px;
- width: 20%;
- margin-bottom: 18px;
- margin-top: 32px;
- border-radius: 6px;
-}
-
-.skeleton-line {
- height: 14px;
- margin-bottom: 12px;
- border-radius: 6px;
-}
-
-/* Symmetrical dynamic widths */
-.skeleton-w90 { width: 90%; }
-.skeleton-w92 { width: 92%; }
-.skeleton-w88 { width: 88%; }
-.skeleton-w85 { width: 85%; }
-.skeleton-w60 { width: 60%; }
-.skeleton-w45 { width: 45%; }
-
-/* Editor pane skeleton overlay */
-.editor-skeleton {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- padding: 30px 24px 24px calc(24px + var(--line-number-gutter));
- z-index: 10;
- pointer-events: none;
- background: var(--editor-bg);
- box-sizing: border-box;
- overflow: hidden;
- transition: opacity 0.3s ease;
-}
-
-.editor-pane:not(.is-loading) .editor-skeleton {
- display: none;
-}
-
-.editor-pane.is-loading textarea {
- opacity: 0; /* Completely hide editor content while initial bootstrap skeleton runs */
-}
-
-/* Preview pane skeleton container */
-.skeleton-preview-container {
- display: block;
- width: 100%;
- box-sizing: border-box;
- padding: 10px 4px;
- background: transparent;
- transition: opacity 0.3s ease;
-}
-
-/* Mermaid compilation loading states */
-.mermaid-container.is-loading {
- min-height: 180px;
- background-color: var(--skeleton-bg);
- border-radius: 8px;
- border: 1px solid var(--border-color);
- position: relative;
- overflow: hidden;
- animation: skeleton-pulse 2.2s cubic-bezier(0.4, 0, 0.2, 1) infinite alternate;
-}
-
-.mermaid-container.is-loading .mermaid {
- opacity: 0; /* Hide raw chart source code during compile */
-}
-
-.mermaid-container.is-loading::after {
- content: "";
- position: absolute;
- inset: 0;
- transform: translateX(-100%);
- background-image: linear-gradient(
- 90deg,
- rgba(255, 255, 255, 0) 0%,
- var(--skeleton-glow) 50%,
- rgba(255, 255, 255, 0) 100%
- );
- animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
-}
-
-/* Accessibility: respect user's motion preferences */
-@media (prefers-reduced-motion: reduce) {
- .skeleton-placeholder,
- .skeleton-placeholder::after,
- .mermaid-container.is-loading,
- .mermaid-container.is-loading::after {
- animation: none;
- }
- .drag-overlay-inner {
- animation: none;
- }
- .tab-item-new {
- animation: none;
- }
- body,
- .app-header,
- .editor-pane,
- .preview-pane,
- .tool-button,
- .markdown-tool-btn {
- transition: none;
- }
-}
-
\ No newline at end of file
+
+/* ========================================
+ ENTERPRISE PRINT & PDF LAYOUT
+ ======================================== */
+@media print {
+ @page {
+ size: A4;
+ margin: 15mm;
+ }
+
+ body {
+ background-color: #ffffff !important;
+ color: #24292e !important;
+ font-size: 12pt;
+ line-height: 1.5;
+ }
+
+ /* Hide UI elements during print */
+ .app-container,
+ .toolbar,
+ .mobile-menu,
+ .stats-container,
+ .resize-divider,
+ .pdf-progress-overlay,
+ .modal,
+ .toast,
+ .navbar,
+ .btn {
+ display: none !important;
+ }
+
+ /* Show and fit print content */
+ .pdf-export {
+ position: static !important;
+ left: 0 !important;
+ top: 0 !important;
+ width: 100% !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ background-color: transparent !important;
+ color: inherit !important;
+ display: block !important;
+ box-shadow: none !important;
+ }
+
+ /* Page breaks & fragmentation */
+ h1, h2, h3, h4, h5, h6 {
+ break-after: avoid-page;
+ page-break-after: avoid;
+ break-inside: avoid;
+ }
+
+ p, li, dd, dt, blockquote {
+ orphans: 3;
+ widows: 3;
+ }
+
+ img, figure, figcaption,
+ .mermaid-container, .mermaid, .mermaid svg,
+ .markdown-alert, .alert, blockquote {
+ break-inside: avoid;
+ page-break-inside: avoid;
+ }
+
+ /* Short code blocks should not split; allow long ones to split */
+ pre {
+ break-inside: auto;
+ page-break-inside: auto;
+ white-space: pre-wrap !important;
+ word-break: break-all;
+ }
+
+ pre:not(.oversized) {
+ break-inside: avoid;
+ page-break-inside: avoid;
+ }
+
+ pre code {
+ white-space: pre-wrap !important;
+ word-break: break-all;
+ }
+
+ /* Tables spanning pages and repeating headers */
+ table {
+ break-inside: auto;
+ page-break-inside: auto;
+ width: 100% !important;
+ border-collapse: collapse;
+ }
+
+ thead {
+ display: table-header-group;
+ break-after: avoid-page;
+ page-break-after: avoid;
+ }
+
+ tr {
+ break-inside: avoid;
+ page-break-inside: avoid;
+ }
+
+ /* Scale oversized assets to fit page height/width */
+ img, svg, .mermaid-container, .mermaid svg {
+ max-width: 100% !important;
+ max-height: calc(100vh - 30mm) !important;
+ height: auto !important;
+ object-fit: contain !important;
+ }
+
+ /* Force background colors to print */
+ * {
+ -webkit-print-color-adjust: exact !important;
+ print-color-adjust: exact !important;
+ }
+}
\ No newline at end of file
diff --git a/test-pdf-export.js b/test-pdf-export.js
new file mode 100644
index 0000000..f5e8303
--- /dev/null
+++ b/test-pdf-export.js
@@ -0,0 +1,93 @@
+/**
+ * test-pdf-export.js
+ * Verification test suite for the Enterprise PDF Export Engine.
+ * Runs in Node.js to check architectural integrity and compliance.
+ */
+
+const fs = require("fs");
+const path = require("path");
+
+const SCRIPT_PATH = path.join(__dirname, "script.js");
+const CSS_PATH = path.join(__dirname, "styles.css");
+const WORKER_PATH = path.join(__dirname, "preview-worker.js");
+const CONFIG_PATH = path.join(__dirname, "desktop-app", "neutralino.config.json");
+const SIDECAR_PATH = path.join(__dirname, "desktop-app", "extensions", "pdf-exporter", "index.js");
+
+let passed = 0;
+let failed = 0;
+
+function assert(condition, message) {
+ if (condition) {
+ console.log(`[PASS] ${message}`);
+ passed++;
+ } else {
+ console.error(`[FAIL] ${message}`);
+ failed++;
+ }
+}
+
+console.log("=========================================");
+console.log("PDF Export Re-engineering Verification Suite");
+console.log("=========================================\n");
+
+// Test 1: Verify render-full message capability in preview-worker.js
+try {
+ const workerContent = fs.readFileSync(WORKER_PATH, "utf8");
+ assert(workerContent.includes("render-full"), "preview-worker.js supports render-full message type");
+ assert(workerContent.includes("render-full-result"), "preview-worker.js emits render-full-result event");
+ assert(workerContent.includes("DOMPurify.sanitize"), "preview-worker.js contains DOMPurify sanitation hooks");
+} catch (e) {
+ assert(false, `Could not read preview-worker.js: ${e.message}`);
+}
+
+// Test 2: Verify print layouts in styles.css
+try {
+ const cssContent = fs.readFileSync(CSS_PATH, "utf8");
+ assert(cssContent.includes("@media print"), "styles.css contains @media print rules");
+ assert(cssContent.includes("size: A4;"), "styles.css specifies A4 page size");
+ assert(cssContent.includes("break-inside: avoid"), "styles.css enforces break-inside protection");
+ assert(cssContent.includes("orphans: 3;"), "styles.css includes widow/orphan protection (orphans)");
+ assert(cssContent.includes("widows: 3;"), "styles.css includes widow/orphan protection (widows)");
+ assert(cssContent.includes("table-header-group"), "styles.css repeats table headers using table-header-group");
+} catch (e) {
+ assert(false, `Could not read styles.css: ${e.message}`);
+}
+
+// Test 3: Verify architectural classes in script.js
+try {
+ const scriptContent = fs.readFileSync(SCRIPT_PATH, "utf8");
+ assert(scriptContent.includes("PdfExportEngine"), "script.js declares the PdfExportEngine namespace");
+ assert(scriptContent.includes("ExportDocumentBuilder"), "script.js implements ExportDocumentBuilder");
+ assert(scriptContent.includes("AssetReadinessGate"), "script.js implements AssetReadinessGate");
+ assert(scriptContent.includes("WebPrintBackend"), "script.js implements WebPrintBackend");
+ assert(scriptContent.includes("DesktopChromiumSidecarBackend"), "script.js implements DesktopChromiumSidecarBackend");
+ assert(scriptContent.includes("LegacyRasterBackend"), "script.js implements LegacyRasterBackend");
+ assert(scriptContent.includes("pdf-export-modal"), "script.js integrates pdf-export-modal controller");
+ assert(scriptContent.includes("parseMarkdownFull"), "script.js contains parseMarkdownFull helper");
+} catch (e) {
+ assert(false, `Could not read script.js: ${e.message}`);
+}
+
+// Test 4: Verify desktop config & sidecar script
+try {
+ const configContent = fs.readFileSync(CONFIG_PATH, "utf8");
+ assert(configContent.includes("extensions.*"), "neutralino.config.json enables extensions native API");
+ assert(configContent.includes("com.markdownviewer.pdfexporter"), "neutralino.config.json registers the pdf-exporter extension");
+
+ const sidecarContent = fs.readFileSync(SIDECAR_PATH, "utf8");
+ assert(sidecarContent.includes("findChromeOrEdge"), "sidecar contains system browser detection logic");
+ assert(sidecarContent.includes("generatePdf"), "sidecar implements generatePdf browser-native caller");
+ assert(sidecarContent.includes("http.createServer"), "sidecar exposes an offline, zero-dependency HTTP server");
+} catch (e) {
+ assert(false, `Could not verify desktop configs or sidecar: ${e.message}`);
+}
+
+console.log("\n=========================================");
+console.log(`Verification Complete: ${passed} passed, ${failed} failed.`);
+console.log("=========================================");
+
+if (failed > 0) {
+ process.exit(1);
+} else {
+ process.exit(0);
+}