[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81118":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":14,"stars30d":13,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":15,"rankGlobal":9,"rankLanguage":9,"license":9,"archived":16,"fork":16,"defaultBranch":17,"hasWiki":18,"hasPages":18,"topics":19,"createdAt":9,"pushedAt":9,"updatedAt":20,"readmeContent":21,"aiSummary":22,"trendingCount":14,"starSnapshotCount":14,"syncStatus":23,"lastSyncTime":24,"discoverSource":25},81118,"Dials","artofpilgrim\u002FDials","artofpilgrim","Black & white dial graphic generator",null,"JavaScript",35,3,1,0,1.81,false,"main",true,[],"2026-06-12 02:04:11","# Dials\n\nA small browser tool for generating clean black-and-white (or colour-band-accented) dial scale graphics. Pick a shape, dial in the range and graduations, configure typography and colour zones, and download the result as a vector `.svg`, a multi-scale `.png`, or copy SVG markup straight to the clipboard.\n\n![Semi-circle speedometer with traffic-light colour band](docs\u002Fscreenshots\u002Fsemi-speedometer.png)\n\n**Live:** https:\u002F\u002Fartofpilgrim.github.io\u002FDials\u002F\n\n## Features\n\n### Shape & layout\n- **Shapes** — straight line (horizontal or vertical), semi-circle, custom arc (any start angle + sweep), full circle.\n- **Reverse direction** — flip the value mapping for counter-clockwise reading or max-at-start straight lines.\n- **Pivot is anchored to the canvas centre** for arc \u002F circle so tweaking start \u002F sweep rotates the dial in place instead of shifting the whole composition. Semi-circles anchor at the bottom so the arc fills the canvas upward.\n- **Self-correcting orientation toggle** on the straight dial — clicking Horiz. \u002F Vert. places the longer canvas dimension on the active axis, so the canvas never ends up squished against the rendering.\n\n### Range & graduations\n- Set min\u002Fmax, major step, and **subdivisions** (minor ticks between adjacent majors).\n- Tick values round relative to `min` (e.g. range `3–23` step `10` yields `3, 13, 23`, not `0, 10, 20`).\n- **Tick length is the visible length past the rim edge** — changing rim thickness no longer eats into the visible tick.\n- **Corner radius** on tick ends (0–100 %), with a **Round both ends** toggle. The rim-side end stays square by default so a thin rim still meets the tick cleanly.\n- Separate major \u002F minor length, weight controls; all are purely cosmetic and don't shrink the dial.\n\n### Rim & colour band\n- **Rim** on\u002Foff with adjustable thickness. Tick endpoints extend through the rim so corners close flush instead of leaving a notch; the rim draws **after** the ticks so back-extensions stay hidden behind it.\n- **Colour band** (zone indicator) — up to 6 user-defined colour zones with a `{ color, endValue }` data model. Inner \u002F outer position relative to the rim, adjustable thickness, configurable **start \u002F end value range** (defaults to full dial range), and a stacked preview bar that updates live as you edit. Four built-in presets (Traffic light, Warning, Cool, Mono) re-scale to your current range on insert.\n\n### Numbers & typography\n- Toggle, size, offset, weight (100–900), unit suffix (e.g. `°`, `%`, `mph`), and per-tick custom labels (`L, M, H` …) that override the numeric value at any index.\n- **Typography section** — font picker with Helvetica plus four curated Google Fonts (Inter, IBM Plex Mono, JetBrains Mono, Space Mono). Fonts are loaded on demand via a `\u003Clink>` injected into `\u003Chead>`. The chosen family applies to tick numbers, custom labels, and the centre title text.\n- For arc dials: **Tick direction** (inward \u002F outward \u002F both) and **Number placement** (inside \u002F outside) are decoupled from layout — flipping either is purely cosmetic, the rim radius doesn't shift.\n\n### Center (arc \u002F semi \u002F circle)\n- Optional **hub dot** with adjustable size.\n- Optional **title text** with its own size, weight, and vertical offset so it can clear the hub.\n\n### Invert\n- Renders white-on-black; flows through to the exports. The colour band keeps its user-defined colours regardless.\n\n![Inverted straight dial — white ticks on black](docs\u002Fscreenshots\u002Finverted-straight.png)\n\n### Canvas\n- Manual width \u002F height (capped at 8192 px each) plus one-click texture sizes (512 \u002F 1024 \u002F 2048) for Substance Painter and other PBR workflows.\n\n### Presets\n- Save the current configuration to `localStorage` and reload it later.\n- Older presets auto-migrate as the schema evolves (`minorStep` → `subdivisions`, etc.).\n\n### Shareable links\n- Every config change is encoded into the URL `#hash`; sharing the URL reproduces the exact dial.\n- `hashchange` is honoured so back \u002F forward and pasted URLs work, and clearing the hash resets to defaults.\n- A dedicated **Copy share link** button writes the live URL to the clipboard.\n\n### Preview\n- Mouse-wheel zooms at the cursor; click-drag pans; on-screen `−` \u002F % \u002F `+` \u002F Fit controls.\n- The preview wrapper is sized via `ResizeObserver` so the canvas always fills the available stage area while preserving its aspect ratio.\n\n### Export\n\n\u003Cimg src=\"docs\u002Fscreenshots\u002Fexport-panel.png\" alt=\"Export controls panel\" width=\"380\" align=\"right\" \u002F>\n\n- **Download SVG** — vector, editable in any vector tool.\n- **Download PNG** at **1× \u002F 2× \u002F 3× \u002F 4×** the canvas resolution.\n- **Transparent PNG** toggle forces an alpha-channel PNG regardless of the dial's background colour — useful for compositing into Painter \u002F Figma.\n- **Outline text on export** — replaces every `\u003Ctext>` element with a vector `\u003Cpath>` (via opentype.js) so the receiver of the file doesn't need the font installed. opentype.js and the TTF are lazy-loaded only when the user actually exports with outlining on, so the main bundle stays small.\n- **Copy SVG to clipboard** — paste straight into Figma \u002F Illustrator without round-tripping through the filesystem.\n\n\u003Cbr clear=\"all\" \u002F>\n\n### Sidebar UX\n- **Collapsible sections** — click a heading to fold it; open\u002Fclosed state persists per section in `localStorage`.\n- **Editable slider values** — every slider has an inline numeric input next to its label. Values are clamped to the slider's range on commit; intermediate keystrokes don't poison the renderer.\n\n## Local development\n\n```bash\nnpm install\nnpm run dev      # http:\u002F\u002Flocalhost:5173\nnpm run build    # produces dist\u002F\nnpm run preview  # serves dist\u002F for sanity checking the build\n```\n\n## Project structure\n\n```\n.\n├── index.html              # Vite entry; lean shell + \u003Cnoscript> fallback\n├── src\u002F\n│   ├── main.jsx            # ReactDOM root\n│   ├── App.jsx             # State, controls, presets, zoom, export\n│   ├── Dial.jsx            # SVG renderer (straight + arc + circle + colour band)\n│   └── styles.css          # All styles\n├── vite.config.js          # base: '\u002FDials\u002F' for the GitHub Pages subpath\n├── .github\u002Fworkflows\u002F\n│   └── deploy.yml          # Builds with Vite, publishes via actions\u002Fdeploy-pages\n└── package.json\n```\n\n## Deployment\n\nEvery push to `main` triggers a GitHub Action that runs `npm ci && npm run build` and publishes `dist\u002F` via the official `actions\u002Fdeploy-pages` workflow. Pages is configured with `build_type: workflow` (set once via the GitHub API; not stored in the repo).\n\nIf you fork this and want to deploy under a different repo name, change the `base` in [vite.config.js](vite.config.js) to match your new subpath (e.g. `\u002Fyour-repo-name\u002F`).\n\n## Notes\n\n- Stack: React 18 + Vite. The original prototype loaded React + Babel-standalone from a CDN; the Vite build cut the runtime from ~3 MB to ~60 KB gzipped (plus a separate ~68 KB chunk for opentype.js that only loads when the user enables outline-on-export).\n- The renderer is pure SVG — no canvas, no third-party drawing library at runtime.\n- The tick loop is hard-capped at 5000 ticks to keep misconfigured ranges from freezing the UI, and `min`\u002F`max` are coerced to numbers with a fallback span so equal or inverted ranges never produce NaN coordinates.\n- The preview wrapper is sized via `ResizeObserver` so the canvas always fills the available stage area while preserving its aspect ratio.\n- Persisted storage:\n  - `dialMaker.presets.v1` — saved presets (view state — zoom \u002F pan — is intentionally not stored).\n  - `dialMaker.section.\u003Cid>` — open\u002Fclosed state per sidebar section.\n- URL hash format: only fields that differ from `DEFAULTS`, written as short-key URL params via `URLSearchParams` (e.g. `#s=arc&sa=135&sw=270`). A default dial has no hash at all. Legacy base64-JSON hashes still decode for backward compatibility with older shared links. The colour-band zones array uses a custom compact `color:value,color:value` encoding rather than nested JSON.\n- Any state loaded from the hash or a preset goes through a sanitizer before reaching the renderer:\n  - **Numbers** are coerced to finite values and clamped to the same range as the matching UI control. Non-finite values fall back to `DEFAULTS`.\n  - **Enum fields** (`shape`, `tickDirection`, `numberPlacement`, `orientation`, `tickSide`, `bg`, `fontFamily`, `colorBandPosition`) are checked against an allowlist; unknown values fall back.\n  - **`tickColor`** must match `^#[0-9a-f]{6}$`; anything else falls back.\n  - **Booleans** accept strict `true` \u002F `false` only. The hash encoding is `1` \u002F `0`; any other value leaves the field unset so `DEFAULTS` wins.\n  - **Free-form strings** (`numberSuffix`, `customLabels`, `centerText`) are type-checked and soft-capped at 32 \u002F 1024 \u002F 256 characters so a maliciously long hash can't stall the renderer.\n  - **Colour-band zones** are validated for monotonic stops, valid `#rrggbb` colours, and that the final stop reaches `max`. Zones outside the band's `[start, end]` range are clipped at render time.\n\u003C\u002Fcontent>\n","Dials 是一个用于生成简洁黑白或彩色带状刻度盘图形的小型浏览器工具。其核心功能包括多种形状选择（直线、半圆、自定义弧线、全圆）、范围和刻度设置、颜色带配置以及数字与字体调整等，支持导出为SVG矢量图或PNG格式图片，也可直接复制SVG代码到剪贴板。该工具特别适合需要创建专业仪表盘设计的场景，如速度计、温度计或其他测量设备界面的设计工作。通过直观的操作界面和丰富的自定义选项，用户能够轻松制作出符合需求的高质量图形。",2,"2026-06-11 04:03:35","CREATED_QUERY"]