hyperframes 0.7.56 → 0.7.58

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.
Files changed (29) hide show
  1. package/dist/cli.js +4595 -3123
  2. package/dist/commands/contrast-audit.browser.js +79 -19
  3. package/dist/commands/layout-audit.browser.js +490 -24
  4. package/dist/hyperframe-runtime.js +50 -30
  5. package/dist/hyperframe.manifest.json +1 -1
  6. package/dist/hyperframe.runtime.iife.js +50 -30
  7. package/dist/hyperframes-player.global.js +2 -2
  8. package/dist/hyperframes-slideshow.global.js +25 -25
  9. package/dist/skills/hyperframes-cli/SKILL.md +41 -12
  10. package/dist/skills/hyperframes-cli/references/cloud.md +123 -0
  11. package/dist/skills/hyperframes-cli/references/lint-validate-inspect.md +1 -1
  12. package/dist/skills/hyperframes-cli/references/preview-render.md +3 -3
  13. package/dist/studio/assets/hyperframes-player-CtTDO63S.js +459 -0
  14. package/dist/studio/assets/{index-CmVCjZjp.js → index-B_UvTX3E.js} +205 -205
  15. package/dist/studio/assets/{index-BWnOxAiH.js → index-C47jAC3Q.js} +1 -1
  16. package/dist/studio/assets/{index-C4csZims.js → index-DeQPzqwH.js} +1 -1
  17. package/dist/studio/assets/index-uahwWkgw.css +1 -0
  18. package/dist/studio/{chunk-5QSIMBEJ.js → chunk-OBAG3GWK.js} +33 -21
  19. package/dist/studio/chunk-OBAG3GWK.js.map +1 -0
  20. package/dist/studio/{domEditingLayers-6LQGKPOI.js → domEditingLayers-2CKWGEHS.js} +2 -2
  21. package/dist/studio/index.d.ts +41 -4
  22. package/dist/studio/index.html +2 -2
  23. package/dist/studio/index.js +7436 -5423
  24. package/dist/studio/index.js.map +1 -1
  25. package/package.json +2 -1
  26. package/dist/studio/assets/hyperframes-player-BBrKzTGd.js +0 -459
  27. package/dist/studio/assets/index-D-GyYi2d.css +0 -1
  28. package/dist/studio/chunk-5QSIMBEJ.js.map +0 -1
  29. /package/dist/studio/{domEditingLayers-6LQGKPOI.js.map → domEditingLayers-2CKWGEHS.js.map} +0 -0
@@ -58,28 +58,29 @@ window.__contrastAuditPrepare = function () {
58
58
  }
59
59
 
60
60
  function parseColor(c) {
61
- var m = c.match(/rgba?\(([^)]+)\)/);
62
- if (!m) return [0, 0, 0, 1];
63
- var p = m[1].split(",").map(function (s) {
64
- return parseFloat(s.trim());
65
- });
66
- return [p[0], p[1], p[2], p[3] != null ? p[3] : 1];
67
- }
68
-
69
- // Like parseColor, but returns null instead of defaulting to black when the
70
- // value isn't a solid rgb()/rgba() color — e.g. SVG paint keywords such as
71
- // "none"/"context-fill", or a gradient/pattern reference like
72
- // 'url("#grad")'. Callers should fall back to another source of truth
73
- // rather than trust a fabricated black.
74
- function tryParseSolidColor(c) {
75
- var m = c.match(/rgba?\(([^)]+)\)/);
76
- if (!m) return null;
61
+ var m = (c || "").match(/^rgba?\(([^)]+)\)$/i);
62
+ if (!m) {
63
+ var mix = (c || "").match(
64
+ /^color-mix\(\s*in\s+srgb\s*,\s*(rgba?\([^)]+\))\s+(\d+(?:\.\d+)?|\.\d+)%\s*,\s*(rgba?\([^)]+\))\s+(\d+(?:\.\d+)?|\.\d+)%\s*\)$/i,
65
+ );
66
+ if (!mix) return null;
67
+ var first = parseColor(mix[1]);
68
+ var second = parseColor(mix[3]);
69
+ var firstWeight = parseFloat(mix[2]);
70
+ var secondWeight = parseFloat(mix[4]);
71
+ var weightTotal = firstWeight + secondWeight;
72
+ if (!first || !second || !isFinite(weightTotal) || weightTotal <= 0) return null;
73
+ return first.map(function (channel, index) {
74
+ return (channel * firstWeight + second[index] * secondWeight) / weightTotal;
75
+ });
76
+ }
77
77
  var p = m[1].split(",").map(function (s) {
78
78
  return parseFloat(s.trim());
79
79
  });
80
80
  if (
81
+ (p.length !== 3 && p.length !== 4) ||
81
82
  p.some(function (v) {
82
- return isNaN(v);
83
+ return !isFinite(v);
83
84
  })
84
85
  )
85
86
  return null;
@@ -131,6 +132,12 @@ window.__contrastAuditPrepare = function () {
131
132
  return !paintsAnyProbePoint(el, rect);
132
133
  }
133
134
 
135
+ function isIntentionallyOccluded(el, rect) {
136
+ if (typeof document.elementFromPoint !== "function") return false;
137
+ if (!el.closest || !el.closest("[data-layout-allow-occlusion]")) return false;
138
+ return !paintsAnyProbePoint(el, rect);
139
+ }
140
+
134
141
  var out = [];
135
142
  var restores = [];
136
143
  // Registered BEFORE the walk starts (not after it finishes) and pushed to
@@ -200,6 +207,10 @@ window.__contrastAuditPrepare = function () {
200
207
  if (rect.width < 8 || rect.height < 8) continue;
201
208
  if (rect.right <= 0 || rect.bottom <= 0) continue;
202
209
  if (isClippedAway(el, rect)) continue;
210
+ // The layout audit's explicit occlusion opt-out means this text is allowed
211
+ // to sit behind another scene. Skip contrast only while every probe point
212
+ // is actually covered; the same copy is audited normally when visible.
213
+ if (isIntentionallyOccluded(el, rect)) continue;
203
214
 
204
215
  // For SVG text, `fill` is the paint that's actually rendered; `color` is
205
216
  // frequently just the inherited/initial value and unrelated to what's on
@@ -208,8 +219,12 @@ window.__contrastAuditPrepare = function () {
208
219
  // `color` rather than crashing parseColor or reporting a fabricated
209
220
  // black.
210
221
  var isSvgText = isSvgTextElement(el);
211
- var fg = isSvgText ? tryParseSolidColor(cs.fill) || parseColor(cs.color) : parseColor(cs.color);
222
+ var fg = isSvgText ? parseColor(cs.fill) || parseColor(cs.color) : parseColor(cs.color);
223
+ if (!fg) continue;
212
224
  if (fg[3] <= 0.01) continue;
225
+ var strokeWidth = parseFloat(cs.webkitTextStrokeWidth || "0");
226
+ var stroke = strokeWidth > 0 ? parseColor(cs.webkitTextStrokeColor || "") : null;
227
+ if (stroke && stroke[3] <= 0.01) stroke = null;
213
228
 
214
229
  var fontSize = parseFloat(cs.fontSize);
215
230
  var fontWeight = Number(cs.fontWeight) || 400;
@@ -241,6 +256,13 @@ window.__contrastAuditPrepare = function () {
241
256
  origFillPriority = el.style.getPropertyPriority("fill");
242
257
  el.style.setProperty("fill", "transparent", "important");
243
258
  }
259
+ var origStrokeColor = null,
260
+ origStrokeColorPriority = null;
261
+ if (stroke) {
262
+ origStrokeColor = el.style.getPropertyValue("-webkit-text-stroke-color");
263
+ origStrokeColorPriority = el.style.getPropertyPriority("-webkit-text-stroke-color");
264
+ el.style.setProperty("-webkit-text-stroke-color", "transparent", "important");
265
+ }
244
266
  restores.push({
245
267
  el: el,
246
268
  origTransition: origTransition,
@@ -249,6 +271,9 @@ window.__contrastAuditPrepare = function () {
249
271
  origColorPriority: origColorPriority,
250
272
  origFill: origFill,
251
273
  origFillPriority: origFillPriority,
274
+ origStrokeColor: origStrokeColor,
275
+ origStrokeColorPriority: origStrokeColorPriority,
276
+ hasStroke: !!stroke,
252
277
  isSvgText: isSvgText,
253
278
  });
254
279
 
@@ -256,6 +281,7 @@ window.__contrastAuditPrepare = function () {
256
281
  selector: selectorOf(el),
257
282
  text: (el.textContent || "").trim().slice(0, 50),
258
283
  fg: fg,
284
+ stroke: stroke,
259
285
  fontSize: fontSize,
260
286
  fontWeight: fontWeight,
261
287
  large: large,
@@ -277,6 +303,15 @@ function __contrastAuditRestoreAll() {
277
303
  if (r.origFill) r.el.style.setProperty("fill", r.origFill, r.origFillPriority);
278
304
  else r.el.style.removeProperty("fill");
279
305
  }
306
+ if (r.hasStroke) {
307
+ if (r.origStrokeColor)
308
+ r.el.style.setProperty(
309
+ "-webkit-text-stroke-color",
310
+ r.origStrokeColor,
311
+ r.origStrokeColorPriority,
312
+ );
313
+ else r.el.style.removeProperty("-webkit-text-stroke-color");
314
+ }
280
315
  if (r.origTransition)
281
316
  r.el.style.setProperty("transition", r.origTransition, r.origTransitionPriority);
282
317
  else r.el.style.removeProperty("transition");
@@ -361,17 +396,25 @@ window.__contrastAuditFinish = async function (imgBase64, time, candidates) {
361
396
  var stepY = Math.max(1, Math.floor((y1 - y0) / 6));
362
397
  var rr = [],
363
398
  gg = [],
364
- bb = [];
399
+ bb = [],
400
+ aa = [];
365
401
  for (var y = y0; y <= y1; y += stepY) {
366
402
  for (var x = x0; x <= x1; x += stepX) {
367
403
  var idx = (y * w + x) * 4;
368
404
  rr.push(px[idx]);
369
405
  gg.push(px[idx + 1]);
370
406
  bb.push(px[idx + 2]);
407
+ aa.push(px[idx + 3]);
371
408
  }
372
409
  }
373
410
  if (rr.length === 0) continue;
374
411
 
412
+ // A transparent sampled backdrop is supplied by the downstream editor,
413
+ // not this composition. WCAG contrast cannot be inferred until that live
414
+ // video/image is known, so do not invent an opaque browser default and
415
+ // hard-fail an otherwise valid alpha overlay.
416
+ if (median(aa) < 255) continue;
417
+
375
418
  var bgR = median(rr),
376
419
  bgG = median(gg),
377
420
  bgB = median(bb);
@@ -384,6 +427,23 @@ window.__contrastAuditFinish = async function (imgBase64, time, candidates) {
384
427
 
385
428
  var ratio = +wcagRatio(compR, compG, compB, bgR, bgG, bgB).toFixed(2);
386
429
 
430
+ // A solid text stroke is part of the visible glyph paint. Use whichever
431
+ // of fill or stroke provides the stronger edge contrast, rather than
432
+ // failing readable outlined captions based on fill alone.
433
+ if (c.stroke) {
434
+ var stroke = c.stroke;
435
+ var strokeR = Math.round(stroke[0] * stroke[3] + bgR * (1 - stroke[3]));
436
+ var strokeG = Math.round(stroke[1] * stroke[3] + bgG * (1 - stroke[3]));
437
+ var strokeB = Math.round(stroke[2] * stroke[3] + bgB * (1 - stroke[3]));
438
+ var strokeRatio = +wcagRatio(strokeR, strokeG, strokeB, bgR, bgG, bgB).toFixed(2);
439
+ if (strokeRatio > ratio) {
440
+ compR = strokeR;
441
+ compG = strokeG;
442
+ compB = strokeB;
443
+ ratio = strokeRatio;
444
+ }
445
+ }
446
+
387
447
  out.push({
388
448
  time: time,
389
449
  selector: c.selector,