@dixy-ui/themes 0.1.0-beta.2 → 0.1.0-beta.4

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 (94) hide show
  1. package/dist/ansi-panes.css +79 -5
  2. package/dist/arcade/ornament.d.ts +3 -0
  3. package/dist/arcade/ornament.js +8 -0
  4. package/dist/arcade-cabinet.css +1086 -0
  5. package/dist/art-deco.css +79 -5
  6. package/dist/atomic-lounge.css +83 -5
  7. package/dist/authoring.d.ts +8 -8
  8. package/dist/azulejo-tile.css +83 -5
  9. package/dist/blueprint.css +79 -5
  10. package/dist/brutalist-concrete.css +83 -5
  11. package/dist/bytes.json +97 -67
  12. package/dist/candy-clay.css +85 -5
  13. package/dist/catalog.d.ts +294 -42
  14. package/dist/catalog.js +301 -43
  15. package/dist/character.d.ts +1 -1
  16. package/dist/character.js +6 -0
  17. package/dist/chrome.js +13 -3
  18. package/dist/compile-css.js +23 -5
  19. package/dist/dispatch.css +731 -0
  20. package/dist/diwali-lights.css +83 -5
  21. package/dist/documents.d.ts +115 -7
  22. package/dist/documents.js +10 -2
  23. package/dist/foundations.d.ts +8 -0
  24. package/dist/foundations.js +10 -0
  25. package/dist/frame-styles.d.ts +3 -0
  26. package/dist/frame-styles.js +6 -0
  27. package/dist/freighter.css +145 -15
  28. package/dist/glass-ui.css +79 -5
  29. package/dist/holi-colours.css +83 -5
  30. package/dist/ledger.css +566 -0
  31. package/dist/living-forest.css +85 -5
  32. package/dist/mainframe.css +764 -0
  33. package/dist/manifests.d.ts +144 -144
  34. package/dist/material-light.css +83 -5
  35. package/dist/neon-night-market.css +83 -5
  36. package/dist/ocean-depths.css +85 -5
  37. package/dist/orbital-hud.css +145 -15
  38. package/dist/organic-paint-launch.d.ts +9 -0
  39. package/dist/organic-paint-launch.js +121 -46
  40. package/dist/organic-paint.d.ts +1 -1
  41. package/dist/organic-settings.d.ts +109 -25
  42. package/dist/organic-settings.js +49 -0
  43. package/dist/papel-picado.css +83 -5
  44. package/dist/phosphor-terminal.css +83 -5
  45. package/dist/prism-glass.css +79 -5
  46. package/dist/recipes.d.ts +162 -0
  47. package/dist/recipes.js +164 -0
  48. package/dist/resolve.d.ts +18 -2
  49. package/dist/retro-os.css +79 -5
  50. package/dist/riso-zine.css +79 -5
  51. package/dist/save-point.css +948 -0
  52. package/dist/savepoint/ornament.d.ts +3 -0
  53. package/dist/savepoint/ornament.js +8 -0
  54. package/dist/scrapbook.css +79 -5
  55. package/dist/signal.css +563 -0
  56. package/dist/skins/arcade-cabinet.d.ts +68 -0
  57. package/dist/skins/arcade-cabinet.js +68 -0
  58. package/dist/skins/dispatch.d.ts +57 -0
  59. package/dist/skins/dispatch.js +57 -0
  60. package/dist/skins/ledger.d.ts +57 -0
  61. package/dist/skins/ledger.js +57 -0
  62. package/dist/skins/mainframe.d.ts +57 -0
  63. package/dist/skins/mainframe.js +57 -0
  64. package/dist/skins/save-point.d.ts +68 -0
  65. package/dist/skins/save-point.js +68 -0
  66. package/dist/skins/signal.d.ts +57 -0
  67. package/dist/skins/signal.js +57 -0
  68. package/dist/skins/sunset-drive.d.ts +68 -0
  69. package/dist/skins/sunset-drive.js +68 -0
  70. package/dist/solar-sail.css +79 -5
  71. package/dist/starters-launch.d.ts +25 -1
  72. package/dist/starters-launch.js +343 -49
  73. package/dist/studio-hardware.css +83 -5
  74. package/dist/sumi-ink.css +79 -5
  75. package/dist/sunset/ornament.d.ts +3 -0
  76. package/dist/sunset/ornament.js +8 -0
  77. package/dist/sunset-drive.css +1024 -0
  78. package/dist/swiss-poster.css +149 -15
  79. package/dist/tactile-atelier.css +85 -5
  80. package/dist/theme-document.schema.json +34 -6
  81. package/dist/theme-fix.d.ts +11 -0
  82. package/dist/theme-fix.js +32 -0
  83. package/dist/theme-fixes.d.ts +2 -0
  84. package/dist/theme-fixes.js +1 -0
  85. package/dist/theme-schema.d.ts +6 -6
  86. package/dist/theme-schema.js +2 -0
  87. package/dist/tokyo-transit.css +83 -5
  88. package/dist/winamp-signal.css +83 -5
  89. package/dist/y2k-titanium.css +151 -15
  90. package/dist/yule-knit.css +79 -5
  91. package/package.json +50 -8
  92. package/dist/lunar-new-year.css +0 -811
  93. package/dist/skins/lunar-new-year.d.ts +0 -68
  94. package/dist/skins/lunar-new-year.js +0 -68
@@ -39,6 +39,11 @@
39
39
  "Orbitron Variable", "Orbitron", "Eurostile", "Bank Gothic",
40
40
  ui-sans-serif, sans-serif;
41
41
  --dixy-skin-edge: #0a8aa366;
42
+ --dixy-skin-field-edge: color-mix(
43
+ in oklab,
44
+ var(--dixy-color-base-9) 50%,
45
+ var(--dixy-color-base-8)
46
+ );
42
47
  --dixy-organic-density: 55;
43
48
  --dixy-organic-cornerDensity: 80;
44
49
  --dixy-organic-canopyDepth: 40;
@@ -127,7 +132,6 @@
127
132
  --dixy-organic-b: #2bb5c8;
128
133
  --dixy-organic-c: #ff8a4c;
129
134
  --dixy-organic-d: #cdf9ff;
130
- --dixy-skin-selected: var(--dixy-skin-finish);
131
135
  --dixy-color-text-primary: color-mix(
132
136
  in srgb,
133
137
  var(--dixy-color-base-12) 65%,
@@ -145,6 +149,7 @@
145
149
  #06161e
146
150
  );
147
151
  }
152
+
148
153
  .dixy-theme[data-dixy-skin="dixy/orbital-hud"][data-relief="2"] {
149
154
  --dixy-skin-shadow:
150
155
  var(--dixy-skin-depth-shadow, var(--dixy-skin-authored-shadow)),
@@ -278,6 +283,28 @@
278
283
  ):not([data-variant="solid"]):not([data-variant="soft"]) {
279
284
  background-color: var(--dixy-skin-surface);
280
285
  }
286
+ :is(
287
+ .dixy-dialog-popup,
288
+ .dixy-alert-dialog-popup,
289
+ .dixy-drawer-popup,
290
+ .dixy-popover-popup,
291
+ .dixy-preview-card-popup,
292
+ .dixy-tooltip-popup,
293
+ .dixy-select-popup,
294
+ .dixy-combobox-popup,
295
+ .dixy-autocomplete-popup,
296
+ .dixy-menu-popup,
297
+ .dixy-context-menu-popup,
298
+ .dixy-navigation-menu-popup
299
+ )[data-dixy-skin="dixy/orbital-hud"]:not([data-dixy-material-override]):not(
300
+ :where([data-dixy-material-override]) *
301
+ ):not([data-variant="solid"]):not([data-variant="soft"]) {
302
+ background-color: color-mix(
303
+ in srgb,
304
+ var(--dixy-color-background-surface-raised) 94%,
305
+ var(--dixy-skin-surface)
306
+ );
307
+ }
281
308
  :is(
282
309
  .dixy-input,
283
310
  .dixy-textarea,
@@ -291,6 +318,11 @@
291
318
  )[data-dixy-skin="dixy/orbital-hud"] {
292
319
  border-radius: calc(var(--dixy-radius-control) * 1);
293
320
  }
321
+ :is(.dixy-textarea)[data-dixy-skin="dixy/orbital-hud"] {
322
+ border-radius: calc(
323
+ min(var(--dixy-radius-control), var(--dixy-radius-card)) * 1
324
+ );
325
+ }
294
326
  :is(
295
327
  .dixy-input,
296
328
  .dixy-textarea,
@@ -305,7 +337,7 @@
305
337
  :where([data-dixy-material-override]) *
306
338
  ) {
307
339
  border-width: var(--dixy-input-border-width);
308
- border-color: var(--dixy-skin-edge);
340
+ border-color: var(--dixy-skin-field-edge);
309
341
  background-image: var(--dixy-skin-paint);
310
342
  background-color: var(--dixy-color-background-canvas);
311
343
  background-size:
@@ -315,6 +347,43 @@
315
347
  100% 100%;
316
348
  box-shadow: var(--dixy-skin-inset);
317
349
  }
350
+ /* materials.css styles [data-dixy-visual] surfaces at 0,5,0. Match the
351
+ skin's paint to that so its image and grain size always land together;
352
+ otherwise the material image is tiled at the grain size (stripes). */
353
+ :is(
354
+ .dixy-card,
355
+ .dixy-box,
356
+ .dixy-dialog-popup,
357
+ .dixy-alert-dialog-popup,
358
+ .dixy-drawer-popup,
359
+ .dixy-popover-popup,
360
+ .dixy-preview-card-popup,
361
+ .dixy-tooltip-popup,
362
+ .dixy-select-popup,
363
+ .dixy-combobox-popup,
364
+ .dixy-autocomplete-popup,
365
+ .dixy-menu-popup,
366
+ .dixy-context-menu-popup,
367
+ .dixy-navigation-menu-popup,
368
+ .dixy-toast-root,
369
+ .dixy-alert,
370
+ .dixy-list-box,
371
+ .dixy-calendar,
372
+ .dixy-range-calendar,
373
+ .dixy-input,
374
+ .dixy-textarea,
375
+ .dixy-native-select,
376
+ .dixy-select-trigger,
377
+ .dixy-input-group,
378
+ .dixy-combobox-input-group,
379
+ .dixy-autocomplete-input-group,
380
+ .dixy-number-field-group,
381
+ .dixy-otp-field-input
382
+ )[data-dixy-skin="dixy/orbital-hud"]:not([data-dixy-material-override]):not(
383
+ :where([data-dixy-material-override]) *
384
+ )[data-dixy-visual][data-material]:not([data-disabled]) {
385
+ background-image: var(--dixy-skin-paint);
386
+ }
318
387
  :is(.dixy-button)[data-dixy-skin="dixy/orbital-hud"]:not(
319
388
  [data-dixy-material-override]
320
389
  ):not(:where([data-dixy-material-override]) *):is(
@@ -345,14 +414,19 @@
345
414
  border: 1px solid var(--dixy-skin-edge);
346
415
  box-shadow: var(--dixy-skin-inset);
347
416
  }
417
+ /* Selected tabs and toggles are one flat colour, lifted by edge and relief only. A gradient behind the label (thumb art, sheen, material image) splits it across two colours. :not([data-variant="line"]) also outranks materials.css (0,3,0). */
348
418
  :is(.dixy-tabs-indicator)[data-dixy-skin="dixy/orbital-hud"]:not(
349
419
  [data-dixy-material-override]
350
420
  ):not(:where([data-dixy-material-override]) *) {
351
- background-image: var(--dixy-skin-finish);
352
421
  box-shadow: var(--dixy-skin-control-relief);
353
422
  border: 1px solid var(--dixy-skin-edge);
354
423
  }
355
- /* Toggle groups read like tabs: a sunken track, the selected toggle a raised thumb (slider-knob paint in light, the raised finish in dark so its label stays readable). */
424
+ :is(.dixy-tabs-indicator)[data-dixy-skin="dixy/orbital-hud"]:not(
425
+ [data-dixy-material-override]
426
+ ):not(:where([data-dixy-material-override]) *):not([data-variant="line"]) {
427
+ background-image: none;
428
+ }
429
+ /* Toggle groups read like tabs: a sunken track, the selected toggle a raised, flat thumb. */
356
430
  .dixy-theme[data-dixy-skin="dixy/orbital-hud"]
357
431
  .dixy-toggle-group:is([data-attached], [data-segmented]):not(
358
432
  [data-dixy-material-override]
@@ -367,7 +441,7 @@
367
441
  [data-variant="solid"]
368
442
  )[data-pressed]:not([data-disabled]) {
369
443
  background-color: var(--dixy-color-background-surface-raised);
370
- background-image: var(--dixy-skin-selected, var(--dixy-skin-thumb));
444
+ background-image: none;
371
445
  box-shadow: var(--dixy-skin-control-relief);
372
446
  border-color: var(--dixy-skin-edge);
373
447
  color: var(--dixy-color-text-primary);
@@ -871,16 +945,72 @@
871
945
  :is(.dixy-card, .dixy-dialog-popup)[data-dixy-skin="dixy/orbital-hud"] {
872
946
  border-radius: 0;
873
947
  --dixy-chrome-bar-radius: 0px;
874
- clip-path: polygon(
875
- 10px 0,
876
- calc(100% - 10px) 0,
877
- 100% 10px,
878
- 100% calc(100% - 10px),
879
- calc(100% - 10px) 100%,
880
- 10px 100%,
881
- 0 calc(100% - 10px),
882
- 0 10px
883
- );
948
+ }
949
+ @supports (corner-shape: bevel) {
950
+ .dixy-theme[data-dixy-skin="dixy/orbital-hud"]
951
+ :is(.dixy-card, .dixy-dialog-popup)[data-dixy-skin="dixy/orbital-hud"] {
952
+ border-radius: 10px;
953
+ corner-shape: bevel;
954
+ }
955
+ }
956
+ @supports not (corner-shape: bevel) {
957
+ .dixy-theme[data-dixy-skin="dixy/orbital-hud"]
958
+ :is(.dixy-card, .dixy-dialog-popup)[data-dixy-skin="dixy/orbital-hud"] {
959
+ clip-path: polygon(
960
+ 10px 0,
961
+ calc(100% - 10px) 0,
962
+ 100% 10px,
963
+ 100% calc(100% - 10px),
964
+ calc(100% - 10px) 100%,
965
+ 10px 100%,
966
+ 0 calc(100% - 10px),
967
+ 0 10px
968
+ );
969
+ }
970
+ .dixy-theme[data-dixy-skin="dixy/orbital-hud"]
971
+ :is(
972
+ .dixy-card,
973
+ .dixy-dialog-popup
974
+ )[data-dixy-skin="dixy/orbital-hud"]::after {
975
+ content: "";
976
+ position: absolute;
977
+ inset: calc(var(--dixy-skin-frame-width, 1px) * -1);
978
+ pointer-events: none;
979
+ z-index: 1;
980
+ background:
981
+ linear-gradient(
982
+ to bottom right,
983
+ transparent 50%,
984
+ var(--dixy-skin-edge) 50%
985
+ calc(50% + var(--dixy-skin-frame-width, 1px)),
986
+ transparent calc(50% + var(--dixy-skin-frame-width, 1px))
987
+ )
988
+ top left/10px 10px no-repeat,
989
+ linear-gradient(
990
+ to bottom left,
991
+ transparent 50%,
992
+ var(--dixy-skin-edge) 50%
993
+ calc(50% + var(--dixy-skin-frame-width, 1px)),
994
+ transparent calc(50% + var(--dixy-skin-frame-width, 1px))
995
+ )
996
+ top right/10px 10px no-repeat,
997
+ linear-gradient(
998
+ to top right,
999
+ transparent 50%,
1000
+ var(--dixy-skin-edge) 50%
1001
+ calc(50% + var(--dixy-skin-frame-width, 1px)),
1002
+ transparent calc(50% + var(--dixy-skin-frame-width, 1px))
1003
+ )
1004
+ bottom left/10px 10px no-repeat,
1005
+ linear-gradient(
1006
+ to top left,
1007
+ transparent 50%,
1008
+ var(--dixy-skin-edge) 50%
1009
+ calc(50% + var(--dixy-skin-frame-width, 1px)),
1010
+ transparent calc(50% + var(--dixy-skin-frame-width, 1px))
1011
+ )
1012
+ bottom right/10px 10px no-repeat;
1013
+ }
884
1014
  }
885
1015
  .dixy-theme[data-dixy-skin="dixy/orbital-hud"]
886
1016
  :is(.dixy-card)[data-dixy-skin="dixy/orbital-hud"]
@@ -24,6 +24,12 @@ export declare const paintPicado: OrganicPainter;
24
24
  export declare const paintGulal: OrganicPainter;
25
25
  export declare const paintKnit: OrganicPainter;
26
26
  export declare const paintLiquid: OrganicPainter;
27
+ /** A striped sun setting behind the top edge and a horizon grid under the card. */
28
+ export declare const paintSunset: OrganicPainter;
29
+ /** A blinking menu cursor beside the window and a sparkle on one corner. */
30
+ export declare const paintMenuCursor: OrganicPainter;
31
+ /** Chasing marquee bulbs over the top edge and credits under the card. */
32
+ export declare const paintMarquee: OrganicPainter;
27
33
  export declare const launchPainters: {
28
34
  readonly crt: OrganicPainter;
29
35
  readonly panes: OrganicPainter;
@@ -40,4 +46,7 @@ export declare const launchPainters: {
40
46
  readonly gulal: OrganicPainter;
41
47
  readonly knit: OrganicPainter;
42
48
  readonly liquid: OrganicPainter;
49
+ readonly sunset: OrganicPainter;
50
+ readonly menu: OrganicPainter;
51
+ readonly marquee: OrganicPainter;
43
52
  };
@@ -925,29 +925,7 @@ export const paintPicado = (ctx, _p, w, h, settings, colors, clock, playing, ind
925
925
  });
926
926
  /* ───────────────────────── Holi Colours ───────────────────────── */
927
927
  export const paintGulal = (ctx, _p, w, h, settings, colors, clock, playing, index, limits, shown = settings.density) => framed(ctx, w, h, limits, () => {
928
- const s = paintState(settings), tier = tierOf(w, h), seed = s.seed + index * 41, inks = [colors.a, colors.b, colors.c, colors.d], drift = playing ? clock * (s.motion / 200) : 0, haze = s.style === "Soft haze", corners = cornersOf(w, h), at = choose(index, s.seed, 1, 4), ink = choose(index, s.seed, 2, 4);
929
- // A powder cloud pushed outward from a corner. Its radius stops before the
930
- // content box, so the soft edge is never sliced by the frame clip.
931
- const cloud = (k, color, scale) => {
932
- const [x, y, sx, sy] = corners[k], base = (18 + s.scale * 0.9) * scale, ox = Math.sin(drift + k) * 2, oy = Math.cos(drift * 0.8 + k) * 2;
933
- for (let j = 0; j < 5; j++) {
934
- const jx = x -
935
- sx * (6 + rand(seed + k * 7 + j) * 10) +
936
- ox +
937
- (rand(seed + k + j * 3) - 0.5) * base * 0.6, jy = y - sy * (6 + rand(seed + k * 7 + j + 3) * 10) + oy, r = Math.min(base * (0.6 + rand(seed + j) * 0.4), room(jx, jy, w, h, limits) - 1);
938
- if (r < 4)
939
- continue;
940
- const g = ctx.createRadialGradient(jx, jy, 0, jx, jy, r);
941
- g.addColorStop(0, color);
942
- g.addColorStop(1, "transparent");
943
- ctx.globalAlpha = haze ? 0.28 : 0.5;
944
- ctx.fillStyle = g;
945
- ctx.beginPath();
946
- ctx.arc(jx, jy, r, 0, tau);
947
- ctx.fill();
948
- }
949
- ctx.globalAlpha = 1;
950
- };
928
+ const s = paintState(settings), tier = tierOf(w, h), seed = s.seed + index * 41, inks = [colors.a, colors.b, colors.c, colors.d], haze = s.style === "Soft haze", corners = cornersOf(w, h), at = choose(index, s.seed, 1, 4), ink = choose(index, s.seed, 2, 4);
951
929
  // Dry powder thrown along an edge, thinning away from its corner.
952
930
  const speckle = (k, color, count) => {
953
931
  const [x, y, sx, sy] = corners[k];
@@ -955,35 +933,23 @@ export const paintGulal = (ctx, _p, w, h, settings, colors, clock, playing, inde
955
933
  const along = rand(seed + i + k * 13) ** 1.6 * Math.min(w, h) * 0.6, off = 3 + rand(seed + i + 1) * 18, edge = i % 2, px = edge ? x + sx * along : x - sx * off, py = edge ? y - sy * off : y + sy * along;
956
934
  if (room(px, py, w, h, limits) < 2)
957
935
  continue;
958
- ctx.globalAlpha = 0.5 + rand(seed + i + 2) * 0.45;
936
+ ctx.globalAlpha = (haze ? 0.3 : 0.5) + rand(seed + i + 2) * 0.45;
959
937
  dot(ctx, px, py, 0.6 + rand(seed + i + 3) * (s.style === "Splashes" ? 2.2 : 1.3), color);
960
938
  }
961
939
  ctx.globalAlpha = 1;
962
940
  };
963
- const count = Math.round((shown / 100) * 40);
941
+ // Corner powder clouds were removed: their blur was cut square by the
942
+ // ornament canvas. Every style now throws dry powder; haze is fainter.
943
+ const count = Math.round((shown / 100) * 40) * (haze ? 0.6 : 1);
964
944
  if (tier === "hero") {
965
- if (s.style !== "Speckle dust") {
966
- cloud(at, inks[ink], 1.1);
967
- cloud((at + 2) % 4, inks[(ink + 2) % 4], 0.9);
968
- }
969
- if (!haze) {
970
- speckle(at, inks[ink], count);
971
- speckle((at + 2) % 4, inks[(ink + 2) % 4], count);
972
- speckle((at + 1) % 4, inks[(ink + 1) % 4], count / 3);
973
- }
974
- }
975
- else if (tier === "card") {
976
- if (s.style !== "Speckle dust")
977
- cloud(at, inks[ink], 0.85);
978
- if (!haze)
979
- speckle(at, inks[ink], count * 0.7);
980
- }
981
- else {
982
- if (s.style !== "Speckle dust")
983
- cloud(at, inks[ink], 0.6);
984
- if (!haze)
985
- speckle(at, inks[ink], count * 0.4);
945
+ speckle(at, inks[ink], count);
946
+ speckle((at + 2) % 4, inks[(ink + 2) % 4], count);
947
+ speckle((at + 1) % 4, inks[(ink + 1) % 4], count / 3);
986
948
  }
949
+ else if (tier === "card")
950
+ speckle(at, inks[ink], count * 0.7);
951
+ else
952
+ speckle(at, inks[ink], count * 0.4);
987
953
  });
988
954
  /* ───────────────────────── Yule Knit ───────────────────────── */
989
955
  function snowflake(ctx, x, y, r, color, turn) {
@@ -1137,6 +1103,112 @@ export const paintLiquid = (ctx, p, w, h, settings, colors, clock, playing, _ind
1137
1103
  ctx.stroke();
1138
1104
  ctx.globalAlpha = 1;
1139
1105
  });
1106
+ /* ───────────────────────── Sunset Drive ───────────────────────── */
1107
+ /** A striped sun setting behind the top edge and a horizon grid under the card. */
1108
+ export const paintSunset = (ctx, _p, w, h, settings, colors, clock, playing, index, limits, shown = settings.density) => framed(ctx, w, h, limits, () => {
1109
+ const s = paintState(settings), tier = tierOf(w, h), right = choose(index, s.seed, 1, 2) === 0, t = playing ? clock * (s.motion / 100) : 0, sun = s.style === "Sun and grid" || s.style === "Sun only", grid = s.style === "Sun and grid" || s.style === "Grid only";
1110
+ if (sun) {
1111
+ const r = tier === "hero"
1112
+ ? Math.min(30, 22 + s.scale * 0.35)
1113
+ : tier === "card"
1114
+ ? Math.min(27, 18 + s.scale * 0.3)
1115
+ : 14, cx = right ? w - r - 26 : r + 26, cy = -2;
1116
+ ctx.save();
1117
+ ctx.beginPath();
1118
+ ctx.rect(cx - r - 14, -40, r * 2 + 28, 40 + cy);
1119
+ ctx.clip();
1120
+ const g = ctx.createLinearGradient(0, cy - r, 0, cy);
1121
+ g.addColorStop(0, colors.b);
1122
+ g.addColorStop(1, colors.a);
1123
+ glow(ctx, colors.a, 10);
1124
+ ctx.fillStyle = g;
1125
+ ctx.beginPath();
1126
+ ctx.arc(cx, cy, r, Math.PI, tau);
1127
+ ctx.fill();
1128
+ ctx.shadowBlur = 0;
1129
+ // Gaps widen toward the horizon, the classic sliced sun.
1130
+ for (let i = 1; i <= 4; i++) {
1131
+ const y = cy - (r * i) / 5;
1132
+ ctx.clearRect(cx - r - 1, y - (5 - i) * 0.45 - 0.6, r * 2 + 2, (5 - i) * 0.9 + 0.6);
1133
+ }
1134
+ ctx.restore();
1135
+ }
1136
+ if (grid && tier !== "small") {
1137
+ const half = tier === "card", x0 = half && right ? w * 0.45 : 0, x1 = half && !right ? w * 0.55 : w, depth = 24, top = h + 4, phase = (t * 0.5) % 1, vx = (x0 + x1) / 2;
1138
+ ctx.strokeStyle = colors.a;
1139
+ ctx.lineWidth = 1;
1140
+ for (let i = 0; i < 5; i++) {
1141
+ const k = (i + phase) / 5, y = top + depth * k * k;
1142
+ ctx.globalAlpha = 0.12 + 0.4 * k;
1143
+ ctx.beginPath();
1144
+ ctx.moveTo(x0, y);
1145
+ ctx.lineTo(x1, y);
1146
+ ctx.stroke();
1147
+ }
1148
+ const cols = Math.max(4, Math.round((x1 - x0) / (40 - shown * 0.15)));
1149
+ ctx.globalAlpha = 0.3;
1150
+ for (let i = 0; i <= cols; i++) {
1151
+ const x = x0 + ((x1 - x0) * i) / cols;
1152
+ ctx.beginPath();
1153
+ ctx.moveTo(vx + (x - vx) * 0.6, top);
1154
+ ctx.lineTo(vx + (x - vx) * 1.3, top + depth);
1155
+ ctx.stroke();
1156
+ }
1157
+ ctx.globalAlpha = 1;
1158
+ }
1159
+ });
1160
+ /* ───────────────────────── Save Point ───────────────────────── */
1161
+ const pixelFont = (size) => `400 ${size}px "Silkscreen", "VT323", ui-monospace, monospace`;
1162
+ /** A blinking menu cursor beside the window and a sparkle on one corner. */
1163
+ export const paintMenuCursor = (ctx, _p, w, h, settings, colors, clock, playing, index, limits) => framed(ctx, w, h, limits, () => {
1164
+ const s = paintState(settings), tier = tierOf(w, h), at = choose(index, s.seed, 1, 4), cursor = s.style === "Cursor and stars" || s.style === "Cursor only", stars = s.style === "Cursor and stars" || s.style === "Stars only", blink = !playing || s.motion === 0 || clock % 1 < 0.6;
1165
+ if (cursor && blink && tier !== "small") {
1166
+ const y = Math.min(h - 14, limits.top + 12), x = -6, size = 6 + s.scale * 0.12;
1167
+ ctx.beginPath();
1168
+ ctx.moveTo(x - size * 1.6, y - size);
1169
+ ctx.lineTo(x, y);
1170
+ ctx.lineTo(x - size * 1.6, y + size);
1171
+ ctx.closePath();
1172
+ ctx.fillStyle = colors.b;
1173
+ ctx.fill();
1174
+ ctx.strokeStyle = colors.d;
1175
+ ctx.lineWidth = 1;
1176
+ ctx.stroke();
1177
+ }
1178
+ if (stars) {
1179
+ const corners = cornersOf(w, h), picks = tier === "hero" ? [at, (at + 2) % 4] : [at];
1180
+ for (const [n, k] of picks.entries()) {
1181
+ const [x, y, sx, sy] = corners[k], twinkle = playing ? 0.55 + 0.45 * Math.sin(clock * 3 + n * 2) : 1, r = (tier === "small" ? 4 : 5) + s.scale * 0.12;
1182
+ ctx.globalAlpha = twinkle;
1183
+ ctx.fillStyle = colors.c;
1184
+ star(ctx, x - sx * 9, y - sy * 9, r, r * 0.28, 4);
1185
+ ctx.fill();
1186
+ }
1187
+ ctx.globalAlpha = 1;
1188
+ }
1189
+ if (tier === "hero" && s.style !== "Plain window")
1190
+ label(ctx, "FILE 1", w - 4, -12, pixelFont(9), colors.d, "right");
1191
+ });
1192
+ /* ───────────────────────── Arcade Cabinet ───────────────────────── */
1193
+ /** Chasing marquee bulbs over the top edge and credits under the card. */
1194
+ export const paintMarquee = (ctx, _p, w, h, settings, colors, clock, playing, _index, limits, shown = settings.density) => framed(ctx, w, h, limits, () => {
1195
+ const s = paintState(settings), tier = tierOf(w, h), bulbs = s.style === "Bulbs and credits" || s.style === "Bulbs only", credits = s.style === "Bulbs and credits" || s.style === "Credits only";
1196
+ if (bulbs) {
1197
+ const gap = 18 - shown * 0.05, n = Math.max(3, Math.floor((w - 16) / gap)), step = playing && s.motion > 0 ? Math.floor(clock * (2 + s.motion / 25)) : 0;
1198
+ for (let i = 0; i <= n; i++) {
1199
+ const x = 8 + ((w - 16) * i) / n, lit = (i + step) % 3 === 0;
1200
+ if (lit)
1201
+ glow(ctx, colors.b, 6);
1202
+ dot(ctx, x, -8, lit ? 2.4 : 1.8, lit ? colors.b : colors.d);
1203
+ ctx.shadowBlur = 0;
1204
+ }
1205
+ }
1206
+ if (credits && tier !== "small") {
1207
+ label(ctx, "1UP 004200", 0, h + 15, pixelFont(9), colors.a);
1208
+ if (tier === "hero" && (!playing || s.motion === 0 || clock % 1.2 < 0.8))
1209
+ label(ctx, "INSERT COIN", w, h + 15, pixelFont(9), colors.b, "right");
1210
+ }
1211
+ });
1140
1212
  export const launchPainters = {
1141
1213
  crt: paintPhosphor,
1142
1214
  panes: paintPanes,
@@ -1153,4 +1225,7 @@ export const launchPainters = {
1153
1225
  gulal: paintGulal,
1154
1226
  knit: paintKnit,
1155
1227
  liquid: paintLiquid,
1228
+ sunset: paintSunset,
1229
+ menu: paintMenuCursor,
1230
+ marquee: paintMarquee,
1156
1231
  };
@@ -35,7 +35,7 @@ declare function paintState(settings: OrganicSettings): {
35
35
  weight: number;
36
36
  motion: number;
37
37
  seed: number;
38
- style: "Woodland mix" | "Wild ivy" | "Fern grove" | "Willow canopy" | "Oak & moss" | "Spring growth" | "Ginkgo fans" | "Maple grove" | "Eucalyptus" | "Olive branch" | "Clover meadow" | "Wildflower hedge" | "Piped petals" | "Twisted rope" | "Candy pebbles" | "Ribbon folds" | "Tidal ribbons" | "Seafoam beads" | "Glass droplets" | "Quiet currents" | "Fine filigree" | "Art deco" | "Hammered edge" | "Quiet gilding" | "Dimensioned" | "Ruler ticks" | "Construction lines" | "Quiet draft" | "Misprint" | "Halftone" | "Taped corners" | "Clean stamp" | "Tube outline" | "Broken tubes" | "Sign shapes" | "Low hum" | "Bamboo" | "Plum blossom" | "Dry brush" | "Ensō" | "Line map" | "Station roundels" | "Platform edge" | "Quiet line" | "Box corners" | "Status line" | "Raster ticks" | "Quiet cursor" | "Pane titles" | "Key hints" | "Split panes" | "Bare panes" | "Crop marks" | "Red block" | "Grid ticks" | "Bare grid" | "Corner tiles" | "Border run" | "Floral rosette" | "Single tile" | "Tie holes" | "Stencil marks" | "Board seams" | "Bare concrete" | "Starbursts" | "Boomerangs" | "Orbit atoms" | "Lone star" | "Target brackets" | "Telemetry" | "Reticle" | "Clean brackets" | "Hazard stripes" | "Rivet seams" | "Cargo stencils" | "Clean plate" | "Orbit rings" | "Gold foil" | "Star field" | "Single orbit" | "Rangoli corners" | "Diya row" | "Marigold string" | "Single diya" | "Lanterns" | "Scallop edge" | "Cloud scrolls" | "Single lantern" | "Bunting" | "Marigold garland" | "Paper confetti" | "Short string" | "Powder clouds" | "Splashes" | "Speckle dust" | "Soft haze" | "Fair Isle band" | "Snowflakes" | "Cable stitch" | "Plain rib" | "Specular rim" | "Refraction" | "Light catch" | "Clear";
38
+ style: "Woodland mix" | "Wild ivy" | "Fern grove" | "Willow canopy" | "Oak & moss" | "Spring growth" | "Ginkgo fans" | "Maple grove" | "Eucalyptus" | "Olive branch" | "Clover meadow" | "Wildflower hedge" | "Piped petals" | "Twisted rope" | "Candy pebbles" | "Ribbon folds" | "Tidal ribbons" | "Seafoam beads" | "Glass droplets" | "Quiet currents" | "Fine filigree" | "Art deco" | "Hammered edge" | "Quiet gilding" | "Dimensioned" | "Ruler ticks" | "Construction lines" | "Quiet draft" | "Misprint" | "Halftone" | "Taped corners" | "Clean stamp" | "Tube outline" | "Broken tubes" | "Sign shapes" | "Low hum" | "Bamboo" | "Plum blossom" | "Dry brush" | "Ensō" | "Line map" | "Station roundels" | "Platform edge" | "Quiet line" | "Box corners" | "Status line" | "Raster ticks" | "Quiet cursor" | "Pane titles" | "Key hints" | "Split panes" | "Bare panes" | "Crop marks" | "Red block" | "Grid ticks" | "Bare grid" | "Corner tiles" | "Border run" | "Floral rosette" | "Single tile" | "Tie holes" | "Stencil marks" | "Board seams" | "Bare concrete" | "Starbursts" | "Boomerangs" | "Orbit atoms" | "Lone star" | "Target brackets" | "Telemetry" | "Reticle" | "Clean brackets" | "Hazard stripes" | "Rivet seams" | "Cargo stencils" | "Clean plate" | "Orbit rings" | "Gold foil" | "Star field" | "Single orbit" | "Rangoli corners" | "Diya row" | "Marigold string" | "Single diya" | "Lanterns" | "Scallop edge" | "Cloud scrolls" | "Single lantern" | "Bunting" | "Marigold garland" | "Paper confetti" | "Short string" | "Powder clouds" | "Splashes" | "Speckle dust" | "Soft haze" | "Fair Isle band" | "Snowflakes" | "Cable stitch" | "Plain rib" | "Specular rim" | "Refraction" | "Light catch" | "Clear" | "Sun and grid" | "Sun only" | "Grid only" | "Bare coast" | "Cursor and stars" | "Cursor only" | "Stars only" | "Plain window" | "Bulbs and credits" | "Bulbs only" | "Credits only" | "Dark cabinet";
39
39
  canopy: "Scattered trails" | "Corner cascades" | "Overhead boughs" | "Long tendrils" | "Open crown";
40
40
  stem: "Twining vines" | "Woody branches" | "Tender shoots" | "Mossy roots";
41
41
  };