Download app/src/scripts/layout/debug-grid.ts from tfrere/research-article-template: direct link, hf CLI and curl.
- Browser
- Download file 7.12 kB
-
https://huggingface.co/spaces/tfrere/research-article-template/resolve/main/app/src/scripts/layout/debug-grid.ts
- Command line
-
hf download hf://spaces/tfrere/research-article-template/app/src/scripts/layout/debug-grid.ts
-
curl -L -o debug-grid.ts https://huggingface.co/spaces/tfrere/research-article-template/resolve/main/app/src/scripts/layout/debug-grid.ts
7.12 kB
| // Debug grid overlay. Draws the live tracks of the page grid (read from the | |
| // computed grid-template-columns of .content-grid, so it always reflects the | |
| // tokens in src/styles/_grid.css), the named lines, the column spans, the | |
| // sticky offset and a baseline. Loaded on demand by DebugGrid.astro. | |
| // G toggle the overlay | |
| // Shift+G toggle the baseline | |
| import { onLayoutChange } from "./headings"; | |
| const CSS = ` | |
| .dg-root { position: fixed; inset: 0; pointer-events: none; z-index: 2147483000; display: none; | |
| font: 10px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; color: #111; } | |
| html.debug-grid .dg-root { display: block; } | |
| .dg-track { position: absolute; top: 0; bottom: 0; border-left: 1px dashed rgba(127, 127, 127, .55); } | |
| .dg-track > span { position: absolute; top: 45%; left: 4px; right: 4px; padding: 2px 4px; border-radius: 3px; | |
| text-align: center; overflow: hidden; background: rgba(255, 255, 255, .85); } | |
| .dg-track.toc { background: rgba(78, 121, 167, .10); } | |
| .dg-track.body { background: rgba(89, 161, 79, .10); } | |
| .dg-track.margin { background: rgba(240, 140, 60, .12); } | |
| .dg-track.gap { background: repeating-linear-gradient(45deg, rgba(127, 127, 127, .12) 0 4px, transparent 4px 8px); } | |
| .dg-track.outer { background: rgba(225, 87, 89, .06); } | |
| .dg-line { position: absolute; top: 70px; color: #c00; writing-mode: vertical-rl; transform: translateX(-12px); white-space: nowrap; } | |
| .dg-span { position: absolute; height: 14px; line-height: 12px; text-align: center; border: 1px solid rgba(0, 0, 0, .5); | |
| border-top: 0; background: rgba(255, 255, 255, .85); } | |
| .dg-sticky { position: absolute; left: 0; right: 0; border-top: 1px dashed rgba(200, 0, 0, .6); } | |
| .dg-sticky > span { position: absolute; right: 8px; top: 2px; color: #c00; } | |
| .dg-badge { position: absolute; left: 8px; bottom: 8px; padding: 4px 8px; border-radius: 4px; background: #111; color: #fff; } | |
| .dg-baseline { position: absolute; inset: 0; display: none; | |
| background-image: linear-gradient(rgba(0, 160, 255, .22) 1px, transparent 1px); } | |
| html.debug-grid-baseline .dg-baseline { display: block; } | |
| [data-theme="dark"] .dg-root { color: #eee; } | |
| [data-theme="dark"] .dg-track > span, [data-theme="dark"] .dg-span { background: rgba(15, 17, 21, .85); border-color: rgba(255, 255, 255, .5); } | |
| [data-theme="dark"] .dg-line, [data-theme="dark"] .dg-sticky > span { color: #ff7a7a; } | |
| `; | |
| const SPANS: Array<[string, string, string]> = [ | |
| ["body", "body-start", "body-end"], | |
| ["outset", "outset-start", "outset-end"], | |
| ["wide", "body-start", "margin-end"], | |
| ["page", "page-start", "page-end"], | |
| ["screen", "screen-start", "screen-end"], | |
| ]; | |
| const root = document.documentElement; | |
| let overlay: HTMLElement | null = null; | |
| let baseline: HTMLElement | null = null; | |
| function setUrl(on: boolean) { | |
| try { | |
| const u = new URL(location.href); | |
| u.searchParams.delete("debug"); | |
| if (on) u.searchParams.set("grid", "1"); | |
| else u.searchParams.delete("grid"); | |
| history.replaceState(history.state, "", u); | |
| } catch {} | |
| } | |
| function parseTracks(template: string) { | |
| const names: string[][] = []; | |
| const sizes: number[] = []; | |
| let current: string[] = []; | |
| const re = /\[([^\]]*)\]|([\d.]+)px/g; | |
| let m: RegExpExecArray | null; | |
| while ((m = re.exec(template))) { | |
| if (m[1] !== undefined) current.push(...m[1].trim().split(/\s+/).filter(Boolean)); | |
| else { | |
| names.push(current); | |
| current = []; | |
| sizes.push(parseFloat(m[2])); | |
| } | |
| } | |
| names.push(current); | |
| return { names, sizes }; | |
| } | |
| function el(cls: string, style: Partial<CSSStyleDeclaration>, html = "") { | |
| const d = document.createElement("div"); | |
| d.className = cls; | |
| Object.assign(d.style, style); | |
| d.innerHTML = html; | |
| return d; | |
| } | |
| function draw() { | |
| if (!overlay || !root.classList.contains("debug-grid")) return; | |
| overlay.querySelectorAll(":scope > :not(.dg-baseline)").forEach((n) => n.remove()); | |
| const grid = document.querySelector<HTMLElement>(".content-grid"); | |
| if (!grid) return; | |
| const cs = getComputedStyle(grid); | |
| const rect = grid.getBoundingClientRect(); | |
| const { names, sizes } = parseTracks(cs.gridTemplateColumns); | |
| const gap = parseFloat(cs.columnGap) || 0; | |
| const lineX: Record<string, number> = {}; | |
| let x = rect.left + parseFloat(cs.paddingLeft); | |
| sizes.forEach((w, i) => { | |
| const start = names[i]; | |
| const cls = start.includes("body-start") | |
| ? "body" | |
| : start.includes("margin-start") | |
| ? "margin" | |
| : start.includes("toc-start") | |
| ? "toc" | |
| : start.includes("screen-start") || start.includes("page-end") || w > 48 | |
| ? "outer" | |
| : "gap"; | |
| start.forEach((n) => (lineX[n] = x)); | |
| overlay!.appendChild( | |
| el(`dg-track ${cls}`, { left: `${x}px`, width: `${w}px` }, `<span>${w >= 70 ? `${cls}<br>` : ""}${Math.round(w)}</span>`), | |
| ); | |
| if (start.length) overlay!.appendChild(el("dg-line", { left: `${x}px` }, start.join(" "))); | |
| x += w + (i < sizes.length - 1 ? gap : 0); | |
| }); | |
| names[sizes.length].forEach((n) => (lineX[n] = x)); | |
| SPANS.forEach(([label, a, b], i) => { | |
| if (lineX[a] === undefined || lineX[b] === undefined) return; | |
| const w = lineX[b] - lineX[a]; | |
| overlay!.appendChild( | |
| el("dg-span", { left: `${lineX[a]}px`, width: `${w}px`, bottom: `${40 + i * 18}px` }, `${label} ${Math.round(w)}px`), | |
| ); | |
| }); | |
| const stickyTop = getComputedStyle(root).getPropertyValue("--layout-sticky-top").trim(); | |
| overlay.appendChild(el("dg-sticky", { top: stickyTop }, `<span>sticky ${stickyTop}</span>`)); | |
| const single = sizes.length <= 3; | |
| const tier = single ? "single column" : window.matchMedia("(min-width: 1280px)").matches ? "rail + margin" : "compact rail"; | |
| overlay.appendChild(el("dg-badge", {}, `${root.clientWidth}px | ${tier} | G grid, Shift+G baseline`)); | |
| syncBaseline(); | |
| } | |
| function syncBaseline() { | |
| if (!baseline) return; | |
| const step = parseFloat(getComputedStyle(root).getPropertyValue("--layout-baseline")) || 8; | |
| baseline.style.backgroundSize = `100% ${step}px`; | |
| baseline.style.backgroundPosition = `0 ${-(window.scrollY % step)}px`; | |
| } | |
| function mount() { | |
| if (overlay) return; | |
| const style = document.createElement("style"); | |
| style.dataset.debugGrid = ""; | |
| style.textContent = CSS; | |
| document.head.appendChild(style); | |
| overlay = el("dg-root", {}); | |
| overlay.setAttribute("aria-hidden", "true"); | |
| baseline = el("dg-baseline", {}); | |
| overlay.appendChild(baseline); | |
| root.classList.add("debug-grid-baseline"); | |
| document.body.appendChild(overlay); | |
| onLayoutChange(draw); | |
| window.addEventListener("scroll", syncBaseline, { passive: true }); | |
| document.addEventListener("keydown", (e) => { | |
| if (e.key.toLowerCase() !== "g" || e.metaKey || e.ctrlKey || e.altKey) return; | |
| if ((e.target as Element | null)?.closest?.("input, textarea, select, [contenteditable]")) return; | |
| if (e.shiftKey) root.classList.toggle("debug-grid-baseline"); | |
| else toggleDebugGrid(); | |
| }); | |
| } | |
| export function toggleDebugGrid(force?: boolean) { | |
| mount(); | |
| const on = force ?? !root.classList.contains("debug-grid"); | |
| root.classList.toggle("debug-grid", on); | |
| setUrl(on); | |
| draw(); | |
| } | |