diff --git a/blueprint/src/mobile.css b/blueprint/src/mobile.css new file mode 100644 index 0000000..670a987 --- /dev/null +++ b/blueprint/src/mobile.css @@ -0,0 +1,233 @@ +/* Blueprint Mobile Responsive — all rules scoped to @media (max-width: 768px) */ + +/* ── TOC backdrop (injected by mobile.js) ── */ +.mobile-toc-backdrop { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.4); + z-index: 99; +} +.mobile-toc-backdrop.visible { + display: block; +} + +@media (max-width: 768px) { + + /* ── 1. TOC as fixed slide-in overlay ── */ + nav.toc { + position: fixed !important; + top: 0; + left: 0; + bottom: 0; + width: min(280px, 80vw); + z-index: 100; + transform: translateX(-100%); + transition: transform 0.25s ease; + background: #fff; + box-shadow: 2px 0 12px rgba(0,0,0,0.15); + display: flex !important; + flex-direction: column; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + margin-right: 0; + padding: 0.5rem; + padding-top: 3.5rem; + border-right: 1px solid #e0e0e0; + } + nav.toc.mobile-open { + transform: translateX(0); + } + body.mobile-toc-active { + overflow: hidden; + } + + /* ── 2. MathJax overflow ── */ + mjx-container[jax="CHTML"] { + overflow-x: auto; + max-width: 100%; + } + mjx-container[jax="CHTML"][display="true"] { + overflow-x: auto; + max-width: 100%; + padding: 4px 0; + } + div.displaymath { + max-width: 100%; + -webkit-overflow-scrolling: touch; + } + + /* ── 3. Content area ── */ + div.content { + padding: 0.75rem; + margin-bottom: 3rem; + } + div.content-wrapper { + max-width: 100%; + } + div.content > p { + margin: 1rem 0; + } + + /* Reduce theorem/proof indentation */ + div[class$=_thmcontent] { + margin-left: 0.25rem; + padding-left: 0.5rem; + } + div.proof_content { + margin-left: 0.25rem; + padding-left: 0.5rem; + } + + /* ── 4. Hover-only elements → always visible on touch ── */ + div.thm_header_hidden_extras { + display: inline-block !important; + opacity: 0.6; + } + + /* ── 5. Annotation system ── */ + + /* Enlarge touch targets (44px Apple HIG minimum) */ + .annotation-indicator { + min-width: 44px; + min-height: 44px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px; + font-size: 1rem; + } + + /* Float indicator: keep within viewport */ + .ann-indicator-float { + right: 0; + position: static; + float: right; + } + + /* Prevent iOS Safari auto-zoom on focus (font-size >= 16px) */ + .ann-author-input, + .ann-text-input, + .ann-edit-textarea { + font-size: 16px !important; + } + + /* Stack author label + input vertically */ + .ann-input-row { + flex-direction: column; + gap: 4px; + } + .ann-input-row label { + min-width: auto; + } + + /* Larger submit button */ + .ann-submit-btn { + width: 100%; + min-height: 44px; + font-size: 1rem; + } + + /* Larger action buttons */ + .ann-actions button { + min-height: 36px; + padding: 6px 12px; + font-size: 0.85rem; + } + + /* Toolbar: avoid overlap with fixed bottom nav */ + #annotation-toolbar { + bottom: 3.5rem; + } + #annotation-toolbar .ann-toolbar-main { + flex-wrap: wrap; + gap: 4px; + } + + /* ── 6. Bottom navigation bar ── */ + nav.prev_up_next { + width: 100%; + left: 0; + justify-content: center; + gap: 1rem; + } + nav.prev_up_next a { + padding: 0.5rem 1.5rem; + } + /* showmore icons in bottom bar */ + nav.prev_up_next svg.showmore { + min-width: 44px; + min-height: 44px; + padding: 0.5rem; + } + + /* ── 7. Dependency graph pages ── */ + div#graph { + height: 70vh; + touch-action: none; + overflow: hidden; + } + + /* Node detail modal → full screen */ + div.dep-modal-container { + width: 100vw !important; + margin-left: 0 !important; + margin-right: 0 !important; + margin-top: 0 !important; + max-height: 100vh; + top: 0 !important; + } + div.dep-modal-content { + max-height: calc(100vh - 2rem); + border-radius: 0; + } + button.dep-closebtn { + min-width: 44px; + min-height: 44px; + font-size: 1.5rem; + } + + /* Legend: wider on mobile so text is readable */ + #Legend dl { + max-width: 70%; + font-size: 100%; + } + + /* ── 8. Lean declaration modal ── */ + div.modal-content { + width: 95vw; + max-height: 80vh; + margin: 10vh auto; + } + div.modal-content header button.closebtn { + min-width: 44px; + min-height: 44px; + font-size: 1.5rem; + } + + /* ── 9. Header ── */ + body > header { + position: sticky; + top: 0; + z-index: 50; + } + #toc-toggle { + min-width: 44px; + min-height: 44px; + padding: 10px; + cursor: pointer; + } + + /* ── 10. Local TOC (in-page) ── */ + nav.local_toc ul { + padding-left: 0.5rem; + } + + /* ── 11. Tables: horizontal scroll ── */ + .tabular { + display: block; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + +} /* end @media (max-width: 768px) */ diff --git a/blueprint/src/mobile.js b/blueprint/src/mobile.js new file mode 100644 index 0000000..21f9f3c --- /dev/null +++ b/blueprint/src/mobile.js @@ -0,0 +1,164 @@ +/* Blueprint Mobile Enhancements — TOC overlay, dep-graph pan-zoom, MathJax tweaks */ +(function() { + 'use strict'; + + var MOBILE_BREAKPOINT = 768; + + function isMobile() { + return window.innerWidth <= MOBILE_BREAKPOINT; + } + + /* ── 1. TOC slide-in overlay ── + * + * Overrides the simple jQuery .toggle() in plastex.js with a proper + * slide-in drawer + backdrop. Only activates on narrow screens. + */ + function initTocOverlay($) { + if (!isMobile()) return; + + var $toc = $('nav.toc'); + if (!$toc.length) return; + + // Undo any inline display style that plastex.js .toggle() may have set + $toc.css('display', ''); + + var $backdrop = $('
'); + $('body').append($backdrop); + + function openToc() { + $toc.addClass('mobile-open'); + $backdrop.addClass('visible'); + $('body').addClass('mobile-toc-active'); + } + + function closeToc() { + $toc.removeClass('mobile-open'); + $backdrop.removeClass('visible'); + $('body').removeClass('mobile-toc-active'); + } + + function toggleToc() { + if ($toc.hasClass('mobile-open')) { + closeToc(); + } else { + openToc(); + } + } + + // Override the plastex.js click handler + $('#toc-toggle').off('click').on('click', function(e) { + e.stopPropagation(); + toggleToc(); + }); + + $backdrop.on('click', closeToc); + + // Close after navigating + $toc.on('click', 'a', function() { + closeToc(); + }); + + // Close on Escape + $(document).on('keydown', function(e) { + if (e.key === 'Escape' && $toc.hasClass('mobile-open')) { + closeToc(); + } + }); + } + + + /* ── 2. Dependency graph pan-zoom ── + * + * Uses d3.zoom (included in the d3 v5 full bundle already loaded by + * dep_graph pages) to enable pinch-zoom and pan on the SVG graph. + * Works on both desktop (scroll-wheel) and mobile (touch gestures). + */ + function initGraphPanZoom() { + var graphEl = document.getElementById('graph'); + if (!graphEl || typeof d3 === 'undefined' || typeof d3.zoom !== 'function') return; + + var observer = new MutationObserver(function(mutations, obs) { + var svg = graphEl.querySelector('svg'); + if (!svg) return; + obs.disconnect(); + // Small delay to let graphviz finish its post-render callbacks + setTimeout(function() { enablePanZoom(svg); }, 200); + }); + observer.observe(graphEl, { childList: true, subtree: true }); + } + + function enablePanZoom(svgEl) { + var svgSel = d3.select(svgEl); + var g = svgSel.select('g'); + if (!g.node()) return; + + var zoom = d3.zoom() + .scaleExtent([0.2, 6]) + .on('zoom', function() { + // d3 v5 API: transform is on d3.event, not the callback argument + g.attr('transform', d3.event.transform); + }); + + svgSel.call(zoom); + + // Disable double-click zoom to prevent accidental triggers on mobile + svgSel.on('dblclick.zoom', null); + + // Let SVG fill its container instead of using fixed width/height + svgEl.removeAttribute('width'); + svgEl.removeAttribute('height'); + svgEl.style.width = '100%'; + svgEl.style.height = '100%'; + + // Fit initial view: reset to identity transform + var bbox = g.node().getBBox(); + var containerW = graphEl.clientWidth; + var containerH = graphEl.clientHeight; + if (bbox.width > 0 && bbox.height > 0 && containerW > 0 && containerH > 0) { + var scale = Math.min( + containerW / bbox.width, + containerH / bbox.height + ) * 0.9; + var tx = (containerW - bbox.width * scale) / 2 - bbox.x * scale; + var ty = (containerH - bbox.height * scale) / 2 - bbox.y * scale; + svgSel.call(zoom.transform, d3.zoomIdentity.translate(tx, ty).scale(scale)); + } + } + + + /* ── 3. MathJax mobile tweaks ── */ + function initMathJaxTweaks() { + if (!isMobile()) return; + + // Disable MathJax context menu on mobile (prevents long-press conflicts) + if (window.MathJax && window.MathJax.startup && window.MathJax.startup.promise) { + window.MathJax.startup.promise.then(function() { + try { + var opts = MathJax.config.options || {}; + opts.menuOptions = opts.menuOptions || {}; + opts.menuOptions.settings = opts.menuOptions.settings || {}; + opts.menuOptions.settings.zoom = 'None'; + opts.enableMenu = false; + } catch(e) { /* non-critical */ } + }); + } + } + + + /* ── Init ── */ + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', boot); + } else { + boot(); + } + + function boot() { + // jQuery may not be loaded yet on dep_graph pages (different script order) + if (typeof jQuery !== 'undefined') { + initTocOverlay(jQuery); + } + initGraphPanZoom(); + initMathJaxTweaks(); + } + +})(); diff --git a/blueprint/src/plastex.cfg b/blueprint/src/plastex.cfg index 734aeb7..43c3ba2 100644 --- a/blueprint/src/plastex.cfg +++ b/blueprint/src/plastex.cfg @@ -13,8 +13,8 @@ split-level=1 [html5] localtoc-level=0 -extra-css=extra_styles.css annotations.css -extra-js=annotations.js +extra-css=extra_styles.css annotations.css mobile.css +extra-js=annotations.js mobile.js mathjax-dollars=False [images] diff --git a/ui/client/index.html b/ui/client/index.html index 3c7b7ae..9a97766 100644 --- a/ui/client/index.html +++ b/ui/client/index.html @@ -2,7 +2,7 @@ - + KIP Dashboard diff --git a/ui/client/src/components/LogEntryLine.module.css b/ui/client/src/components/LogEntryLine.module.css index 1e161cc..c7a8a1f 100644 --- a/ui/client/src/components/LogEntryLine.module.css +++ b/ui/client/src/components/LogEntryLine.module.css @@ -7,3 +7,9 @@ .expandable { cursor: pointer; } .expandable:hover { color: var(--text-primary); } .expandHint { color: var(--text-muted); font-size: 10px; } + +@media (max-width: 768px) { + .line { gap: 6px; font-size: 12px; padding: 6px 0; } + .event { min-width: 60px; font-size: 11px; } + .ts { font-size: 11px; } +} diff --git a/ui/client/src/components/NodeDetail.module.css b/ui/client/src/components/NodeDetail.module.css index 74d1f61..3877f10 100644 --- a/ui/client/src/components/NodeDetail.module.css +++ b/ui/client/src/components/NodeDetail.module.css @@ -320,3 +320,84 @@ details[open] > summary.sectionLabel { margin-bottom: 6px; } .runStatus.error { background: rgba(203,36,49,0.12); color: var(--red); } .runMeta { color: var(--text-muted); font-size: 10px; margin-top: 2px; font-variant-numeric: tabular-nums; } .runHint { color: var(--text-secondary); font-size: 10px; font-family: var(--font-mono); margin-top: 2px; word-break: break-all; } + +.closeBtn { + margin-left: auto; + cursor: pointer; + border: none; + background: transparent; + font-size: 18px; + color: var(--text-muted); + line-height: 1; +} + +@media (max-width: 768px) { + /* Keep the node id + close button visible while the user scrolls through + long Lean source / NL bodies on a small screen. */ + .header { + position: sticky; + top: 0; + z-index: 5; + padding: 12px 12px 8px; + } + .id { font-size: 14px; } + + .section { padding: 10px 12px; } + + /* Two-column flag grid keeps each cell wide enough to read on a 375px + viewport. */ + .flagsGrid { grid-template-columns: repeat(2, 1fr); } + + /* Stack the "Reviewing as" label + input vertically so the input gets the + full row width. */ + .reviewerLabel { + flex-direction: column; + align-items: stretch; + gap: 4px; + } + /* font-size ≥ 16px on the actual /