@dixy-ui/themes 0.1.0-beta.3 → 0.1.0-beta.5

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 (92) hide show
  1. package/README.md +3 -3
  2. package/dist/ansi-panes.css +75 -8
  3. package/dist/arcade/ornament.d.ts +3 -0
  4. package/dist/arcade/ornament.js +8 -0
  5. package/dist/arcade-cabinet.css +1084 -0
  6. package/dist/art-deco.css +75 -8
  7. package/dist/atomic-lounge.css +79 -8
  8. package/dist/authoring.d.ts +8 -8
  9. package/dist/azulejo-tile.css +79 -8
  10. package/dist/blueprint.css +75 -8
  11. package/dist/brutalist-concrete.css +80 -9
  12. package/dist/bytes.json +97 -62
  13. package/dist/candy-clay.css +81 -8
  14. package/dist/catalog.d.ts +294 -0
  15. package/dist/catalog.js +301 -0
  16. package/dist/character.d.ts +1 -1
  17. package/dist/character.js +6 -0
  18. package/dist/chrome.js +13 -3
  19. package/dist/compile-css.js +27 -7
  20. package/dist/dispatch.css +729 -0
  21. package/dist/diwali-lights.css +79 -8
  22. package/dist/documents.d.ts +115 -7
  23. package/dist/documents.js +10 -2
  24. package/dist/foundations.d.ts +8 -0
  25. package/dist/foundations.js +10 -0
  26. package/dist/frame-styles.d.ts +3 -0
  27. package/dist/frame-styles.js +6 -0
  28. package/dist/freighter.css +141 -18
  29. package/dist/glass-ui.css +75 -8
  30. package/dist/holi-colours.css +79 -8
  31. package/dist/ledger.css +564 -0
  32. package/dist/living-forest.css +81 -8
  33. package/dist/mainframe.css +762 -0
  34. package/dist/manifests.d.ts +144 -144
  35. package/dist/material-light.css +79 -8
  36. package/dist/neon-night-market.css +79 -8
  37. package/dist/ocean-depths.css +81 -8
  38. package/dist/orbital-hud.css +141 -18
  39. package/dist/organic-paint-launch.d.ts +9 -0
  40. package/dist/organic-paint-launch.js +121 -46
  41. package/dist/organic-paint.d.ts +1 -1
  42. package/dist/organic-settings.d.ts +109 -25
  43. package/dist/organic-settings.js +49 -0
  44. package/dist/papel-picado.css +79 -8
  45. package/dist/phosphor-terminal.css +79 -8
  46. package/dist/prism-glass.css +75 -8
  47. package/dist/recipes.d.ts +162 -0
  48. package/dist/recipes.js +164 -0
  49. package/dist/resolve.d.ts +18 -2
  50. package/dist/retro-os.css +75 -8
  51. package/dist/riso-zine.css +75 -8
  52. package/dist/save-point.css +946 -0
  53. package/dist/savepoint/ornament.d.ts +3 -0
  54. package/dist/savepoint/ornament.js +8 -0
  55. package/dist/scrapbook.css +75 -8
  56. package/dist/signal.css +561 -0
  57. package/dist/skins/arcade-cabinet.d.ts +68 -0
  58. package/dist/skins/arcade-cabinet.js +68 -0
  59. package/dist/skins/dispatch.d.ts +57 -0
  60. package/dist/skins/dispatch.js +57 -0
  61. package/dist/skins/ledger.d.ts +57 -0
  62. package/dist/skins/ledger.js +57 -0
  63. package/dist/skins/mainframe.d.ts +57 -0
  64. package/dist/skins/mainframe.js +57 -0
  65. package/dist/skins/save-point.d.ts +68 -0
  66. package/dist/skins/save-point.js +68 -0
  67. package/dist/skins/signal.d.ts +57 -0
  68. package/dist/skins/signal.js +57 -0
  69. package/dist/skins/sunset-drive.d.ts +68 -0
  70. package/dist/skins/sunset-drive.js +68 -0
  71. package/dist/solar-sail.css +75 -8
  72. package/dist/starters-launch.d.ts +24 -0
  73. package/dist/starters-launch.js +343 -0
  74. package/dist/studio-hardware.css +79 -8
  75. package/dist/sumi-ink.css +75 -8
  76. package/dist/sunset/ornament.d.ts +3 -0
  77. package/dist/sunset/ornament.js +8 -0
  78. package/dist/sunset-drive.css +1022 -0
  79. package/dist/swiss-poster.css +145 -18
  80. package/dist/tactile-atelier.css +81 -8
  81. package/dist/theme-document.schema.json +34 -6
  82. package/dist/theme-fix.d.ts +11 -0
  83. package/dist/theme-fix.js +32 -0
  84. package/dist/theme-fixes.d.ts +2 -0
  85. package/dist/theme-fixes.js +1 -0
  86. package/dist/theme-schema.d.ts +6 -6
  87. package/dist/theme-schema.js +2 -0
  88. package/dist/tokyo-transit.css +79 -8
  89. package/dist/winamp-signal.css +79 -8
  90. package/dist/y2k-titanium.css +147 -18
  91. package/dist/yule-knit.css +75 -8
  92. package/package.json +50 -3
@@ -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)),
@@ -170,11 +175,9 @@
170
175
  }
171
176
  }
172
177
  @layer dixy.components {
173
- .dixy-theme[data-dixy-skin="dixy/orbital-hud"] {
178
+ .dixy-theme[data-dixy-skin="dixy/orbital-hud"]:not(.dixy-portal-scope) {
174
179
  position: relative;
175
180
  isolation: isolate;
176
- }
177
- .dixy-theme[data-dixy-skin="dixy/orbital-hud"]:not(.dixy-portal-scope) {
178
181
  background-color: var(--dixy-skin-ground);
179
182
  background-image: var(--dixy-skin-canvas);
180
183
  }
@@ -278,6 +281,28 @@
278
281
  ):not([data-variant="solid"]):not([data-variant="soft"]) {
279
282
  background-color: var(--dixy-skin-surface);
280
283
  }
284
+ :is(
285
+ .dixy-dialog-popup,
286
+ .dixy-alert-dialog-popup,
287
+ .dixy-drawer-popup,
288
+ .dixy-popover-popup,
289
+ .dixy-preview-card-popup,
290
+ .dixy-tooltip-popup,
291
+ .dixy-select-popup,
292
+ .dixy-combobox-popup,
293
+ .dixy-autocomplete-popup,
294
+ .dixy-menu-popup,
295
+ .dixy-context-menu-popup,
296
+ .dixy-navigation-menu-popup
297
+ )[data-dixy-skin="dixy/orbital-hud"]:not([data-dixy-material-override]):not(
298
+ :where([data-dixy-material-override]) *
299
+ ):not([data-variant="solid"]):not([data-variant="soft"]) {
300
+ background-color: color-mix(
301
+ in srgb,
302
+ var(--dixy-color-background-surface-raised) 94%,
303
+ var(--dixy-skin-surface)
304
+ );
305
+ }
281
306
  :is(
282
307
  .dixy-input,
283
308
  .dixy-textarea,
@@ -310,7 +335,7 @@
310
335
  :where([data-dixy-material-override]) *
311
336
  ) {
312
337
  border-width: var(--dixy-input-border-width);
313
- border-color: var(--dixy-skin-edge);
338
+ border-color: var(--dixy-skin-field-edge);
314
339
  background-image: var(--dixy-skin-paint);
315
340
  background-color: var(--dixy-color-background-canvas);
316
341
  background-size:
@@ -320,6 +345,43 @@
320
345
  100% 100%;
321
346
  box-shadow: var(--dixy-skin-inset);
322
347
  }
348
+ /* materials.css styles [data-dixy-visual] surfaces at 0,5,0. Match the
349
+ skin's paint to that so its image and grain size always land together;
350
+ otherwise the material image is tiled at the grain size (stripes). */
351
+ :is(
352
+ .dixy-card,
353
+ .dixy-box,
354
+ .dixy-dialog-popup,
355
+ .dixy-alert-dialog-popup,
356
+ .dixy-drawer-popup,
357
+ .dixy-popover-popup,
358
+ .dixy-preview-card-popup,
359
+ .dixy-tooltip-popup,
360
+ .dixy-select-popup,
361
+ .dixy-combobox-popup,
362
+ .dixy-autocomplete-popup,
363
+ .dixy-menu-popup,
364
+ .dixy-context-menu-popup,
365
+ .dixy-navigation-menu-popup,
366
+ .dixy-toast-root,
367
+ .dixy-alert,
368
+ .dixy-list-box,
369
+ .dixy-calendar,
370
+ .dixy-range-calendar,
371
+ .dixy-input,
372
+ .dixy-textarea,
373
+ .dixy-native-select,
374
+ .dixy-select-trigger,
375
+ .dixy-input-group,
376
+ .dixy-combobox-input-group,
377
+ .dixy-autocomplete-input-group,
378
+ .dixy-number-field-group,
379
+ .dixy-otp-field-input
380
+ )[data-dixy-skin="dixy/orbital-hud"]:not([data-dixy-material-override]):not(
381
+ :where([data-dixy-material-override]) *
382
+ )[data-dixy-visual][data-material]:not([data-disabled]) {
383
+ background-image: var(--dixy-skin-paint);
384
+ }
323
385
  :is(.dixy-button)[data-dixy-skin="dixy/orbital-hud"]:not(
324
386
  [data-dixy-material-override]
325
387
  ):not(:where([data-dixy-material-override]) *):is(
@@ -350,14 +412,19 @@
350
412
  border: 1px solid var(--dixy-skin-edge);
351
413
  box-shadow: var(--dixy-skin-inset);
352
414
  }
415
+ /* 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). */
353
416
  :is(.dixy-tabs-indicator)[data-dixy-skin="dixy/orbital-hud"]:not(
354
417
  [data-dixy-material-override]
355
418
  ):not(:where([data-dixy-material-override]) *) {
356
- background-image: var(--dixy-skin-finish);
357
419
  box-shadow: var(--dixy-skin-control-relief);
358
420
  border: 1px solid var(--dixy-skin-edge);
359
421
  }
360
- /* 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). */
422
+ :is(.dixy-tabs-indicator)[data-dixy-skin="dixy/orbital-hud"]:not(
423
+ [data-dixy-material-override]
424
+ ):not(:where([data-dixy-material-override]) *):not([data-variant="line"]) {
425
+ background-image: none;
426
+ }
427
+ /* Toggle groups read like tabs: a sunken track, the selected toggle a raised, flat thumb. */
361
428
  .dixy-theme[data-dixy-skin="dixy/orbital-hud"]
362
429
  .dixy-toggle-group:is([data-attached], [data-segmented]):not(
363
430
  [data-dixy-material-override]
@@ -372,7 +439,7 @@
372
439
  [data-variant="solid"]
373
440
  )[data-pressed]:not([data-disabled]) {
374
441
  background-color: var(--dixy-color-background-surface-raised);
375
- background-image: var(--dixy-skin-selected, var(--dixy-skin-thumb));
442
+ background-image: none;
376
443
  box-shadow: var(--dixy-skin-control-relief);
377
444
  border-color: var(--dixy-skin-edge);
378
445
  color: var(--dixy-color-text-primary);
@@ -876,16 +943,72 @@
876
943
  :is(.dixy-card, .dixy-dialog-popup)[data-dixy-skin="dixy/orbital-hud"] {
877
944
  border-radius: 0;
878
945
  --dixy-chrome-bar-radius: 0px;
879
- clip-path: polygon(
880
- 10px 0,
881
- calc(100% - 10px) 0,
882
- 100% 10px,
883
- 100% calc(100% - 10px),
884
- calc(100% - 10px) 100%,
885
- 10px 100%,
886
- 0 calc(100% - 10px),
887
- 0 10px
888
- );
946
+ }
947
+ @supports (corner-shape: bevel) {
948
+ .dixy-theme[data-dixy-skin="dixy/orbital-hud"]
949
+ :is(.dixy-card, .dixy-dialog-popup)[data-dixy-skin="dixy/orbital-hud"] {
950
+ border-radius: 10px;
951
+ corner-shape: bevel;
952
+ }
953
+ }
954
+ @supports not (corner-shape: bevel) {
955
+ .dixy-theme[data-dixy-skin="dixy/orbital-hud"]
956
+ :is(.dixy-card, .dixy-dialog-popup)[data-dixy-skin="dixy/orbital-hud"] {
957
+ clip-path: polygon(
958
+ 10px 0,
959
+ calc(100% - 10px) 0,
960
+ 100% 10px,
961
+ 100% calc(100% - 10px),
962
+ calc(100% - 10px) 100%,
963
+ 10px 100%,
964
+ 0 calc(100% - 10px),
965
+ 0 10px
966
+ );
967
+ }
968
+ .dixy-theme[data-dixy-skin="dixy/orbital-hud"]
969
+ :is(
970
+ .dixy-card,
971
+ .dixy-dialog-popup
972
+ )[data-dixy-skin="dixy/orbital-hud"]::after {
973
+ content: "";
974
+ position: absolute;
975
+ inset: calc(var(--dixy-skin-frame-width, 1px) * -1);
976
+ pointer-events: none;
977
+ z-index: 1;
978
+ background:
979
+ linear-gradient(
980
+ to bottom right,
981
+ transparent 50%,
982
+ var(--dixy-skin-edge) 50%
983
+ calc(50% + var(--dixy-skin-frame-width, 1px)),
984
+ transparent calc(50% + var(--dixy-skin-frame-width, 1px))
985
+ )
986
+ top left/10px 10px no-repeat,
987
+ linear-gradient(
988
+ to bottom left,
989
+ transparent 50%,
990
+ var(--dixy-skin-edge) 50%
991
+ calc(50% + var(--dixy-skin-frame-width, 1px)),
992
+ transparent calc(50% + var(--dixy-skin-frame-width, 1px))
993
+ )
994
+ top right/10px 10px no-repeat,
995
+ linear-gradient(
996
+ to top right,
997
+ transparent 50%,
998
+ var(--dixy-skin-edge) 50%
999
+ calc(50% + var(--dixy-skin-frame-width, 1px)),
1000
+ transparent calc(50% + var(--dixy-skin-frame-width, 1px))
1001
+ )
1002
+ bottom left/10px 10px no-repeat,
1003
+ linear-gradient(
1004
+ to top left,
1005
+ transparent 50%,
1006
+ var(--dixy-skin-edge) 50%
1007
+ calc(50% + var(--dixy-skin-frame-width, 1px)),
1008
+ transparent calc(50% + var(--dixy-skin-frame-width, 1px))
1009
+ )
1010
+ bottom right/10px 10px no-repeat;
1011
+ }
889
1012
  }
890
1013
  .dixy-theme[data-dixy-skin="dixy/orbital-hud"]
891
1014
  :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
  };