From a3ea8a062778902873baac5be6937b7a4e379e85 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 16 Jul 2026 10:42:28 +0200 Subject: [PATCH] feat: add independent viewer zoom controls --- scripts/check_repository.py | 9 ++++++ tools/render_card.py | 62 +++++++++++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/scripts/check_repository.py b/scripts/check_repository.py index 5e82c0b..3ba958d 100644 --- a/scripts/check_repository.py +++ b/scripts/check_repository.py @@ -169,6 +169,15 @@ def check_section_assets() -> None: raise AssertionError("HTML zawiera surowe środowisko verbatim") if "
stemctl test demo
" not in html: raise AssertionError("HTML nie zawiera wyrenderowanego bloku verbatim") + for marker in ( + 'class="sheet-content"', + "--viewer-canvas-scale", + "--viewer-content-scale", + "event.altKey", + "event.shiftKey", + ): + if marker not in html: + raise AssertionError(f"HTML nie zawiera obsługi zoomu viewera: {marker!r}") if not copied_asset.is_file(): raise AssertionError(f"asset nie został skopiowany: {copied_asset}") diff --git a/tools/render_card.py b/tools/render_card.py index 10ac7d1..12b62eb 100644 --- a/tools/render_card.py +++ b/tools/render_card.py @@ -2843,24 +2843,28 @@ def render_main_html(data: dict) -> str: ) sections_html.append( f'
' + f'
' f'{render_page_header_html(data, page_number, total_pages) if use_resource_header else ""}' f'' f'
{render_running_header_html(card) if not use_resource_header else ""}' f'{heading}' f'{render_step_map_html(section, data)}{content}
' f'' - f'{render_page_footer_html(data, page_number, total_pages) if use_resource_header else ""}
' + f'{render_page_footer_html(data, page_number, total_pages) if use_resource_header else ""}' + f'' ) if bibliography_html: bibliography_page = len(data.get("sections", [])) + 2 sections_html.append( f'
' + f'
' f'{render_page_header_html(data, bibliography_page, total_pages) if use_resource_header else ""}' f'' f'
{render_running_header_html(card) if not use_resource_header else ""}' f'

Bibliografia i źródła

{bibliography_html}
' f'' - f'{render_page_footer_html(data, bibliography_page, total_pages) if use_resource_header else ""}
' + f'{render_page_footer_html(data, bibliography_page, total_pages) if use_resource_header else ""}' + f'' ) scope = data.get("front_page_scope", {}) @@ -2882,11 +2886,11 @@ def render_main_html(data: dict) -> str: dictionary_page = total_pages css = r""" -:root{--ink:#111;--muted:#666;--line:#b9b9b9;--line-soft:#dddddd;--paper:#fff;--desk:#eeeeee;--og:#008000;--tech:#d36b00;--kw:#0018c8;--we:#e00000} +:root{--ink:#111;--muted:#666;--line:#b9b9b9;--line-soft:#dddddd;--paper:#fff;--desk:#eeeeee;--og:#008000;--tech:#d36b00;--kw:#0018c8;--we:#e00000;--viewer-canvas-scale:1;--viewer-content-scale:1} *{box-sizing:border-box} html{scroll-behavior:smooth} body{margin:0;background:var(--desk);color:var(--ink);font-family:"Latin Modern Roman","LM Roman 12",Georgia,"Times New Roman",serif;line-height:1.25} .topbar{position:sticky;top:0;z-index:20;background:rgba(245,245,245,.94);border-bottom:1px solid #d0d0d0;padding:8px 14px;font-family:system-ui,-apple-system,Segoe UI,sans-serif} -.topbar details{max-width:1120px;margin:0 auto}.topbar summary{cursor:pointer;font-weight:650}.toc-links{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}.toc-links a{font-size:13px;color:#333;text-decoration:none;border:1px solid #ccc;background:white;border-radius:4px;padding:3px 7px} -.paper{padding:18px 12px 44px;overflow-x:auto}.sheet{position:relative;width:210mm;min-height:297mm;margin:0 auto 18px;background:var(--paper);box-shadow:0 2px 12px rgba(0,0,0,.13);display:block;padding:13mm 21.5mm 15mm}.resource-header-enabled .sheet{padding-top:5mm}.sheet>.page-resource-header{margin-bottom:3mm} +.topbar-inner{max-width:1120px;margin:0 auto;display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.topbar details{min-width:0;flex:1}.topbar summary{cursor:pointer;font-weight:650}.toc-links{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}.toc-links a{font-size:13px;color:#333;text-decoration:none;border:1px solid #ccc;background:white;border-radius:4px;padding:3px 7px}.viewer-zoom-status{flex:none;font:600 12px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace;color:#444;white-space:nowrap} +.paper{padding:18px 12px 44px;overflow-x:auto}.sheet{position:relative;width:210mm;min-height:297mm;margin:0 auto 18px;background:var(--paper);box-shadow:0 2px 12px rgba(0,0,0,.13);display:block;padding:0;overflow:hidden;zoom:var(--viewer-canvas-scale)}.sheet-content{position:relative;width:100%;min-height:297mm;padding:13mm 21.5mm 15mm;transform:scale(var(--viewer-content-scale));transform-origin:top left}.resource-header-enabled .sheet-content{padding-top:5mm}.sheet-content>.page-resource-header{margin-bottom:3mm} .pdf-main{min-width:0;width:100%}.running-header{display:flex;justify-content:space-between;gap:18px;align-items:flex-end;border-bottom:1px solid #111;padding-bottom:3px;margin-bottom:26px;font-size:15px}.running-header span{white-space:nowrap} .hero h1{font-size:24px;line-height:1.15;margin:0 0 4px;border-bottom:1px solid #111;padding-bottom:4px}.hero .byline{float:right;margin-top:-31px;font-size:15px} .meta{clear:both;display:grid;grid-template-columns:148px minmax(0,1fr);gap:4px 18px;padding:34px 0 24px;border-bottom:1px solid #111;font-size:17px}.meta dt{color:#333}.meta dd{margin:0;font-family:"Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace} @@ -2904,8 +2908,8 @@ figure{margin:24px 0;text-align:center}figure img{max-width:100%;display:block;m .empty-check{display:inline-block;width:1em;height:1em;border:1px solid #111;vertical-align:-.12em}.answer-line,.dotfill{display:block;border-bottom:1px dotted #111;height:1.45em;min-width:10em}.write-row{display:block;min-height:2.8em} code{font-family:"Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace}.tasks{font-size:16px}.task-legacy{margin:2.5mm 0}.task-legacy>strong{display:block;font:700 10px/1.2 "Latin Modern Mono",ui-monospace,monospace;color:#555}.task-flow{border:1px solid #aaa;margin:2mm 0 4mm}.task-flow-header{display:grid;grid-template-columns:auto 1fr auto;align-items:baseline;gap:10px;padding:8px 11px;background:#f3f3f3;border-bottom:1px solid #aaa}.task-flow-header span{font:700 10px/1.2 "Latin Modern Mono",ui-monospace,monospace;color:#555}.task-flow-header h3{font-size:18px;margin:0}.task-flow-header code{font-size:8px;color:#777}.task-block,.task-exercise{margin:10px 12px;padding:0 0 10px;border-bottom:1px solid #ddd}.task-block header,.task-exercise header{display:flex;align-items:baseline;gap:8px;margin-bottom:6px}.task-block header span,.task-exercise header span{font:700 9px/1.2 "Latin Modern Mono",ui-monospace,monospace;text-transform:uppercase;color:#666}.task-block h4,.task-exercise h4{font-size:16px;margin:0}.block-steps{counter-reset:block-step;margin:5px 0 0 24px!important}.block-steps li{margin:7px 0}.block-steps li::marker{font-family:"Latin Modern Mono",ui-monospace,monospace}.task-exercise{border:1px solid #777;border-left:4px solid #111;padding:9px 11px;background:#fafafa}.task-exercise header{flex-wrap:wrap}.exercise-based-on{margin-left:auto;font-size:8px;color:#777}.exercise-evidence{border-top:1px dashed #bbb;margin-top:8px;padding-top:6px}.exercise-evidence>strong{font-size:11px;text-transform:uppercase}.exercise-criterion{font-size:13px;margin:7px 0 0}.task-flow>footer{padding:8px 11px;border-top:1px solid #aaa;font-size:12px}.we-block{border-top:1px solid #ddd;padding:8px 0}.we-block summary{cursor:pointer}.dictionary-sheet .pdf-margin{display:none} .inspector{position:fixed;right:14px;bottom:14px;width:min(360px,calc(100vw - 28px));max-height:46vh;overflow:auto;background:white;border:1px solid #cfcfcf;box-shadow:0 4px 18px rgba(0,0,0,.18);border-radius:6px;padding:12px;font-family:system-ui,-apple-system,Segoe UI,sans-serif;font-size:13px;z-index:30}.inspector h2{font-size:14px;margin:0 0 8px}.inspector .empty{color:#777}.tree-card h3{font-size:15px;margin:6px 0}.tree-card code{display:inline-block;margin:4px 0}.tree-card .line{font-weight:700}.tree-card .line.OG{color:var(--og)}.tree-card .line.TECH{color:var(--tech)}.kw-list{padding-left:18px}.kw-list code{color:var(--kw)} -@media(max-width:900px){.sheet{display:block;min-height:0;padding:26px 18px}.pdf-margin{position:static;max-height:none;overflow:visible;margin:10px 0;padding:8px;border:1px solid #ddd}.pdf-margin.left{border-left:3px solid var(--tech)}.pdf-margin.right{border-left:3px solid var(--og)}.hero .byline{float:none;margin:0 0 14px}.meta{grid-template-columns:1fr}.section-heading{flex-wrap:wrap}.task-identity{width:100%;padding:6px 0 0}.inspector{position:static;width:auto;max-height:none;margin:0 12px 18px}.topbar{position:static}} -@media print{html,body{background:white}.topbar,.inspector{display:none}.paper{padding:0}.sheet{position:relative;display:block;width:auto;min-height:0;margin:0;box-shadow:none;break-after:page;padding:0}.sheet:last-child{break-after:auto}.resource-header-enabled .sheet{padding:0}.sheet>.page-resource-header,.sheet>.page-resource-footer{display:none!important}.pdf-margin{position:absolute;top:0;width:18mm;max-height:none;overflow:hidden;margin:0;padding:0;border:0}.pdf-margin.left{left:-19.5mm}.pdf-margin.right{right:-19.5mm}.task-block,.task-exercise,figure{break-inside:avoid}} +@media(max-width:900px){.sheet{display:block;min-height:0}.sheet-content{min-height:0;padding:26px 18px}.pdf-margin{position:static;max-height:none;overflow:visible;margin:10px 0;padding:8px;border:1px solid #ddd}.pdf-margin.left{border-left:3px solid var(--tech)}.pdf-margin.right{border-left:3px solid var(--og)}.hero .byline{float:none;margin:0 0 14px}.meta{grid-template-columns:1fr}.section-heading{flex-wrap:wrap}.task-identity{width:100%;padding:6px 0 0}.inspector{position:static;width:auto;max-height:none;margin:0 12px 18px}.topbar{position:static}.viewer-zoom-status{font-size:10px}} +@media print{html,body{background:white}.topbar,.inspector{display:none}.paper{padding:0}.sheet{position:relative;display:block;width:auto;min-height:0;margin:0;box-shadow:none;break-after:page;padding:0;overflow:visible;zoom:1!important}.sheet-content{position:relative;width:auto;min-height:0;padding:0;transform:none!important}.sheet:last-child{break-after:auto}.resource-header-enabled .sheet-content{padding:0}.sheet-content>.page-resource-header,.sheet-content>.page-resource-footer{display:none!important}.pdf-margin{position:absolute;top:0;width:18mm;max-height:none;overflow:hidden;margin:0;padding:0;border:0}.pdf-margin.left{left:-19.5mm}.pdf-margin.right{right:-19.5mm}.task-block,.task-exercise,figure{break-inside:avoid}} """ css += page_resource_header_css(data) if use_resource_header: @@ -2942,6 +2946,43 @@ document.addEventListener('click', event => { inspector.innerHTML = `

${display}

${tip}

`; } }); +""" + viewer_js = r""" +const viewerRoot = document.documentElement; +const viewerPaper = document.querySelector('.paper'); +const viewerZoomStatus = document.getElementById('viewer-zoom-status'); +let viewerCanvasScale = 1; +let viewerContentScale = 1; +function clampViewerScale(value, minimum, maximum){ + return Math.min(maximum, Math.max(minimum, value)); +} +function nextViewerScale(current, delta, minimum, maximum){ + const boundedDelta = Math.min(120, Math.max(-120, delta)); + return clampViewerScale(current * Math.exp(-boundedDelta * 0.001), minimum, maximum); +} +function applyViewerScale(){ + viewerRoot.style.setProperty('--viewer-canvas-scale', viewerCanvasScale.toFixed(3)); + viewerRoot.style.setProperty('--viewer-content-scale', viewerContentScale.toFixed(3)); + if(viewerZoomStatus){ + viewerZoomStatus.textContent = `Płótno ${Math.round(viewerCanvasScale * 100)}% · Zawartość ${Math.round(viewerContentScale * 100)}%`; + } +} +if(viewerPaper){ + viewerPaper.addEventListener('wheel', event => { + const target = event.target instanceof Element ? event.target.closest('.sheet') : null; + if(!target || (!event.altKey && !event.shiftKey) || event.ctrlKey){ return; } + const delta = event.deltaY || event.deltaX; + if(!delta){ return; } + event.preventDefault(); + if(event.altKey){ + viewerCanvasScale = nextViewerScale(viewerCanvasScale, delta, 0.4, 2.0); + }else{ + viewerContentScale = nextViewerScale(viewerContentScale, delta, 0.5, 2.0); + } + applyViewerScale(); + }, {passive:false}); +} +applyViewerScale(); """ inspector_html = ( '