nimiq-branding-cli 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LINT.md CHANGED
@@ -129,13 +129,18 @@ Weight/tracking are heading-scoped (buttons and bold body labels are legitimatel
129
129
  orphan check is general (it caught a body banner, not a heading) but gated at ≥ 20px so calibrated
130
130
  fine-print under 20px — which wasn't measured on the reference — is never flagged.
131
131
 
132
- **Mobile (a second pass at 390px)**
132
+ **Responsive sweep (360 · 414 · 768 · 1024 · 1280px)**
133
+
134
+ The desktop pass renders at 1440; this sweep re-measures at five more widths so a layout that's
135
+ clean at phone *and* desktop but **breaks in the tablet / small-laptop no-man's-land** is caught —
136
+ the single most common responsive bug a fixed 390+1440 check misses. Measured: nimiq.com has **zero
137
+ horizontal overflow at every width** 320→1440, so overflow-at-a-breakpoint is calibrated to 0.
133
138
 
134
139
  | Check | Threshold |
135
140
  |---|---|
136
- | horizontal overflow | `scrollWidth > viewport` by > 4px |
137
- | tap targets too small | a button / input / link-button under 30px (inline text links are exempt) |
138
- | text smaller than 12px | any text element below 12px at mobile width |
141
+ | horizontal overflow at a breakpoint | `scrollWidth > viewport` by > 4px at **any** swept width (reports which widths and by how much) |
142
+ | tap targets too small | a button / input / link-button under 30px at any width (reports the worst breakpoint) |
143
+ | text smaller than 12px | any text element below 12px at any width (reports the worst breakpoint) |
139
144
 
140
145
  The curated spacing scale (from `assets/css/modern/spacing.css`, desktop max of each step):
141
146
  `8 · 12 · 16 · 24 · 32 · 40 · 48 · 72 · 80 · 96 · 144 · 200`. Sub-8px is treated as optical
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nimiq-branding-cli",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "nq — pixel-verified Nimiq UI component registry + CLI. 39 components (Vue 3 + plain HTML) diffed against the real Nimiq apps, plus the team's real asset library. Unofficial community tool.",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/lint.mjs CHANGED
@@ -483,10 +483,20 @@ export async function lint(target, opts = {}) {
483
483
  try { const en = page.locator('button.flag-btn', { hasText: 'English' }).first(); if (await en.count()) { await en.click({ timeout: 3000 }); await page.waitForLoadState('networkidle', { timeout: 12000 }).catch(() => {}); await page.waitForTimeout(1000); } } catch {}
484
484
  await page.evaluate(async () => { const s = innerHeight * 0.8; for (let y = 0; y < document.body.scrollHeight; y += s) { scrollTo(0, y); await new Promise((r) => setTimeout(r, 200)); } scrollTo(0, 0); await new Promise((r) => setTimeout(r, 350)); });
485
485
  const r = await page.evaluate(pageProbe, { SPACING_SCALE, ANCHORS, RADIUS_SCALE });
486
- // second pass at mobile width (no reload resize + re-measure)
487
- await page.setViewportSize({ width: 390, height: 844 });
488
- await page.waitForTimeout(500);
489
- const mob = await page.evaluate(mobileProbe);
486
+ // responsive sweep overflow / tap-targets / tiny-text across standard breakpoints. The 1440
487
+ // desktop pass above already covered the wide end; overflow at ANY intermediate width is the
488
+ // "no-man's-land" bug a fixed 390+1440 check misses. nimiq.com is overflow-clean at every width.
489
+ const SWEEP = [360, 414, 768, 1024, 1280];
490
+ const sweep = [];
491
+ for (const w of SWEEP) {
492
+ await page.setViewportSize({ width: w, height: 900 });
493
+ await page.waitForTimeout(350);
494
+ await page.evaluate(async () => { const s = innerHeight * 0.8; for (let y = 0; y < document.body.scrollHeight; y += s) { scrollTo(0, y); await new Promise((r) => setTimeout(r, 80)); } scrollTo(0, 0); await new Promise((r) => setTimeout(r, 120)); });
495
+ sweep.push({ w, ...(await page.evaluate(mobileProbe)) });
496
+ }
497
+ const overflowAt = sweep.filter((s) => s.overflowPx > 4);
498
+ const tapWorst = sweep.reduce((a, s) => (s.smallTargetN > a.smallTargetN ? s : a), sweep[0]);
499
+ const tinyWorst = sweep.reduce((a, s) => (s.tinyText > a.tinyText ? s : a), sweep[0]);
490
500
 
491
501
  // social-icon exemption (run in Node — SOCIAL anchors aren't in the page context)
492
502
  const socialName = (rgb) => { let best = Infinity, name = null; for (const [k, v] of Object.entries(SOCIAL)) { const d = Math.hypot(rgb[0] - v[0], rgb[1] - v[1], rgb[2] - v[2]); if (d < best) { best = d; name = k; } } return best <= SOCIAL_DELTA ? name : null; };
@@ -550,13 +560,13 @@ export async function lint(target, opts = {}) {
550
560
  ['flat-fill navy/colored section (use radial)', r.flatNavySection.length, r.flatNavySection[0]],
551
561
  ['focus outline removed w/o :focus-visible', r.noFocusRing ? 1 : 0, r.noFocusRing ? 'add a :focus-visible ring' : ''],
552
562
  ['Mulish not loaded (system-font fallback)', r.fontsNotLoaded ? 1 : 0, r.fontsNotLoaded ? 'load Mulish' : ''],
553
- ['mobile horizontal overflow @390px', mob.overflowPx > 4 ? 1 : 0, `${mob.overflowPx}px`],
554
- ['mobile tap targets < 36px', mob.smallTargetN, mob.smallTargets[0]],
555
- ['text smaller than 12px @390px', mob.tinyText, `${mob.tinyText} element type(s)`],
563
+ ['horizontal overflow at a breakpoint', overflowAt.length, overflowAt.map((s) => `${s.w}px:${s.overflowPx}px`).join(' ')],
564
+ ['tap targets < 36px (any breakpoint)', tapWorst.smallTargetN, tapWorst.smallTargetN ? `@${tapWorst.w}px ${tapWorst.smallTargets[0] || ''}` : ''],
565
+ ['text smaller than 12px (any breakpoint)', tinyWorst.tinyText, tinyWorst.tinyText ? `@${tinyWorst.w}px, ${tinyWorst.tinyText} type(s)` : ''],
556
566
  ];
557
567
  warnCount = warns.reduce((n, w) => n + (w[1] ? 1 : 0), 0);
558
568
 
559
- if (opts.json) { out(JSON.stringify({ url, errorCount, warnCount, raw: r, mobile: mob, exemptSocial: exemptSocial.map(([c, v]) => ({ color: c, icon: v.social })) }, null, 2)); return { errorCount, warnCount }; }
569
+ if (opts.json) { out(JSON.stringify({ url, errorCount, warnCount, raw: r, responsive: sweep, exemptSocial: exemptSocial.map(([c, v]) => ({ color: c, icon: v.social })) }, null, 2)); return { errorCount, warnCount }; }
560
570
 
561
571
  out(`\n══════ nq lint — ${target} ══════\n`);
562
572
  out('ERRORS (off-brand / a11y — must fix to pass)');