shadertown 0.2.0 → 0.4.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.
package/dist/registry.js CHANGED
@@ -1068,6 +1068,584 @@ export const shaderRegistry = {
1068
1068
  colorC: color("Light", "#c9f6ff", "The caustic web itself."),
1069
1069
  },
1070
1070
  },
1071
+ radiance: {
1072
+ name: "Radiance",
1073
+ tagline: "Lamps in a dark room, traced to whatever they light",
1074
+ description: "Global illumination taken the short way round. There is no light pass and no probe grid to merge: the room is a handful of signed distances, so every pixel fans rays into it and marches each one back to its source. That buys a true shadow — hard where a lamp is small, soft where it is near, and cast by bars that turn every frame. The pointer is the brightest lamp in the room, so moving it moves every shadow.",
1075
+ family: "spatial",
1076
+ tags: ["light", "shadow", "dark", "pointer"],
1077
+ cost: "heavy",
1078
+ controls: {
1079
+ ...base,
1080
+ speed: { ...base.speed, default: 0.7, max: 2 },
1081
+ scale: { ...base.scale, default: 1 },
1082
+ intensity: { ...base.intensity, default: 1.1 },
1083
+ grain: { ...base.grain, default: 0.02 },
1084
+ vignette: { ...base.vignette, default: 0.4 },
1085
+ rays: {
1086
+ slot: "a",
1087
+ label: "Rays",
1088
+ min: 3,
1089
+ max: 48,
1090
+ step: 1,
1091
+ default: 24,
1092
+ hint: "Directions each pixel samples. Low is streaky and cheap; high is smooth and is what costs.",
1093
+ },
1094
+ reach: {
1095
+ slot: "b",
1096
+ label: "Reach",
1097
+ min: 0.05,
1098
+ max: 1.5,
1099
+ step: 0.01,
1100
+ default: 0.42,
1101
+ hint: "How far a lamp throws before the inverse square eats it.",
1102
+ },
1103
+ walls: {
1104
+ slot: "c",
1105
+ label: "Walls",
1106
+ min: 0,
1107
+ max: 2,
1108
+ step: 0.01,
1109
+ default: 1,
1110
+ hint: "Size of the bars that block light. 0 removes them and the shadows with them.",
1111
+ },
1112
+ skylight: {
1113
+ slot: "d",
1114
+ label: "Skylight",
1115
+ min: 0,
1116
+ max: 0.5,
1117
+ step: 0.005,
1118
+ default: 0.06,
1119
+ hint: "Ambient floor, so the bars read as objects in a room rather than holes cut out of it.",
1120
+ },
1121
+ },
1122
+ colors: {
1123
+ colorA: color("Room", "#0b1020", "The ambient the skylight adds everywhere."),
1124
+ colorB: color("Warm", "#f59e0b", "The middle lamp of the three."),
1125
+ colorC: color("Cool", "#38bdf8", "The far lamp, and the colour under the pointer."),
1126
+ },
1127
+ },
1128
+ lumen: {
1129
+ name: "Lumen",
1130
+ tagline: "A lattice of dots, each one a lamp and a wall at once",
1131
+ description: "Every dot gives light and blocks it, so the field shadows itself. The lattice is what makes that affordable: all the dots are the same size, so the nearest one to any point is simply the nearest lattice node and the distance to the whole field is exact rather than estimated. A ray crosses a dozen cells in a few steps, which is why the shadows run the width of the frame. A wave decides which dots are lit; the dark ones stay in the picture as grey, and keep casting.",
1132
+ family: "geometric",
1133
+ tags: ["light", "grid", "loader", "dark"],
1134
+ cost: "heavy",
1135
+ controls: {
1136
+ ...base,
1137
+ speed: { ...base.speed, default: 0.6, max: 2 },
1138
+ scale: { ...base.scale, default: 1, min: 0.3, max: 2.5 },
1139
+ intensity: { ...base.intensity, default: 1.1 },
1140
+ grain: { ...base.grain, default: 0.02 },
1141
+ vignette: { ...base.vignette, default: 0.45 },
1142
+ rays: {
1143
+ slot: "a",
1144
+ label: "Rays",
1145
+ min: 3,
1146
+ max: 40,
1147
+ step: 1,
1148
+ default: 20,
1149
+ hint: "Directions each pixel samples. Low is streaky and cheap; high is smooth and is what costs.",
1150
+ },
1151
+ dot: {
1152
+ slot: "b",
1153
+ label: "Dot size",
1154
+ min: 0.02,
1155
+ max: 0.45,
1156
+ step: 0.005,
1157
+ default: 0.16,
1158
+ hint: "Radius of a dot, in cells. Big dots shadow more of the lattice than they light.",
1159
+ },
1160
+ lit: {
1161
+ slot: "c",
1162
+ label: "Lit",
1163
+ min: 0,
1164
+ max: 1,
1165
+ step: 0.01,
1166
+ default: 0.35,
1167
+ hint: "Share of the lattice that is emitting at any moment.",
1168
+ },
1169
+ reach: {
1170
+ slot: "d",
1171
+ label: "Reach",
1172
+ min: 0.3,
1173
+ max: 8,
1174
+ step: 0.05,
1175
+ default: 1.6,
1176
+ hint: "How many cells a dot throws across before the inverse square eats it.",
1177
+ },
1178
+ },
1179
+ colors: {
1180
+ colorA: color("Body", "#1e293b", "The grey of a dot that is not lit."),
1181
+ colorB: color("Mid", "#7c3aed", "The middle of the ramp the lit dots draw from."),
1182
+ colorC: color("Light", "#e0f2fe", "The brightest dots."),
1183
+ },
1184
+ },
1185
+ beacon: {
1186
+ name: "Beacon",
1187
+ tagline: "A triangle of LEDs on a floor that catches what they throw",
1188
+ description: "The glow is computed, never blurred. Each edge contributes an exponential falloff off the line itself and a second off the emitter nearest along it, both analytic, so the halo keeps its shape at any zoom and the LEDs stay round at any pixel density. The floor is the same light field read at the mirrored point, widened with depth — a reflection for the price of a second lookup. Give two edges different colours and the plate picks up both.",
1189
+ family: "geometric",
1190
+ tags: ["hero", "neon", "dark", "pointer"],
1191
+ cost: "light",
1192
+ controls: {
1193
+ ...base,
1194
+ speed: { ...base.speed, default: 0.8, max: 2.5 },
1195
+ scale: { ...base.scale, default: 1, min: 0.3, max: 2 },
1196
+ intensity: { ...base.intensity, default: 1 },
1197
+ grain: { ...base.grain, default: 0.02 },
1198
+ vignette: { ...base.vignette, default: 0.35 },
1199
+ leds: {
1200
+ slot: "a",
1201
+ label: "LEDs",
1202
+ min: 2,
1203
+ max: 40,
1204
+ step: 1,
1205
+ default: 13,
1206
+ hint: "Emitters per edge. Few reads as a marquee, many as a strip.",
1207
+ },
1208
+ bleed: {
1209
+ slot: "b",
1210
+ label: "Bleed",
1211
+ min: 0.004,
1212
+ max: 0.2,
1213
+ step: 0.002,
1214
+ default: 0.04,
1215
+ hint: "How far light carries off an edge. Small is a hard point, large is a wash.",
1216
+ },
1217
+ reflection: {
1218
+ slot: "c",
1219
+ label: "Reflection",
1220
+ min: 0,
1221
+ max: 1,
1222
+ step: 0.01,
1223
+ default: 0.6,
1224
+ hint: "How much of the triangle the floor gives back.",
1225
+ },
1226
+ chase: {
1227
+ slot: "d",
1228
+ label: "Chase",
1229
+ min: 0,
1230
+ max: 1,
1231
+ step: 0.01,
1232
+ default: 0.7,
1233
+ hint: "0 holds every LED steady, 1 runs a pulse around the perimeter.",
1234
+ },
1235
+ },
1236
+ colors: {
1237
+ colorA: color("Room", "#0a0a12", "The wall, the plate and the floor."),
1238
+ colorB: color("Edge", "#22d3ee", "The first edge, and half of the third."),
1239
+ colorC: color("Highlight", "#f472b6", "The second edge, the pointer's lamp, and half of the third."),
1240
+ },
1241
+ },
1242
+ orb: {
1243
+ name: "Orb",
1244
+ tagline: "A liquid blob suspended in a sphere of glass",
1245
+ description: "The glass is analytic and the blob is marched, which is the only way this fits in one pass: a sphere is two roots of a quadratic, so entry, exit and both normals are exact and the ray only has to be stepped between them. The body inside is lobes melted together with a smooth minimum, which is what keeps it reading as one liquid thing rather than as balls in a bag. Nothing has a hard edge — the silhouette is antialiased off a field that stays smooth across the frame, and the blob off its own distance. The pointer turns the studio, so the highlights travel across the glass.",
1246
+ family: "organic",
1247
+ tags: ["glass", "hero", "3d", "pointer"],
1248
+ cost: "heavy",
1249
+ controls: {
1250
+ ...base,
1251
+ speed: { ...base.speed, default: 0.45, max: 2 },
1252
+ scale: { ...base.scale, default: 1, min: 0.4, max: 2 },
1253
+ intensity: { ...base.intensity, default: 1 },
1254
+ grain: { ...base.grain, default: 0.015 },
1255
+ vignette: { ...base.vignette, default: 0.4 },
1256
+ warp: {
1257
+ ...warp,
1258
+ label: "Refraction",
1259
+ min: 0,
1260
+ max: 2,
1261
+ default: 0.75,
1262
+ hint: "How hard the glass bends a ray. 0 is a clear shell, 2 is a heavy lens.",
1263
+ },
1264
+ core: {
1265
+ slot: "a",
1266
+ label: "Core",
1267
+ min: 0,
1268
+ max: 1,
1269
+ step: 0.01,
1270
+ default: 0.8,
1271
+ hint: "0 is one round drop. 1 pulls the lobes apart and ripples the skin, so the body churns and pinches.",
1272
+ },
1273
+ dispersion: {
1274
+ slot: "b",
1275
+ label: "Dispersion",
1276
+ min: 0,
1277
+ max: 1,
1278
+ step: 0.01,
1279
+ default: 0.45,
1280
+ hint: "How far the red and blue ends part on the way out of the glass.",
1281
+ },
1282
+ lobes: {
1283
+ slot: "c",
1284
+ label: "Lobes",
1285
+ min: 1,
1286
+ max: 8,
1287
+ step: 1,
1288
+ default: 5,
1289
+ hint: "Bodies melted into the blob. Each one costs a distance every step of the march.",
1290
+ },
1291
+ studio: {
1292
+ slot: "d",
1293
+ label: "Studio",
1294
+ min: 0,
1295
+ max: 2,
1296
+ step: 0.01,
1297
+ default: 1,
1298
+ hint: "Strength of the two softboxes the glass reflects.",
1299
+ },
1300
+ },
1301
+ colors: {
1302
+ colorA: color("Backdrop", "#171430", "The seamless behind the glass."),
1303
+ colorB: color("Glass", "#5eead4", "What the glass takes out of a ray as it passes through."),
1304
+ colorC: color("Key", "#fff7ed", "The key light, its rim, and the sheen on the core."),
1305
+ },
1306
+ },
1307
+ eyes: {
1308
+ name: "Eyes",
1309
+ tagline: "A wall of eyes that look where the pointer goes",
1310
+ description: "Each cell is one eye: an almond of sclera, an iris that travels toward the cursor, a lid that blinks on its own clock. The gaze is the interactive part — move the pointer and the whole wall follows it — but the idle frame carries itself: with no pointer the eyes wander, each on its own phase, so a card is never a grid of dead centres.",
1311
+ family: "geometric",
1312
+ tags: ["interactive", "pointer", "playful"],
1313
+ cost: "light",
1314
+ controls: {
1315
+ ...base,
1316
+ speed: { ...base.speed, default: 1, max: 2.5 },
1317
+ scale: { ...base.scale, default: 1, min: 0.4, max: 2.5 },
1318
+ grain: { ...base.grain, default: 0.02 },
1319
+ vignette: { ...base.vignette, default: 0.3 },
1320
+ follow: {
1321
+ slot: "a",
1322
+ label: "Follow",
1323
+ min: 0,
1324
+ max: 1,
1325
+ step: 0.01,
1326
+ default: 0.85,
1327
+ hint: "0 lets every eye wander on its own, 1 locks them all onto the pointer.",
1328
+ },
1329
+ blink: {
1330
+ slot: "b",
1331
+ label: "Blink",
1332
+ min: 0,
1333
+ max: 3,
1334
+ step: 0.01,
1335
+ default: 1,
1336
+ hint: "Blink rate. 0 stares, which is worse.",
1337
+ },
1338
+ iris: {
1339
+ slot: "c",
1340
+ label: "Iris",
1341
+ min: 0.4,
1342
+ max: 1.6,
1343
+ step: 0.01,
1344
+ default: 1,
1345
+ hint: "Size of the iris inside the almond.",
1346
+ },
1347
+ pupil: {
1348
+ slot: "d",
1349
+ label: "Pupil",
1350
+ min: 0.15,
1351
+ max: 0.9,
1352
+ step: 0.01,
1353
+ default: 0.45,
1354
+ hint: "Pupil as a share of the iris. Big is dark-adapted, or alarmed.",
1355
+ },
1356
+ },
1357
+ colors: {
1358
+ colorA: color("Wall", "#151221", "The wall the eyes are set into."),
1359
+ colorB: color("Iris", "#38bdf8", "The iris, shaded darker at its rim."),
1360
+ colorC: color("Sclera", "#ece7dc", "The white of the eye."),
1361
+ },
1362
+ },
1363
+ tesla: {
1364
+ name: "Tesla",
1365
+ tagline: "A plasma globe whose filament reaches for your finger",
1366
+ description: "The bolts are analytic: each one is a base angle plus a noise wiggle that grows with radius, so a pixel's distance to a filament is its angular error times its radius — no marching, no segments. Bolts re-roll their heading on a coarse clock, which is the jump that reads as electricity. One bolt always seeks the pointer, clamped to the inside of the glass, exactly what the real toy does when a hand touches the dome.",
1367
+ family: "organic",
1368
+ tags: ["interactive", "pointer", "dark", "hero"],
1369
+ cost: "medium",
1370
+ controls: {
1371
+ ...base,
1372
+ speed: { ...base.speed, default: 1, max: 2.5 },
1373
+ scale: { ...base.scale, default: 1, min: 0.4, max: 2 },
1374
+ grain: { ...base.grain, default: 0.02 },
1375
+ vignette: { ...base.vignette, default: 0.4 },
1376
+ warp: {
1377
+ ...warp,
1378
+ label: "Wiggle",
1379
+ min: 0,
1380
+ max: 2,
1381
+ default: 0.8,
1382
+ hint: "How hard a filament thrashes as it travels. 0 is spokes.",
1383
+ },
1384
+ bolts: {
1385
+ slot: "a",
1386
+ label: "Bolts",
1387
+ min: 2,
1388
+ max: 8,
1389
+ step: 1,
1390
+ default: 5,
1391
+ hint: "Free-roaming filaments, not counting the one that seeks the pointer.",
1392
+ },
1393
+ glow: {
1394
+ slot: "b",
1395
+ label: "Glow",
1396
+ min: 0.3,
1397
+ max: 2,
1398
+ step: 0.01,
1399
+ default: 1,
1400
+ hint: "Width of the halo riding each filament.",
1401
+ },
1402
+ flicker: {
1403
+ slot: "c",
1404
+ label: "Flicker",
1405
+ min: 0,
1406
+ max: 2,
1407
+ step: 0.01,
1408
+ default: 1,
1409
+ hint: "How often the bolts re-roll their heading.",
1410
+ },
1411
+ haze: {
1412
+ slot: "d",
1413
+ label: "Haze",
1414
+ min: 0,
1415
+ max: 2,
1416
+ step: 0.01,
1417
+ default: 0.8,
1418
+ hint: "The gas glow filling the globe behind the filaments.",
1419
+ },
1420
+ },
1421
+ colors: {
1422
+ colorA: color("Gas", "#1e1b4b", "The unexcited gas inside the globe."),
1423
+ colorB: color("Halo", "#a78bfa", "The glow around each filament, and the glass rim."),
1424
+ colorC: color("Core", "#f5f3ff", "The filament cores and the electrode."),
1425
+ },
1426
+ },
1427
+ magnet: {
1428
+ name: "Magnet",
1429
+ tagline: "Field lines of three charges, one riding the pointer",
1430
+ description: "Nothing here is traced. The stream function of a point charge is its angle and its potential is the log of its distance, so field lines are level sets of one sum and equipotentials of the other — the whole plot is two sin() calls over closed forms, live at any resolution. The pointer carries the third charge; its slider steps by whole numbers because the picture's continuity across the branch cuts depends on the charges being integers.",
1431
+ family: "geometric",
1432
+ tags: ["interactive", "pointer", "diagram"],
1433
+ cost: "light",
1434
+ controls: {
1435
+ ...base,
1436
+ speed: { ...base.speed, default: 1, max: 2.5 },
1437
+ scale: { ...base.scale, default: 1, min: 0.4, max: 2.5 },
1438
+ grain: { ...base.grain, default: 0.02 },
1439
+ vignette: { ...base.vignette, default: 0.35 },
1440
+ lines: {
1441
+ slot: "a",
1442
+ label: "Lines",
1443
+ min: 4,
1444
+ max: 48,
1445
+ step: 1,
1446
+ default: 14,
1447
+ hint: "Field lines leaving each unit of charge.",
1448
+ },
1449
+ flow: {
1450
+ slot: "b",
1451
+ label: "Flow",
1452
+ min: 0,
1453
+ max: 3,
1454
+ step: 0.01,
1455
+ default: 1,
1456
+ hint: "The phase sliding along the lines. 0 is a printed diagram.",
1457
+ },
1458
+ charge: {
1459
+ slot: "c",
1460
+ label: "Charge",
1461
+ min: -2,
1462
+ max: 2,
1463
+ step: 1,
1464
+ default: 1,
1465
+ hint: "The pointer's charge. Whole numbers only — the picture tears otherwise. 0 removes it.",
1466
+ },
1467
+ },
1468
+ colors: {
1469
+ colorA: color("Paper", "#0b0e14", "The ground behind the plot."),
1470
+ colorB: color("South", "#60a5fa", "The negative pole, and the equipotential rings."),
1471
+ colorC: color("North", "#fb7185", "The positive pole, and a positive pointer."),
1472
+ },
1473
+ },
1474
+ torch: {
1475
+ name: "Torch",
1476
+ tagline: "A light in your hand, moving over rock in the dark",
1477
+ description: "The wall is a heightfield and the lamp is the pointer, so the shading is honest: the fbm that draws the rock is differentiated for a normal, and crevices light up and go dark as the torch passes over them — which is what makes the surface read as carved rather than printed. The flame gutters on two noise rates, a slow breathe under a fast sputter, and dust motes drift through the beam because lit air is the only air you can see.",
1478
+ family: "spatial",
1479
+ tags: ["interactive", "pointer", "dark"],
1480
+ cost: "medium",
1481
+ controls: {
1482
+ ...base,
1483
+ speed: { ...base.speed, default: 1, max: 2 },
1484
+ scale: { ...base.scale, default: 1, min: 0.4, max: 2.5 },
1485
+ grain: { ...base.grain, default: 0.03 },
1486
+ vignette: { ...base.vignette, default: 0.5 },
1487
+ warp: {
1488
+ ...warp,
1489
+ label: "Relief",
1490
+ min: 0,
1491
+ max: 2.5,
1492
+ default: 1,
1493
+ hint: "Steepness of the rock. It scales the slope, not the texture.",
1494
+ },
1495
+ beam: {
1496
+ slot: "a",
1497
+ label: "Beam",
1498
+ min: 0.15,
1499
+ max: 1.5,
1500
+ step: 0.01,
1501
+ default: 0.7,
1502
+ hint: "Radius of the pool of light around the pointer.",
1503
+ },
1504
+ gutter: {
1505
+ slot: "b",
1506
+ label: "Gutter",
1507
+ min: 0,
1508
+ max: 2,
1509
+ step: 0.01,
1510
+ default: 1,
1511
+ hint: "How much the flame wavers. 0 is an LED.",
1512
+ },
1513
+ motes: {
1514
+ slot: "c",
1515
+ label: "Motes",
1516
+ min: 0,
1517
+ max: 2,
1518
+ step: 0.01,
1519
+ default: 0.8,
1520
+ hint: "Dust drifting through the beam.",
1521
+ },
1522
+ ambient: {
1523
+ slot: "d",
1524
+ label: "Ambient",
1525
+ min: 0,
1526
+ max: 1,
1527
+ step: 0.01,
1528
+ default: 0.35,
1529
+ hint: "What little the dark still shows. 0 is a cave.",
1530
+ },
1531
+ },
1532
+ colors: {
1533
+ colorA: color("Rock", "#241a12", "The stone in shadow."),
1534
+ colorB: color("Flame", "#fbbf24", "The torchlight itself."),
1535
+ colorC: color("Spark", "#fde68a", "Speculars and the dust in the beam."),
1536
+ },
1537
+ },
1538
+ parallax: {
1539
+ name: "Parallax",
1540
+ tagline: "Ridgelines in depth, and the pointer moves the camera",
1541
+ description: "Five silhouettes, each a one-dimensional ridged fbm thresholded against the pixel's height. Depth is faked twice over: haze mixes each layer toward the sky, and the pointer pans each layer by its own factor — the near ridge slides furthest, which is the one cue that turns a flat stack into a landscape you are standing in. Time scrolls the world slowly on its own.",
1542
+ family: "organic",
1543
+ tags: ["interactive", "pointer", "hero", "calm"],
1544
+ cost: "medium",
1545
+ controls: {
1546
+ ...base,
1547
+ speed: { ...base.speed, default: 0.6, max: 2 },
1548
+ scale: { ...base.scale, default: 1, min: 0.4, max: 2.5 },
1549
+ grain: { ...base.grain, default: 0.03 },
1550
+ vignette: { ...base.vignette, default: 0.3 },
1551
+ warp: {
1552
+ ...warp,
1553
+ label: "Jaggedness",
1554
+ min: 0.2,
1555
+ max: 2,
1556
+ default: 1,
1557
+ hint: "How sharp the ridgelines cut. Low rolls, high tears.",
1558
+ },
1559
+ height: {
1560
+ slot: "b",
1561
+ label: "Height",
1562
+ min: 0.3,
1563
+ max: 2,
1564
+ step: 0.01,
1565
+ default: 1,
1566
+ hint: "Amplitude of the mountains against the sky.",
1567
+ },
1568
+ pan: {
1569
+ slot: "c",
1570
+ label: "Pan",
1571
+ min: 0,
1572
+ max: 2,
1573
+ step: 0.01,
1574
+ default: 1,
1575
+ hint: "How far the pointer swings the camera.",
1576
+ },
1577
+ sun: {
1578
+ slot: "d",
1579
+ label: "Sun",
1580
+ min: 0,
1581
+ max: 2,
1582
+ step: 0.01,
1583
+ default: 1,
1584
+ hint: "The low sun and the rim light it puts on every ridge.",
1585
+ },
1586
+ },
1587
+ colors: {
1588
+ colorA: color("Zenith", "#2b1b5e", "The top of the sky; the near ridges are its ink."),
1589
+ colorB: color("Horizon", "#c86a8a", "The haze the far layers sink into."),
1590
+ colorC: color("Sun", "#ffd9a0", "The disc, its glow, and the rim light."),
1591
+ },
1592
+ },
1593
+ gravity: {
1594
+ name: "Gravity",
1595
+ tagline: "A starfield lensed by a mass that rides the pointer",
1596
+ description: "The lens is the physics, simplified once: a point mass deflects light by the square of its Einstein radius over the impact distance, so every sample is just read from a pushed position. That one line produces everything the picture is known for — stars smeared into arcs, the ring at the Einstein radius, doubled images inside it — and dragging the pointer drags the distortion across the sky.",
1597
+ family: "spatial",
1598
+ tags: ["interactive", "pointer", "dark", "hero"],
1599
+ cost: "medium",
1600
+ controls: {
1601
+ ...base,
1602
+ speed: { ...base.speed, default: 0.8, max: 2 },
1603
+ scale: { ...base.scale, default: 1, min: 0.4, max: 2.5 },
1604
+ grain: { ...base.grain, default: 0.02 },
1605
+ vignette: { ...base.vignette, default: 0.35 },
1606
+ mass: {
1607
+ slot: "a",
1608
+ label: "Mass",
1609
+ min: 0.1,
1610
+ max: 2.5,
1611
+ step: 0.01,
1612
+ default: 1,
1613
+ hint: "The Einstein radius. Everything else scales from it.",
1614
+ },
1615
+ ring: {
1616
+ slot: "b",
1617
+ label: "Ring",
1618
+ min: 0,
1619
+ max: 2,
1620
+ step: 0.01,
1621
+ default: 0.9,
1622
+ hint: "The photon ring, and the hot rim on the shadow's edge.",
1623
+ },
1624
+ grid: {
1625
+ slot: "c",
1626
+ label: "Grid",
1627
+ min: 0,
1628
+ max: 1,
1629
+ step: 0.01,
1630
+ default: 0.25,
1631
+ hint: "The spacetime grid, bent through the same lens. 0 hides it.",
1632
+ },
1633
+ stars: {
1634
+ slot: "d",
1635
+ label: "Stars",
1636
+ min: 0.2,
1637
+ max: 1.6,
1638
+ step: 0.01,
1639
+ default: 1,
1640
+ hint: "Density of the field behind the lens.",
1641
+ },
1642
+ },
1643
+ colors: {
1644
+ colorA: color("Space", "#050510", "The dark between the stars."),
1645
+ colorB: color("Nebula", "#7c3aed", "The soft field, the grid, and the faintest stars."),
1646
+ colorC: color("Star", "#f8fafc", "The bright stars and the shadow's rim."),
1647
+ },
1648
+ },
1071
1649
  glass: {
1072
1650
  name: "Glass",
1073
1651
  tagline: "A refracting, frosted sheet over the layer behind it",
@@ -1674,6 +2252,258 @@ export const shaderRegistry = {
1674
2252
  colorC: color("Edge", "#fbcfe8", "The light on the boundary itself."),
1675
2253
  },
1676
2254
  },
2255
+ "neon-sign": {
2256
+ name: "Neon Sign",
2257
+ tagline: "The layer's edges bent into glass tube and lit",
2258
+ description: "The tube is the level set where the layer's luma crosses a threshold, which is why this works on anything but sings on text: a glyph's boundary is one clean crossing, and the band around it becomes the glass. The halo is the blurred luma itself rather than a blur of the tube, so the light lands where the ink is. Hand it `source={{ text: \"OPEN\" }}` and it is a sign.",
2259
+ family: "optical",
2260
+ tags: ["text", "neon", "dark", "sign"],
2261
+ cost: "heavy",
2262
+ defaultSource: "truchet",
2263
+ controls: {
2264
+ ...base,
2265
+ speed: { ...base.speed, default: 1, max: 2 },
2266
+ scale: { ...base.scale, default: 1 },
2267
+ grain: { ...base.grain, default: 0.02 },
2268
+ vignette: { ...base.vignette, default: 0.35 },
2269
+ warp: {
2270
+ ...warp,
2271
+ label: "Glow",
2272
+ min: 0.05,
2273
+ max: 2,
2274
+ default: 0.6,
2275
+ hint: "How far the tube's light carries onto the wall.",
2276
+ },
2277
+ threshold: {
2278
+ slot: "a",
2279
+ label: "Threshold",
2280
+ min: 0.05,
2281
+ max: 0.95,
2282
+ step: 0.01,
2283
+ default: 0.45,
2284
+ hint: "The luma level the tube is bent along.",
2285
+ },
2286
+ fill: {
2287
+ slot: "b",
2288
+ label: "Fill",
2289
+ min: 0,
2290
+ max: 2,
2291
+ step: 0.01,
2292
+ default: 0.8,
2293
+ hint: "The dim glow inside the letterforms, behind the tube.",
2294
+ },
2295
+ flicker: {
2296
+ slot: "c",
2297
+ label: "Flicker",
2298
+ min: 0,
2299
+ max: 1,
2300
+ step: 0.01,
2301
+ default: 0.35,
2302
+ hint: "Sputter. The dropouts arrive as events, not as noise.",
2303
+ },
2304
+ boost: {
2305
+ slot: "d",
2306
+ label: "Boost",
2307
+ min: 0,
2308
+ max: 2,
2309
+ step: 0.01,
2310
+ default: 1,
2311
+ hint: "Overall drive into the tube.",
2312
+ },
2313
+ },
2314
+ colors: {
2315
+ colorA: color("Wall", "#160b16", "The wall behind the sign, lit by its spill."),
2316
+ colorB: color("Tube", "#f0abfc", "The neon colour: the halo and the fill."),
2317
+ colorC: color("Core", "#fff1f2", "What the middle of the tube burns to."),
2318
+ },
2319
+ },
2320
+ ascii: {
2321
+ name: "ASCII",
2322
+ tagline: "The layer redrawn as a density ramp of glyphs",
2323
+ description: "Ten 3×5 bitmaps packed into ten integers, ordered by how much ink they carry, so brightness picks the character the way the classic ramp does. The font ships inside the shader — a vendored copy runs with no assets at all. Colorize slides between pure phosphor and the source's own colour pushed through the glyphs; scan adds the monitor the font implies.",
2324
+ family: "image",
2325
+ tags: ["text", "retro", "terminal"],
2326
+ cost: "light",
2327
+ defaultSource: "aurora",
2328
+ controls: {
2329
+ ...base,
2330
+ speed: { ...base.speed, default: 1, max: 2 },
2331
+ scale: { ...base.scale, default: 1 },
2332
+ grain: { ...base.grain, default: 0 },
2333
+ vignette: { ...base.vignette, default: 0.3 },
2334
+ cells: {
2335
+ slot: "a",
2336
+ label: "Cells",
2337
+ min: 8,
2338
+ max: 120,
2339
+ step: 1,
2340
+ default: 48,
2341
+ hint: "Characters across the short axis.",
2342
+ },
2343
+ contrast: {
2344
+ slot: "b",
2345
+ label: "Contrast",
2346
+ min: 0.3,
2347
+ max: 3,
2348
+ step: 0.01,
2349
+ default: 1,
2350
+ hint: "Gamma on the luma before it picks a glyph.",
2351
+ },
2352
+ colorize: {
2353
+ slot: "c",
2354
+ label: "Colorize",
2355
+ min: 0,
2356
+ max: 1,
2357
+ step: 0.01,
2358
+ default: 0,
2359
+ hint: "0 is phosphor, 1 keeps the source's own colours.",
2360
+ },
2361
+ scan: {
2362
+ slot: "d",
2363
+ label: "Scan",
2364
+ min: 0,
2365
+ max: 1,
2366
+ step: 0.01,
2367
+ default: 0.4,
2368
+ hint: "Scanlines and a slow refresh bar.",
2369
+ },
2370
+ },
2371
+ colors: {
2372
+ colorA: color("Screen", "#0a0f0a", "The unlit glass behind the characters."),
2373
+ colorB: color("Mid", "#166534", "Unused by the glyphs; feeds post."),
2374
+ colorC: color("Phosphor", "#4ade80", "The terminal's ink."),
2375
+ },
2376
+ },
2377
+ shatter: {
2378
+ name: "Shatter",
2379
+ tagline: "The layer breaks into shards that fly from the pointer",
2380
+ description: "A voronoi partition where the shards hold still and their contents move: each cell samples the source from a position swung back toward the impact point, so the picture inside every shard slides and rotates away from the cursor while the crack pattern stays fixed. At speed 0 the progress slider is a scrubber, same contract as reveal — bind it to a scroll position and the break happens under the reader's thumb.",
2381
+ family: "distortion",
2382
+ tags: ["interactive", "pointer", "text", "transition"],
2383
+ cost: "medium",
2384
+ defaultSource: "kaleidoscope",
2385
+ controls: {
2386
+ ...base,
2387
+ speed: { ...base.speed, default: 0.6, max: 2 },
2388
+ scale: { ...base.scale, default: 1 },
2389
+ grain: { ...base.grain, default: 0.02 },
2390
+ vignette: { ...base.vignette, default: 0.25 },
2391
+ warp: {
2392
+ ...warp,
2393
+ label: "Scatter",
2394
+ min: 0,
2395
+ max: 2,
2396
+ default: 1,
2397
+ hint: "How far the shards travel from the impact.",
2398
+ },
2399
+ progress: {
2400
+ slot: "a",
2401
+ label: "Progress",
2402
+ min: 0,
2403
+ max: 1,
2404
+ step: 0.01,
2405
+ default: 0,
2406
+ hint: "Position of the break. Set speed to 0 and drive this yourself.",
2407
+ },
2408
+ shards: {
2409
+ slot: "b",
2410
+ label: "Shards",
2411
+ min: 2,
2412
+ max: 24,
2413
+ step: 1,
2414
+ default: 8,
2415
+ hint: "Density of the crack pattern.",
2416
+ },
2417
+ spin: {
2418
+ slot: "c",
2419
+ label: "Spin",
2420
+ min: 0,
2421
+ max: 2,
2422
+ step: 0.01,
2423
+ default: 0.8,
2424
+ hint: "How much each shard rotates as it flies.",
2425
+ },
2426
+ glint: {
2427
+ slot: "d",
2428
+ label: "Glint",
2429
+ min: 0,
2430
+ max: 2,
2431
+ step: 0.01,
2432
+ default: 0.8,
2433
+ hint: "Light catching the fresh crack edges.",
2434
+ },
2435
+ },
2436
+ colors: {
2437
+ colorA: color("Behind", "#0b0b12", "What shows through the gaps between shards."),
2438
+ colorB: color("Mid", "#334155", "Unused by the shards; feeds post."),
2439
+ colorC: color("Glint", "#e2e8f0", "The light on the crack edges."),
2440
+ },
2441
+ },
2442
+ melt: {
2443
+ name: "Melt",
2444
+ tagline: "The layer runs downhill, and the pointer is the heat",
2445
+ description: "A melting pixel shows what used to be above it, so the whole effect is sampling the source at a smaller y. How much smaller is a column-coherent noise — coherent down each column, varied across them — which is what makes drips instead of fog. The streak pass keeps the brightest tap of the climb, the way a run of paint carries its highlight down with it. The pointer is a blowtorch: hold it on text and the text sags.",
2446
+ family: "distortion",
2447
+ tags: ["interactive", "pointer", "text"],
2448
+ cost: "medium",
2449
+ defaultSource: "neon-grid",
2450
+ controls: {
2451
+ ...base,
2452
+ speed: { ...base.speed, default: 1, max: 2 },
2453
+ scale: { ...base.scale, default: 1 },
2454
+ grain: { ...base.grain, default: 0.02 },
2455
+ vignette: { ...base.vignette, default: 0.25 },
2456
+ warp: {
2457
+ ...warp,
2458
+ label: "Wobble",
2459
+ min: 0,
2460
+ max: 2,
2461
+ default: 0.8,
2462
+ hint: "Sideways sway in the runs, so no drip is perfectly plumb.",
2463
+ },
2464
+ heat: {
2465
+ slot: "a",
2466
+ label: "Heat",
2467
+ min: 0,
2468
+ max: 2,
2469
+ step: 0.01,
2470
+ default: 0.8,
2471
+ hint: "Overall melting. The pointer adds its own on top.",
2472
+ },
2473
+ drips: {
2474
+ slot: "b",
2475
+ label: "Drips",
2476
+ min: 0.2,
2477
+ max: 3,
2478
+ step: 0.01,
2479
+ default: 1,
2480
+ hint: "How many runs share the width of the frame.",
2481
+ },
2482
+ streaks: {
2483
+ slot: "c",
2484
+ label: "Streaks",
2485
+ min: 0,
2486
+ max: 1,
2487
+ step: 0.01,
2488
+ default: 0.7,
2489
+ hint: "The bright smear a run drags down with it.",
2490
+ },
2491
+ torch: {
2492
+ slot: "d",
2493
+ label: "Torch",
2494
+ min: 0.05,
2495
+ max: 0.8,
2496
+ step: 0.01,
2497
+ default: 0.25,
2498
+ hint: "Radius of the pointer's heat.",
2499
+ },
2500
+ },
2501
+ colors: {
2502
+ colorA: color("Ground", "#0a0a12", "Unused by the melt; feeds post."),
2503
+ colorB: color("Mid", "#7f1d1d", "Unused by the melt; feeds post."),
2504
+ colorC: color("Molten", "#fda4af", "The glow on the steepest runs."),
2505
+ },
2506
+ },
1677
2507
  };
1678
2508
  export const shaderNames = Object.keys(shaderRegistry);
1679
2509
  export function getShader(name) {