@real-music-packages/web-core 0.42.2 → 0.43.0

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.
@@ -891,10 +891,23 @@ declare const portraitFactory: LayerFactory<PortraitProps>;
891
891
  interface BrandingProps {
892
892
  /** Wordmark to draw. Default theme.brand. */
893
893
  logo?: string;
894
+ /** Optional second line under the wordmark (e.g. a domain) — the brand-card
895
+ * form used for an end-of-clip mark: wordmark + URL, nothing else. Omit
896
+ * for the original single-line chrome mark. */
897
+ sub?: string;
898
+ /** Sub-line font size in px. Default size*0.5. */
899
+ subSize?: number;
900
+ /** Sub-line colour. Default color/theme.sepia at reduced alpha handled by
901
+ * the caller's theme choice (no alpha applied here so callers stay in
902
+ * control of exact tone). */
903
+ subColor?: string;
904
+ /** Gap between wordmark baseline and sub-line baseline, px. Default size*0.85. */
905
+ gap?: number;
894
906
  /** Show the safe-zone debug overlay on top (apps' ?safe=1). Default false. */
895
907
  safezone?: boolean;
896
908
  /** Vertical anchor as a fraction of safeBox height from its TOP. Default 1
897
- * (bottom edge of the safe box). */
909
+ * (bottom edge of the safe box). With `sub` set, this anchors the WORDMARK
910
+ * line and the sub-line is drawn `gap` px below it. */
898
911
  yFrac?: number;
899
912
  /** Font size in px. Default 34. */
900
913
  size?: number;
@@ -1731,10 +1744,15 @@ interface CompositeRhythmProps {
1731
1744
  tickH?: number;
1732
1745
  /** Merged-mark radius (world px). Default 12. */
1733
1746
  mergeR?: number;
1734
- /** Reference subdivision counts, drawn as faint rows ABOVE the LH row (e.g.
1735
- * [3,2] for a 3-against-2 bar) each divides `windowMs` into N equal
1736
- * ticks. Default none. */
1737
- grids?: number[];
1747
+ /** Reference subdivision rows, drawn as faint rows ABOVE the LH row. Two
1748
+ * forms per entry: a plain number N divides `windowMs` into N EQUAL ticks
1749
+ * (e.g. `3` for a triplet feel, `2` for straight eighths — a 3-against-2
1750
+ * bar is `grids: [3, 2]`); an array of positive weights draws ticks at
1751
+ * each GROUP's start per those relative durations (e.g. `[3, 3, 2]` for a
1752
+ * 3-3-2 clave bar — ticks at fractions 0, 3/8, 6/8, labeled "3+3+2") — for
1753
+ * asymmetric groupings the rhythm montage needs that an equal-N grid can't
1754
+ * represent. Default none. */
1755
+ grids?: Array<number | number[]>;
1738
1756
  /** Onsets within this many ms of each other collapse into ONE composite
1739
1757
  * mark. Default 60. */
1740
1758
  mergeWindowMs?: number;
@@ -1245,6 +1245,10 @@ var spectrumFactory = {
1245
1245
  // src/scene/layers/branding.ts
1246
1246
  function brandingLayer() {
1247
1247
  let logo;
1248
+ let sub;
1249
+ let subSize = 0;
1250
+ let subColor;
1251
+ let gap = 0;
1248
1252
  let safezone = false;
1249
1253
  let yFrac = 1;
1250
1254
  let size = 34;
@@ -1253,10 +1257,14 @@ function brandingLayer() {
1253
1257
  key: "branding",
1254
1258
  init(_ctx, props) {
1255
1259
  logo = props.logo;
1260
+ sub = props.sub;
1256
1261
  safezone = props.safezone ?? false;
1257
1262
  yFrac = props.yFrac ?? 1;
1258
1263
  size = props.size ?? 34;
1264
+ subSize = props.subSize ?? size * 0.5;
1265
+ gap = props.gap ?? size * 0.85;
1259
1266
  color = props.color;
1267
+ subColor = props.subColor;
1260
1268
  },
1261
1269
  draw(ctx) {
1262
1270
  const c = ctx.ctx2d;
@@ -1266,8 +1274,14 @@ function brandingLayer() {
1266
1274
  c.textAlign = "center";
1267
1275
  c.font = `italic ${size}px ${ctx.theme.fontBody}`;
1268
1276
  c.fillStyle = color ?? ctx.theme.sepia;
1269
- const y = sb.top + sb.h * yFrac - (yFrac >= 1 ? size * 0.3 : 0);
1277
+ const pairShift = sub ? gap / 2 : 0;
1278
+ const y = sb.top + sb.h * yFrac - (yFrac >= 1 ? size * 0.3 : 0) - pairShift;
1270
1279
  c.fillText(text, sb.cx, y);
1280
+ if (sub) {
1281
+ c.font = `${subSize}px ${ctx.theme.fontBody}`;
1282
+ c.fillStyle = subColor ?? color ?? ctx.theme.sepia;
1283
+ c.fillText(sub, sb.cx, y + gap);
1284
+ }
1271
1285
  c.restore();
1272
1286
  if (safezone) drawSafeGuides(c);
1273
1287
  }
@@ -1281,6 +1295,10 @@ var brandingFactory = {
1281
1295
  const p = props;
1282
1296
  const errs = [];
1283
1297
  if (p.logo != null && typeof p.logo !== "string") errs.push("branding.logo must be a string");
1298
+ if (p.sub != null && typeof p.sub !== "string") errs.push("branding.sub must be a string");
1299
+ if (p.subSize != null && (typeof p.subSize !== "number" || p.subSize <= 0)) errs.push("branding.subSize must be a positive number");
1300
+ if (p.subColor != null && typeof p.subColor !== "string") errs.push("branding.subColor must be a string");
1301
+ if (p.gap != null && typeof p.gap !== "number") errs.push("branding.gap must be a number");
1284
1302
  if (p.safezone != null && typeof p.safezone !== "boolean") errs.push("branding.safezone must be a boolean");
1285
1303
  if (p.yFrac != null && typeof p.yFrac !== "number") errs.push("branding.yFrac must be a number");
1286
1304
  if (p.size != null && (typeof p.size !== "number" || p.size <= 0)) errs.push("branding.size must be a positive number");
@@ -4416,8 +4434,11 @@ function compositeRhythmLayer() {
4416
4434
  const lhY = yCenter - rowGap;
4417
4435
  const rhY = yCenter + rowGap;
4418
4436
  c.save();
4419
- grids.forEach((n, gi) => {
4420
- if (n <= 0) return;
4437
+ grids.forEach((entry, gi) => {
4438
+ const isGrouping = Array.isArray(entry);
4439
+ const weights = isGrouping ? entry.filter((w) => w > 0) : [];
4440
+ if (isGrouping && weights.length === 0) return;
4441
+ if (!isGrouping && entry <= 0) return;
4421
4442
  const gy = lhY - rowGap * (grids.length - gi);
4422
4443
  c.strokeStyle = "rgba(26,22,20,0.22)";
4423
4444
  c.lineWidth = 2;
@@ -4425,8 +4446,20 @@ function compositeRhythmLayer() {
4425
4446
  c.moveTo(sb.left, gy);
4426
4447
  c.lineTo(sb.left + sb.w, gy);
4427
4448
  c.stroke();
4428
- for (let i = 0; i < n; i++) {
4429
- const t = w0 + (w1 - w0) * i / n;
4449
+ const starts = [];
4450
+ if (isGrouping) {
4451
+ const total = weights.reduce((a, b) => a + b, 0);
4452
+ let acc = 0;
4453
+ for (const w of weights) {
4454
+ starts.push(acc / total);
4455
+ acc += w;
4456
+ }
4457
+ } else {
4458
+ const n = entry;
4459
+ for (let i = 0; i < n; i++) starts.push(i / n);
4460
+ }
4461
+ for (const f of starts) {
4462
+ const t = w0 + (w1 - w0) * f;
4430
4463
  if (!revealed(t)) continue;
4431
4464
  const gx = x(t);
4432
4465
  c.strokeStyle = "rgba(26,22,20,0.38)";
@@ -4440,7 +4473,7 @@ function compositeRhythmLayer() {
4440
4473
  c.font = "600 22px system-ui, sans-serif";
4441
4474
  c.textAlign = "right";
4442
4475
  c.textBaseline = "middle";
4443
- c.fillText(String(n), sb.left - 10, gy);
4476
+ c.fillText(isGrouping ? weights.join("+") : String(entry), sb.left - 10, gy);
4444
4477
  });
4445
4478
  const drawTicks = (ticks, y, color) => {
4446
4479
  c.fillStyle = color;
@@ -4532,8 +4565,14 @@ var compositeRhythmFactory = {
4532
4565
  if (p.rowGap != null && (typeof p.rowGap !== "number" || p.rowGap <= 0)) errs.push("composite-rhythm.rowGap must be a positive number");
4533
4566
  if (p.tickH != null && (typeof p.tickH !== "number" || p.tickH <= 0)) errs.push("composite-rhythm.tickH must be a positive number");
4534
4567
  if (p.mergeR != null && (typeof p.mergeR !== "number" || p.mergeR <= 0)) errs.push("composite-rhythm.mergeR must be a positive number");
4535
- if (p.grids != null && (!Array.isArray(p.grids) || p.grids.some((v) => typeof v !== "number" || v <= 0)))
4536
- errs.push("composite-rhythm.grids must be an array of positive numbers");
4568
+ if (p.grids != null) {
4569
+ const gridsValid = Array.isArray(p.grids) && p.grids.every((v) => {
4570
+ if (typeof v === "number") return v > 0;
4571
+ if (Array.isArray(v)) return v.length > 0 && v.every((w) => typeof w === "number" && w > 0);
4572
+ return false;
4573
+ });
4574
+ if (!gridsValid) errs.push("composite-rhythm.grids must be an array of positive numbers or arrays of positive-number weights");
4575
+ }
4537
4576
  if (p.mergeWindowMs != null && (typeof p.mergeWindowMs !== "number" || p.mergeWindowMs < 0))
4538
4577
  errs.push("composite-rhythm.mergeWindowMs must be a non-negative number");
4539
4578
  if (p.mergeColor != null && typeof p.mergeColor !== "string") errs.push("composite-rhythm.mergeColor must be a string");