@ciphore/radiocli 0.2.1 → 0.2.3

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 (72) hide show
  1. package/CHANGELOG.md +133 -1
  2. package/README.md +76 -6
  3. package/dist/agent/alarm-service.js +210 -0
  4. package/dist/agent/cli.js +193 -0
  5. package/dist/agent/headless-host.js +143 -0
  6. package/dist/agent/launcher.js +71 -0
  7. package/dist/agent/mcp-install.js +467 -0
  8. package/dist/agent/mcp-server.js +139 -0
  9. package/dist/agent/service.js +347 -0
  10. package/dist/agent/session.js +248 -0
  11. package/dist/alarms/active-session.js +183 -0
  12. package/dist/alarms/cli.js +312 -0
  13. package/dist/alarms/guard.js +343 -0
  14. package/dist/alarms/inhibitor.js +48 -0
  15. package/dist/alarms/power-guard-store.js +169 -0
  16. package/dist/alarms/runner.js +342 -0
  17. package/dist/alarms/runtime-health.js +79 -0
  18. package/dist/alarms/schedule.js +149 -0
  19. package/dist/alarms/scheduler.js +250 -0
  20. package/dist/alarms/setup-verification.js +187 -0
  21. package/dist/alarms/system-volume.js +43 -0
  22. package/dist/alarms/terminal-launcher.js +181 -0
  23. package/dist/alarms/tui-presence.js +38 -0
  24. package/dist/cli.js +113 -5
  25. package/dist/player/backend-install.js +2 -1
  26. package/dist/player/command-diagnostics.js +27 -0
  27. package/dist/player/command.js +123 -62
  28. package/dist/player/player-controller.js +32 -2
  29. package/dist/providers/provider-manager.js +5 -0
  30. package/dist/providers/radio-browser.js +36 -6
  31. package/dist/setup.js +462 -0
  32. package/dist/storage/store.js +262 -2
  33. package/dist/types.js +6 -0
  34. package/dist/ui/AdaptiveContent.js +111 -26
  35. package/dist/ui/App.js +401 -67
  36. package/dist/ui/AppContent.js +24 -7
  37. package/dist/ui/adaptive-explore-layout.js +47 -0
  38. package/dist/ui/alarm-editor.js +174 -0
  39. package/dist/ui/alarm-tui-service.js +84 -0
  40. package/dist/ui/app-state.js +3 -0
  41. package/dist/ui/ascii.js +8 -0
  42. package/dist/ui/components/AdaptiveMarquee.js +28 -0
  43. package/dist/ui/components/StationList.js +10 -5
  44. package/dist/ui/components/VersionIndicator.js +19 -0
  45. package/dist/ui/cosmo-world-map.js +5 -2
  46. package/dist/ui/explore-map-layout.js +18 -6
  47. package/dist/ui/format.js +21 -0
  48. package/dist/ui/help-content.js +14 -2
  49. package/dist/ui/layout.js +1 -1
  50. package/dist/ui/page-footer.js +120 -2
  51. package/dist/ui/receiver-animation.js +68 -0
  52. package/dist/ui/screen-items.js +41 -9
  53. package/dist/ui/screen-meta.js +4 -0
  54. package/dist/ui/screens/AlarmsScreen.js +202 -0
  55. package/dist/ui/screens/CountriesScreen.js +8 -5
  56. package/dist/ui/screens/ExploreScreen.js +8 -3
  57. package/dist/ui/screens/HomeScreen.js +3 -1
  58. package/dist/ui/screens/NowPlayingScreen.js +6 -2
  59. package/dist/ui/screens/SettingsScreen.js +70 -53
  60. package/dist/ui/screens/StationScreen.js +3 -2
  61. package/dist/ui/selection-state.js +10 -0
  62. package/dist/ui/terminal-mouse.js +18 -3
  63. package/dist/ui/use-alarm-tui.js +727 -0
  64. package/dist/ui/use-app-input.js +107 -46
  65. package/dist/ui/visualizers/gallop.js +118 -0
  66. package/dist/ui/visualizers/horse-stride.js +20 -0
  67. package/dist/ui/visualizers/receiver-style-registry.js +14 -7
  68. package/dist/ui/visualizers/receiver-visualizers.js +233 -128
  69. package/dist/ui/visualizers/retro-receivers.js +4 -0
  70. package/dist/ui/visualizers/terminal-receivers.js +57 -0
  71. package/dist/update-check.js +26 -7
  72. package/package.json +6 -1
@@ -1,10 +1,19 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useInput } from 'ink';
3
- import { homeItems, settingsItems } from './screen-items.js';
4
- import { applyTextInput, clamp, favoriteTarget, isEditableInput, isPlainPrintableInput, mediaTransportActionForInput, searchEditingArrowAction, shouldHandleKeyboardEvent, shouldToggleNearbyLocationShortcut } from './app-state.js';
3
+ import { homeItems, settingsItemsForPage, settingsPageForRootItem } from './screen-items.js';
4
+ import { applyTextInput, favoriteTarget, isEditableInput, isPlainPrintableInput, mediaTransportActionForInput, searchEditingArrowAction, shouldHandleKeyboardEvent, shouldToggleNearbyLocationShortcut } from './app-state.js';
5
5
  import { parseTerminalMouseEvents, primaryMousePress, wheelScrollDelta } from './terminal-mouse.js';
6
6
  import { completeCommand } from './help-content.js';
7
- export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, beginLearningTransportKey, capturingTransportAction, commandMode, commandText, confirmCtrlCExit, copyStationUrl, openStationHomepage, currentItemCount, cycleDisplayColor, cycleAudioOutput, cycleReceiverStyle, cycleSleepTimer, editingCountryFilter, editingSearch, executeCommand, filteredCountries, go, lastRawTransportAtRef, lastSubmittedSearchRef, loadCountry, openAdjacentTab, openAirPlayCode, openAirPlaySettings, openScreen, playAdjacent, playStation, player, playingStation, recallSearchHistory, moveExploreCursor, moveExploreCursorToCell, refreshAirPlayTargets, refreshProviderHealth, resetLearnedTransportKeys, runSearch, saveLearnedTransportKey, screen, searchQuery, selected, selectedStation, selectAirPlayDeviceAt, setCapturingTransportAction, setAirPlayCode, setCommandMode, setCommandText, setCountryFilter, setEditingCountryFilter, setEditingSearch, setMessage, setSearchQuery, setSelected, setShowDiagnostics, submitAirPlayCode, settingsRef, shutdown, stdin, toggleFavorite, toggleMute, togglePause, toggleSetting, toggleNearbyLocation, toggleDirectoryVoting, toggleRadioGarden, toggleSkipBrokenStreams, updateFromSettings }) {
7
+ import { commitImmediateSelection } from './selection-state.js';
8
+ export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, cancelPendingAutoSkip, beginLearningTransportKey, capturingTransportAction, commandMode, commandText, confirmCtrlCExit, copyStationUrl, openStationHomepage, currentItemCount, cycleDisplayColor, cycleAudioOutput, cycleReceiverStyle, cycleSleepTimer, editingCountryFilter, editingSearch, executeCommand, filteredCountries, go, lastRawTransportAtRef, lastSubmittedSearchRef, loadCountry, openAdjacentTab, openAirPlayCode, openAirPlaySettings, openSettingsPage, closeSettingsPage, openScreen, handleAlarmInput, openAlarmForStation, openActiveAlarms, playAdjacent, playStation, player, playingStation, recallSearchHistory, moveExploreCursor, moveExploreCursorToCell, refreshAirPlayTargets, refreshProviderHealth, resetLearnedTransportKeys, repairMcpIntegrations, setAgentIntegrationEnabled, runSearch, saveLearnedTransportKey, screen, settingsPage, searchQuery, selectedRef, selectedStationForInput, selectAirPlayDeviceAt, setCapturingTransportAction, setAirPlayCode, setCommandMode, setCommandText, setCountryFilter, setEditingCountryFilter, setEditingSearch, setMessage, setSearchQuery, setSelected, setShowDiagnostics, submitAirPlayCode, settingsRef, shutdown, stdin, toggleFavorite, toggleMute, togglePause, toggleSetting, toggleAgentSetting, toggleNearbyLocation, toggleDirectoryVoting, toggleRadioGarden, toggleSkipBrokenStreams, updateFromSettings }) {
9
+ const commitSelection = (next) => {
10
+ commitImmediateSelection(selectedRef, setSelected, next, currentItemCount(screen));
11
+ };
12
+ const moveSelection = (delta) => {
13
+ if (hasStationSelection(screen))
14
+ cancelPendingAutoSkip();
15
+ commitSelection(selectedRef.current + delta);
16
+ };
8
17
  useEffect(() => {
9
18
  const onData = (data) => {
10
19
  const rawInput = String(data);
@@ -26,7 +35,9 @@ export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, be
26
35
  const click = primaryMousePress(mouseEvents);
27
36
  lastRawTransportAtRef.current = Date.now();
28
37
  if (wheelDelta !== 0 && shouldScrollSelectionWithWheel(screen, commandMode, editingCountryFilter)) {
29
- setSelected(value => clamp(value + wheelDelta * 3, currentItemCount(screen) - 1));
38
+ if (handleAlarmInput('', { scrollDelta: wheelDelta * 3 }))
39
+ return;
40
+ moveSelection(wheelDelta * 3);
30
41
  return;
31
42
  }
32
43
  if (!commandMode && screen === 'explore' && click) {
@@ -57,18 +68,23 @@ export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, be
57
68
  stdin.off('data', onData);
58
69
  };
59
70
  }, [
71
+ cancelPendingAutoSkip,
60
72
  capturingTransportAction,
61
73
  commandMode,
74
+ currentItemCount,
62
75
  editingCountryFilter,
63
76
  editingSearch,
77
+ handleAlarmInput,
64
78
  lastRawTransportAtRef,
65
79
  playAdjacent,
66
80
  player,
67
81
  moveExploreCursorToCell,
68
82
  saveLearnedTransportKey,
69
83
  screen,
84
+ selectedRef,
70
85
  setCapturingTransportAction,
71
86
  setMessage,
87
+ setSelected,
72
88
  settingsRef,
73
89
  stdin,
74
90
  togglePause
@@ -109,30 +125,17 @@ export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, be
109
125
  }
110
126
  return;
111
127
  }
112
- if (key.shift && key.leftArrow) {
113
- playAdjacent(-1);
114
- return;
115
- }
116
- if (key.shift && key.rightArrow) {
117
- playAdjacent(1);
118
- return;
119
- }
120
- if (key.tab) {
121
- openAdjacentTab(key.shift ? -1 : 1);
122
- return;
123
- }
124
- if (key.rightArrow) {
125
- openAdjacentTab(1);
126
- return;
127
- }
128
- if (key.leftArrow) {
129
- openAdjacentTab(-1);
128
+ if (handleAlarmInput(input, key)) {
130
129
  return;
131
130
  }
131
+ // An active text field owns its keystrokes before app-wide shortcuts. In
132
+ // particular, a normal "a" in a station query must not invoke the global
133
+ // schedule-this-station shortcut and leave Search for the alarm editor.
132
134
  if (screen === 'search' && editingSearch) {
133
135
  if (key.return) {
134
- if (searchQuery.trim() && searchQuery.trim() === lastSubmittedSearchRef.current && selectedStation) {
135
- void playStation(selectedStation);
136
+ const inputStation = selectedStationForInput();
137
+ if (searchQuery.trim() && searchQuery.trim() === lastSubmittedSearchRef.current && inputStation) {
138
+ void playStation(inputStation);
136
139
  }
137
140
  else {
138
141
  void runSearch();
@@ -152,10 +155,10 @@ export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, be
152
155
  recallSearchHistory('newer');
153
156
  }
154
157
  else if (arrowAction === 'select-previous') {
155
- setSelected(value => clamp(value - 1, currentItemCount(screen) - 1));
158
+ moveSelection(-1);
156
159
  }
157
160
  else {
158
- setSelected(value => clamp(value + 1, currentItemCount(screen) - 1));
161
+ moveSelection(1);
159
162
  }
160
163
  return;
161
164
  }
@@ -165,10 +168,38 @@ export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, be
165
168
  return;
166
169
  }
167
170
  }
171
+ if (input === '!' || (input === 'A' && screen !== 'explore')) {
172
+ openActiveAlarms();
173
+ return;
174
+ }
175
+ if (input === 'a' && ['stations', 'search', 'nearby', 'library', 'now-playing'].includes(screen)) {
176
+ openAlarmForStation(favoriteTarget(screen, selectedStationForInput(), playingStation));
177
+ return;
178
+ }
179
+ if (key.shift && key.leftArrow) {
180
+ playAdjacent(-1);
181
+ return;
182
+ }
183
+ if (key.shift && key.rightArrow) {
184
+ playAdjacent(1);
185
+ return;
186
+ }
187
+ if (key.tab) {
188
+ openAdjacentTab(key.shift ? -1 : 1);
189
+ return;
190
+ }
191
+ if (key.rightArrow) {
192
+ openAdjacentTab(1);
193
+ return;
194
+ }
195
+ if (key.leftArrow) {
196
+ openAdjacentTab(-1);
197
+ return;
198
+ }
168
199
  if ((screen === 'countries' || screen === 'map') && editingCountryFilter) {
169
200
  if (key.return || key.escape) {
170
201
  setEditingCountryFilter(false);
171
- setSelected(0);
202
+ commitSelection(0);
172
203
  return;
173
204
  }
174
205
  if (isEditableInput(input, key)) {
@@ -287,16 +318,16 @@ export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, be
287
318
  return;
288
319
  }
289
320
  if (input === ']') {
290
- setSelected(value => clamp(value + 10, currentItemCount(screen) - 1));
321
+ moveSelection(10);
291
322
  return;
292
323
  }
293
324
  if (input === '[') {
294
- setSelected(value => clamp(value - 10, currentItemCount(screen) - 1));
325
+ moveSelection(-10);
295
326
  return;
296
327
  }
297
- if (screen === 'home' && /^[1-8]$/.test(input)) {
328
+ if (screen === 'home' && /^[1-9]$/.test(input)) {
298
329
  const menuIndex = Number(input) - 1;
299
- setSelected(menuIndex);
330
+ commitSelection(menuIndex);
300
331
  const target = homeItems[menuIndex]?.screen;
301
332
  if (target) {
302
333
  openScreen(target);
@@ -307,6 +338,10 @@ export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, be
307
338
  go('settings');
308
339
  return;
309
340
  }
341
+ if ((input === 'b' || key.escape) && screen === 'settings') {
342
+ closeSettingsPage();
343
+ return;
344
+ }
310
345
  if (input === 'b' || key.escape) {
311
346
  if (screen === 'stations') {
312
347
  go('countries');
@@ -333,15 +368,15 @@ export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, be
333
368
  return;
334
369
  }
335
370
  if (input === 'f') {
336
- toggleFavorite(favoriteTarget(screen, selectedStation, playingStation));
371
+ toggleFavorite(favoriteTarget(screen, selectedStationForInput(), playingStation));
337
372
  return;
338
373
  }
339
374
  if (input === 'O') {
340
- openStationHomepage(favoriteTarget(screen, selectedStation, playingStation));
375
+ openStationHomepage(favoriteTarget(screen, selectedStationForInput(), playingStation));
341
376
  return;
342
377
  }
343
378
  if (input === 'y') {
344
- void copyStationUrl(favoriteTarget(screen, selectedStation, playingStation));
379
+ void copyStationUrl(favoriteTarget(screen, selectedStationForInput(), playingStation));
345
380
  return;
346
381
  }
347
382
  if (input === ' ') {
@@ -357,38 +392,45 @@ export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, be
357
392
  return;
358
393
  }
359
394
  if (input === 'n') {
360
- setSelected(value => clamp(value + 1, currentItemCount(screen) - 1));
395
+ moveSelection(1);
361
396
  return;
362
397
  }
363
398
  if (input === 'p') {
364
- setSelected(value => clamp(value - 1, currentItemCount(screen) - 1));
399
+ moveSelection(-1);
365
400
  return;
366
401
  }
367
402
  if (key.downArrow) {
368
- setSelected(value => clamp(value + 1, currentItemCount(screen) - 1));
403
+ moveSelection(1);
369
404
  return;
370
405
  }
371
406
  if (key.upArrow) {
372
- setSelected(value => clamp(value - 1, currentItemCount(screen) - 1));
407
+ moveSelection(-1);
373
408
  return;
374
409
  }
375
410
  if (key.return) {
376
411
  if (screen === 'home') {
377
- const target = homeItems[selected]?.screen;
412
+ const target = homeItems[selectedRef.current]?.screen;
378
413
  if (target) {
379
414
  openScreen(target);
380
415
  }
381
416
  return;
382
417
  }
383
418
  if (screen === 'countries') {
384
- const country = filteredCountries[selected];
419
+ const country = filteredCountries[selectedRef.current];
385
420
  if (country) {
386
421
  void loadCountry(country);
387
422
  }
388
423
  return;
389
424
  }
390
425
  if (screen === 'settings') {
391
- const item = settingsItems[selected];
426
+ const item = settingsItemsForPage(settingsPage)[selectedRef.current];
427
+ if (settingsPage === 'root') {
428
+ const targetPage = settingsPageForRootItem(item);
429
+ if (targetPage) {
430
+ openSettingsPage(targetPage);
431
+ }
432
+ return;
433
+ }
392
434
  if (item === 'Cycle display color') {
393
435
  cycleDisplayColor();
394
436
  }
@@ -437,6 +479,21 @@ export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, be
437
479
  else if (item === 'Mouse and trackpad scrolling') {
438
480
  toggleSetting('mouseSupport');
439
481
  }
482
+ else if (item === 'Automatically check for updates') {
483
+ toggleSetting('automaticUpdateChecks');
484
+ }
485
+ else if (item === 'Allow local agent control') {
486
+ void setAgentIntegrationEnabled();
487
+ }
488
+ else if (item === 'Install or repair MCP integrations') {
489
+ void repairMcpIntegrations();
490
+ }
491
+ else if (item === 'Open TUI for agent playback') {
492
+ toggleAgentSetting('openUiOnPlay');
493
+ }
494
+ else if (item === 'Show Now Playing for agent playback') {
495
+ toggleAgentSetting('focusNowPlaying');
496
+ }
440
497
  else if (item === 'Export preferences and library') {
441
498
  setCommandText('export ');
442
499
  setCommandMode(true);
@@ -467,18 +524,19 @@ export function useAppInput({ adjustVolume, airPlayCode, canEnterAirPlayCode, be
467
524
  return;
468
525
  }
469
526
  if (screen === 'airplay-settings') {
470
- selectAirPlayDeviceAt(selected);
527
+ selectAirPlayDeviceAt(selectedRef.current);
471
528
  return;
472
529
  }
473
530
  if (screen === 'map') {
474
- const country = filteredCountries[selected];
531
+ const country = filteredCountries[selectedRef.current];
475
532
  if (country) {
476
533
  void loadCountry(country);
477
534
  }
478
535
  return;
479
536
  }
480
- if (selectedStation) {
481
- void playStation(selectedStation);
537
+ const inputStation = selectedStationForInput();
538
+ if (inputStation) {
539
+ void playStation(inputStation);
482
540
  }
483
541
  }
484
542
  });
@@ -489,9 +547,12 @@ function shouldScrollSelectionWithWheel(screen, commandMode, editingCountryFilte
489
547
  }
490
548
  return [
491
549
  'home', 'countries', 'map', 'stations', 'search', 'nearby', 'explore',
492
- 'library', 'settings', 'help', 'airplay-settings'
550
+ 'library', 'settings', 'help', 'airplay-settings', 'alarms', 'alarm-editor', 'alarm-picker', 'alarm-ringing'
493
551
  ].includes(screen);
494
552
  }
553
+ function hasStationSelection(screen) {
554
+ return ['stations', 'search', 'nearby', 'explore', 'library'].includes(screen);
555
+ }
495
556
  function exploreMoveForInput(input) {
496
557
  const normalized = input.toLowerCase();
497
558
  if (normalized === 'w') {
@@ -0,0 +1,118 @@
1
+ import { themeAccent } from '../theme.js';
2
+ import { horseStrideFrames, horseStrideHeight, horseStrideWidth } from './horse-stride.js';
3
+ const frames = horseStrideFrames.map(frame => Buffer.from(frame, 'base64'));
4
+ const brailleBits = [[1, 8], [2, 16], [4, 32], [64, 128]];
5
+ /** One pose per receiver tick. The same scale and torso anchor are used for every pose. */
6
+ export function buildGallop(pulse, width, height, theme) {
7
+ const w = Math.max(1, Math.floor(width));
8
+ const h = Math.max(1, Math.floor(height));
9
+ const color = themeAccent(theme);
10
+ const frameIndex = ((Math.floor(pulse) % frames.length) + frames.length) % frames.length;
11
+ const frame = frames[frameIndex];
12
+ const usableW = w > 8 ? w - 4 : w;
13
+ const usableH = h > 4 ? h - 2 : h;
14
+ const scale = Math.min(usableW * 2 / horseStrideWidth, usableH * 4 / horseStrideHeight);
15
+ const dotsW = Math.max(1, Math.floor(horseStrideWidth * scale));
16
+ const dotsH = Math.max(1, Math.floor(horseStrideHeight * scale));
17
+ const left = Math.floor((w - Math.ceil(dotsW / 2)) / 2) * 2;
18
+ const top = Math.floor((h - Math.ceil(dotsH / 4)) / 2) * 4;
19
+ const masks = new Uint8Array(w * h);
20
+ for (let y = 0; y < dotsH; y++) {
21
+ const sy0 = Math.floor(y * horseStrideHeight / dotsH);
22
+ const sy1 = Math.ceil((y + 1) * horseStrideHeight / dotsH);
23
+ for (let x = 0; x < dotsW; x++) {
24
+ const sx0 = Math.floor(x * horseStrideWidth / dotsW);
25
+ const sx1 = Math.ceil((x + 1) * horseStrideWidth / dotsW);
26
+ let ink = 0;
27
+ for (let sy = sy0; sy < sy1; sy++)
28
+ for (let sx = sx0; sx < sx1; sx++) {
29
+ const bit = sy * horseStrideWidth + sx;
30
+ ink += (frame[bit >> 3] >> (bit & 7)) & 1;
31
+ }
32
+ // Area coverage keeps thin legs continuous as the terminal becomes smaller.
33
+ if (ink / ((sx1 - sx0) * (sy1 - sy0)) < 0.22)
34
+ continue;
35
+ const dx = left + x;
36
+ const dy = top + y;
37
+ const index = Math.floor(dy / 4) * w + Math.floor(dx / 2);
38
+ masks[index] |= brailleBits[dy % 4][dx % 2];
39
+ }
40
+ }
41
+ return Array.from({ length: h }, (_, y) => {
42
+ let text = '';
43
+ for (let x = 0; x < w; x++) {
44
+ const mask = masks[y * w + x];
45
+ text += mask === 0 ? ' ' : String.fromCharCode(0x2800 + mask);
46
+ }
47
+ // No background color: only the horse's ink is drawn over the receiver panel.
48
+ return { text, color, segments: [{ text, color }] };
49
+ });
50
+ }
51
+ /** Lightly pare back thick stroke edges, keeping endpoints and junctions intact. */
52
+ export function buildGallopFine(pulse, width, height, theme) {
53
+ return thinGallopStrokes(buildGallop(pulse, width, height, theme), 0.14);
54
+ }
55
+ function thinGallopStrokes(original, reduction) {
56
+ const w = original[0].text.length;
57
+ const h = original.length;
58
+ const dw = w * 2;
59
+ const dh = h * 4;
60
+ const dots = new Uint8Array(dw * dh);
61
+ let inkCount = 0;
62
+ original.forEach((row, y) => {
63
+ [...row.text].forEach((glyph, x) => {
64
+ const mask = glyph === ' ' ? 0 : glyph.charCodeAt(0) - 0x2800;
65
+ for (let yy = 0; yy < 4; yy++)
66
+ for (let xx = 0; xx < 2; xx++) {
67
+ if (mask & brailleBits[yy][xx]) {
68
+ dots[(y * 4 + yy) * dw + x * 2 + xx] = 1;
69
+ inkCount++;
70
+ }
71
+ }
72
+ });
73
+ });
74
+ const neighbors = [-dw, -dw + 1, 1, dw + 1, dw, dw - 1, -1, -dw - 1];
75
+ const canThin = (index) => {
76
+ if (!dots[index])
77
+ return false;
78
+ const ring = neighbors.map(offset => dots[index + offset]);
79
+ const count = ring.reduce((total, dot) => total + dot, 0);
80
+ // Already-fine lines and endpoints stay untouched. A single connected arc of
81
+ // neighbors ensures removing this edge dot cannot split a leg or contour.
82
+ if (count < 3 || count > 6)
83
+ return false;
84
+ let transitions = 0;
85
+ for (let i = 0; i < 8; i++)
86
+ if (!ring[i] && ring[(i + 1) % 8])
87
+ transitions++;
88
+ return transitions === 1;
89
+ };
90
+ const candidates = [];
91
+ for (let y = 1; y < dh - 1; y++)
92
+ for (let x = 1; x < dw - 1; x++) {
93
+ const index = y * dw + x;
94
+ if (canThin(index))
95
+ candidates.push(index);
96
+ }
97
+ const budget = Math.min(candidates.length, Math.floor(inkCount * reduction));
98
+ for (let i = 0; i < budget; i++) {
99
+ // Spread the small reduction over the entire horse instead of thinning its
100
+ // head first. Recheck after each removal to preserve connected strokes.
101
+ const index = candidates[Math.floor(i * candidates.length / budget)];
102
+ if (canThin(index))
103
+ dots[index] = 0;
104
+ }
105
+ return original.map((row, y) => {
106
+ let text = '';
107
+ for (let x = 0; x < w; x++) {
108
+ let mask = 0;
109
+ for (let yy = 0; yy < 4; yy++)
110
+ for (let xx = 0; xx < 2; xx++) {
111
+ if (dots[(y * 4 + yy) * dw + x * 2 + xx])
112
+ mask |= brailleBits[yy][xx];
113
+ }
114
+ text += mask ? String.fromCharCode(0x2800 + mask) : ' ';
115
+ }
116
+ return { text, color: row.color, segments: [{ text, color: row.color }] };
117
+ });
118
+ }
@@ -0,0 +1,20 @@
1
+ /** Fixed-canvas gallop poses derived from the user-provided horse line-art reference.
2
+ * Each base64 frame is a 128 × 80 one-bit ink mask, packed least-significant bit first.
3
+ * Background pixels are transparent; no video decoder or source file is needed at runtime.
4
+ */
5
+ export const horseStrideWidth = 128;
6
+ export const horseStrideHeight = 80;
7
+ export const horseStrideFrames = [
8
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAOB9AAAAAAAAAAAAAAAAAADwMQAAAAAAAAAAAAAAAAAADzADAAAAAAAAAAAAAAAAwA8mBwAAAAAAAAAAAAAAAPCAAxwAAAAAAAAAAAAAAAA4wAE4AAAAAAAAAAAAAAAAmGcAcAAAAAAAAAAAAAAAAIg5APgAAAAAAAAAAAAAAAAMOGCAAQAAAAAAAAAAAAAABgxgIAMAAAAAAAAAAAAAgAMGYAAIAAAAAAAAAAAAAMABA2AAGAAAAAAAAAAAAADgxwPAABAAAAAAAAAAAAAA8OMA4A8QAAAAAAAAAAAAAPB8AGB+EAAAAAAAAAAAAADgHgAw4BAAAAAAAAAAAAAAwAcAEIARAAAAAAAAAID/AcABABgAAQAAAAAAAADw//9hgAAYAAAAAAAAAAAAeAD8AQABDAAAAAAAAAAAAB4AAAAAAAwAAAAAAAAAAAAHEAAAAAAMAAAAAAAAAACAAxAAAAAGDAAAAAAAAAAAgAEAAAAADAYAAAAAAAAAADgAAAAAABgGAAAAAAAAAMAfAAAAAAA4BgAAAAAAAAD/AwAAAAAAMAIAAAAAAADg/wMAAAAAAHAHAAAAAAAAMIBgAAAAAADwBwAAAAAAAAjwYADAAAAA8AcAAAAAAAAAOOAAwAAAAPAGAAAAAAAAAAjAEMAAAAD4DgAAAAAAAAAIwHGAAAAA/DwAAAAAAAAAAIBDgAAAAPwgAAAAAAAAAAAAB4AAAAB9AA8AAAAABgAAAB+AAACCfQAeAAAA5gwAAAB+gAAAxngAGAAAAIwBgAAA+MADAObw/zEAAAAYAIAAALCB/wB+YK8HAAAAcADAAADggf//f+AADgAAAMABYAAA4IP5f/DEAQwAAAAAPnAAAECDAQDAhwMYAAAAAD84AABAhgEAAB8HGAAAAIAfHwAAwIYAAAB8DtAAAAAA/QcAAMCGAAAA8BzQAAAAAPABAACAhAEAAMABsAEAAAAAAAAAgIYBAEyACbABAAAAAAAAAIAGAYDPwAlwAQAAAAAAAADAHgOA8/kAcAMAAAAAAAAAwD4cANt/APADAAAAAAAAAGD4+AC4PgDgAwAAAAAAAAAAwOcDcD4AwAMAAAAAAAAAwACfD+APAOAGAAAAAAAAAIACOAzgBwDgBwAAAAAAAAAAAuAYwAAAQAcAAAAAAAAAADbAOQAAAMAAAAAAAAAAAABmAHMAAADABQAAAAAAAAAAbADBAAAAAAcAAAAAAAAAANgAhwAAAAAGAAAAAAAAAACYAA4AAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAACACAAAAAAAAAAAAAAAAAABgAgAAAAAAAAAAAAAAAAAAYAYAAAAAAAAAAAAAAAAAAEAdAAAAAAAAAAAAAAAAAACANwAAAAAAAAAAAAAAAAAAgGMAAAAAAAAAAAAAAAAA4P//AwAAAAAAAAAAAAAAAOD//wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
9
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAACA/wAAAAAAAAAAAAAAAAAAAEQAAAAAAAAAAAAAAAAAAABgBgAAAAAAAAAAAAAAAAAA7BwAAAAAAAAAAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAYA4AAAAAAAAAAAAAAAAABAAOABAAAAAAAAAAAAAABgMABwAwAAAAAAAAAAAABAABjAAAYAAAAAAAAAAAAAAAAMwMAMAAAAAAAAAAAAAAAAAMAAGAAAAAAAAAAAAAAAAADAADAAAAAAAAAAAAAAAMAAwAMgAAAAAAAAAAAAABx4AIB/YAAAAAAAAAAAAAAEHACA4GEAAAAAAAAAAAAAAAcAAIBjAAAAAAAAAAAAAIABAAAAQgAAAAAAAACA/wHAAAAAAAAAAAAAAAAAAMD/XwAAEAAAAAAAAAAAAAYA/AMAAhAAAAAAAAAAAIADAAAAAAYYAAAAAAAAAACAAQAAAAAMCAAAAAAAAAAA4AAAAAAADAwAAAAAAAAAAHwAAAAAABgMAAAAAAAAAAAeACAAAAAQBAAAAAAAAAAAGABgAAAAAAYAAAAAAAAAADgAYAAAAAACAAAAAAAAAAAAAEAAAAAAAwAAAAAAAAwAYADAAAAAgAcAAAAAAAAAAGAAwAAAAKAEAAAAAAAAAADAAIAAAAAgAAAAAAAAAAAA4AGAAAAAwAAAAAAAAAAAAKADgAEAA0AAAAAAAAAAAABgDwAAAAKAQAAAAAAAAAAAYB8AAAAG4EAAAAAAOAAAAMB4AAEABOEADwAAAPABAAHA8QMDAIzAAR4AAADAi4ABgIEDHwDMADwwAAAAwMDAAAADA///D3z8YAAAAIB5wAAAAwbi//vxuQMAAAAAH2AAAAMMAgDAf2AOAAAAAAAwAAADGwYAAPhjOAAAAMACHAAAgxkGAAAAR7ADAADA/w8AgIExBgAAAExgAwAAAP8HAIDBMAQAAAAMwAYAAAA8AADAcSAMAAAABIAMAAAAAAAA8DmwDAAAABaAGQAAAAAAAPAYsBkAAAAbgBkAAAAAAADwDKA3AACAG4ARAAAAAAAA4AzgcAAAxA2AOwAAAAAAAGAMgOMAgPUGAH8AAAAAAABgBgDPAeAfBgDsAAAAAAAAwAQAnAfwDQMAiAAAAAAAAMAEAHAOsI0BAPwBAAAAAADADADgHPC/AQCcAQAAAAAAgAwAwDkAMAAAOAMAAAAAAIANAIAzAAAAAPADAAAAAACAGQAAdwAAAACAAgAAAAAAgBkAAOwAAAAAAAAAAAAAAIA5AADuAQAAAAAAAAAAAACAOwAAvgMAAAAAAAAAAAAAwD8AAPwHAAAAAAAAAAAAAAA/AAD4BwAAAAAAAAAAAAAAfQAAAAAAAAAAAAAAAAAAAO4AAAAAAAAAAAAAAAAAAACEAQAAAAAAAAAAAAAAAAAA/AMAAAAAAAAAAAAAAAAAAPgDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
10
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAwMwAAAAAAAAAAAAAAAAAAPjNAQAAAAAAAAAAAAAAAAAcAAMAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAYAAADAAAAAAAAAAAAABAhjgACB4AAAAAAAAAAAAAcBAcAAwwAAAAAAAAAAAAAAAABwAMbAAAAAAAAAAAAAAA4AMADIAAAAAAAAAAAAAAAHgAAAyAAQAAAAAAAAAAAAAOAAAcAAMAAAAAAAAAAACAAwAA/gECAAAAAAAA8B8A4AEBAOQHAgAAAAAAAP7/D34AAwAAHgIAAAAAAIAPAA8PAAYAADgCAAAAAADAAQAAAAAEAAAQAgAAAAAAcAAAAAAAECAAAAAAAAAAAD4AAAAAABAwAAAAAAAAAIAfAAAAAAAAGAAAAAAAAACADwAAAAAAAAwAAAAAAAAAgA8AAAAAAAAMAAAAAAAAAAAEAAAAAAAABgAAAAAAAAAADgAAAAAAgAMAAAAAAAAAABoAAAAAAIACAAAAAAAAAAAbABAAAACAAgAAAAAAAAAAGwAQAAAAwAIAAAAAAAAAADEAEAAAAMACAAAAAAAAAIAxABAAAAAAAgAAAAAAAACAYQAYAAAM4AIAAAAAAAAQgOEAGAAAAMAHAAAAAAAACIDBAQgAAAAGBwAAAAAAAAyAAAd4AAAcAgwAAAAA4AAEwAAE/AMAPAL8AAAAAMADBuAABow/gH/44AAAAAAAA0NwwAUE8P/nTwABAAAAAABhPOAEBgAAgP8fBgAAAAAMYB5wBgMAAADAPwYAAAAACLgfOAYDAAAAgHMAAAAAADjPBx6GAQAAAADnAAAAAADnR0EHwwAAAAAAjAQAAAAA/mDxgeMAAAAAAJwEAAAAAACgeMBhAAAAAACYDQAAAAAAMB7gMQAAAAAA8A0AAAAAABgHYBEAAAAAAOANAAAAAACYA+ARAAAAAACACQAAAAAA4AHAEQAAAAAAADkAAAAAAHcAgAEAAAAAAAD4AAAAAPA7AIAzAAAAAACAiQEAAAD0HAAANgAAAAAAgAkDAAAAHw4AAGYAAAAAAMAPHgAAgAkHAADsAAAAAADA/XwAAMCgAwAA3AAAAAAA4P/5AQBg/AEAAJgBAAAAAGCHjwMAcHwAAACYAwAAAABABjcAAPAHAAAAMAcAAAAAwAIwAADAAAAAAHAGAAAAAIADAAAAAAAAAABwDgAAAACAAQAAAAAAAAAAwB8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAYwAAAAAAAAAAAAAAAAAAgP8AAAAAAAAAAAAAAAAAAAD+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
11
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwH8AAAAAAAAAAAAAAAAAAAB4AAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAAAAA8H8DAAAAAAAAAAAAAAAAAH4ABwAAAAAAAAAAAAAAAIAPAAwAAAAAAAAAAAAAAATgAQAYAAAAAAAAAAAAAAAAPAAAMAAAAAAAAAAAAAAAAA8AIHgAAAAAAAAAAAAAAMADACDYAAAAAAAAAAAAAAD+AAAwkAEAAAAAAAAAAACADwAAOBADAAAAAAAAAAAAwAMAADgAAwAAAAAAAPwfAPABAABwAAYAAAAAAID/////AAAA/AMMAAAAAADAB4A/AAAAAAEPAAAAAAAA4AEAAAAADMABOAwAAAAAAH8AAAAAAADgAHAMAAAAAOAfAAAAAABgMABADAAAAIC7DwAAAAAAQBgAAAAAAADgzQ8AAAAAAMAcAAAAAAAA8AAOAAAAAACADwAAAAAAAHgADgAAAAAAgAcAAAAAAAA4AA4AAAAAAAADAAAAAAAACAAOAAAAAACAAQAAAAAAAAAADgAIAAAAgAEAAAAAAAAAAB4ADAAACIABAAAAAAAAAAAeAAwAAAiAAQAAAAAAAAAAvgAMAAAIgAEAAAAAAAAAAPYBBgAACIABAAAAAAAAAABmA38AADiIAQAAAAAAAAAAYoIPAAA/DAMAAAAAAAAAAGKA/wAAeAQGAAAAAAAgAABgwPADAGAEDAAAAAAAIwBgYMCA//PPBzgAAAAAQAYAOHBgAPD/w5/hAAAAAMAEAB4wcAAAAAD7gAEAAADAAAADODAAAAAA5wcDAAAAgAMABx0cAAAAAIY/BwAAAAB/gO8fDgAAAAAscQwAAAAAfsD3DwcAAAAAOOMAAAAAAADA+IMBAAAAADDCAwAAAAAAYN7AAAAAAABgBgMAAAAAAADHYAAAAAAAwAw2AAAAAAAww3AAAAAAAMANbgAAAAAAmMExAAAAAACADewAAAAA/syAEQAAAAAAgA/YAAAAAI9kgBkAAAAAAAAXmAEAAACHMwAbAAAAAAAAPrgDAAAAjjEAEwAAAAAAAHz4AwAAAJgYABIAAAAAAAD88AcAAADgHwA2AAAAAAAA+MEMAAAAwAcANgAAAAAAAPBDDAAAAAACAGwAAAAAAAAwwggAAAAAAADMAAAAAAAAYIAfAAAAAAAAiAEAAAAAAOAcFAAAAAAAAIwBAAAAAADAewAAAAAAAAC+AwAAAAAAgPcAAAAAAAAAPA8AAAAAAADGAQAAAAAAAEAdAAAAAAAA/gMAAAAAAADgPwAAAAAAAPwAAAAAAAAA4D8AAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
12
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwH8AAAAAAAAAAAAAAAAAAAB4AAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAAAAA8H8DAAAAAAAAAAAAAAAAAH4ABwAAAAAAAAAAAAAAAIAPAAwAAAAAAAAAAAAAAATgAQAYAAAAAAAAAAAAAAAAPAAAMAAAAAAAAAAAAAAAAA8AIHgAAAAAAAAAAAAAAMADACDYAAAAAAAAAAAAAAD+AAAwkAEAAAAAAAAAAACADwAAOBADAAAAAAAAAAAAwAMAADgAAwAAAAAAAPwfAPABAABwAAYAAAAAAID/////AAAA/AMMAAAAAADAB4A/AAAAAAEPAAAAAAAA4AEAAAAADMABOAwAAAAAAH8AAAAAAADgAHAMAAAAAOAfAAAAAABgMABADAAAAIC7DwAAAAAAQBgAAAAAAADgzQ8AAAAAAMAcAAAAAAAA8AAOAAAAAACADwAAAAAAAHgADgAAAAAAgAcAAAAAAAA4AA4AAAAAAAADAAAAAAAACAAOAAAAAACAAQAAAAAAAAAADgAIAAAAgAEAAAAAAAAAAB4ADAAACIABAAAAAAAAAAAeAAwAAAiAAQAAAAAAAAAAvgAMAAAIgAEAAAAAAAAAAPYBBgAACIABAAAAAAAAAABmA38AADiIAQAAAAAAAAAAYoIPAAA/DAMAAAAAAAAAAGKA/wAAeAQGAAAAAAAgAABgwPADAGAEDAAAAAAAIwBgYMCA//PPBzgAAAAAQAYAOHBgAPD/w5/hAAAAAMAEAB4wcAAAAAD7gAEAAADAAAADODAAAAAA5wcDAAAAgAMABx0cAAAAAIY/BwAAAAB/gO8fDgAAAAAscQwAAAAAfsD3DwcAAAAAOOMAAAAAAADA+IMBAAAAADDCAwAAAAAAYN7AAAAAAABgBgMAAAAAAADHYAAAAAAAwAw2AAAAAAAww3AAAAAAAMAMbgAAAAAAmMExAAAAAACADewAAAAA/syAEQAAAAAAgA/YAAAAAI9kgBkAAAAAAAAXmAEAAACHMwAbAAAAAAAAPrgDAAAAjjEAEwAAAAAAAHz4AwAAAJgYABIAAAAAAAD88AcAAADgHwA2AAAAAAAA+MEMAAAAwAcANgAAAAAAAPBDDAAAAAACAGwAAAAAAAAwwggAAAAAAADMAAAAAAAAYIAfAAAAAAAAiAEAAAAAAOAcFAAAAAAAAIwBAAAAAADAewAAAAAAAAC+AwAAAAAAgPcAAAAAAAAAPA8AAAAAAADGAQAAAAAAAEAdAAAAAAAA/gMAAAAAAADgPwAAAAAAAPwAAAAAAAAA4D8AAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
13
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+PgAAAAAAAAAAAAAAAAAA4DwAAAAAAAAAAAAAAAAAAMAtAAAAAAAAAAAAAAAAABj43wAAAAAAAAAAAAAAAAAMEMABAAAAAAAAAAAAAAAAwAGAAwAAAAAAAAAAAAAAwGAAAAcAAAAAAAAAAAAAAHA4AAAMAAAAAAAAAAAAAMAgDgAMDwAAAAAAAAAAAADwBwYADB8AAAAAAAAAAAAA8IcAAA42AAAAAAAAAAAAAAB8AAAOZAAAAAAAAAAAAAAADwAADmAAAAAAAAAA/AcAgAMAAA7AAAAAAAAAgP/+APABAAB/gAEAAAAAAPAB4P8/AACA4YEBAAAAAAA4AAjgAQAAAAAHAQAAAAAAHgAAAAAAAAAAjgEAAAAAgAsAAAAAAAAAAIwBAAAAAOAEAAAAAAAAGACIAQAAAAAABAAAAAAAQAwAAAAAAAAACAQAAAAAAMAMAAAAAAAAAA4GAAQAAACABgAAAAAAAAADBAAEAAAAgAMAAAAAAACAAQQABAAAAIABAAAAAAAAAAAAAAQAAAAAAQAAAAAAAAAAAAAGAAAAAAMAAAAAAACAAAAABgAAAAADAAAAAAAAEAAAAAMAAAAAAwAAAAAAABCAF4ADAAAAAAMAAAAAAAAA8A+AHwAAAIADAAAAAAAACBAM4B8AAADAAwAAAAAAAAwGDvA5AAAA4AIAAAAAAAAGGgc4cAAAOGAGAAAAAAAABpwDHMAHAPAADAAAAAAAAAf9AA4A/wzcARgAAAAAAICDv4AHAPD/HwcwAAAAAADDgz/gAQAADAyUYAAAAACg4cEHeAAAAACE8cMBAAAA8IDBDw4AAAAAhMCPAwAAAODP4AsBAAAAAMQABwcAAAAARnPDAQAAAABEABwOAAAAAIB/wwAAAAAAZABwDAAAAADAvAEAAAAAAGYA4AgAAAAA4I0ZAAAAAABmAIBjAAAAAHDQDAAAAAAAZgAAxwMAAAD4/wYAAAAAADYAANgPAAAA/HcDAAAAAAA2AABwHAAAAAC8AwAAAAAAHgAAwPEAAADAnAEAAAAAAB4AAIDvAwAA8McAAAAAAAAPAAAAjw8AALjjAAAAAAAADwAAAAgAAAAcfwAAAAAAgA8AAAD4AQAALD8AAAAAAIALAAAA8AEAAP4/AAAAAACADwAAAAAAAAB4AAAAAAAAwAwAAAAAAAAAAAAAAAAAAMAMAAAAAAAAAAAAAAAAAADADAAAAAAAAAAAAAAAAAAAgA8AAAAAAAAAAAAAAAAAAAA7AAAAAAAAAAAAAAAAAAAAcwAAAAAAAAAAAAAAAAAAAOMAAAAAAAAAAAAAAAAAAAD/AQAAAAAAAAAAAAAAAAAA/wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
14
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAADAHAAAAAAAAAAAAAAAAAAA4A8AAAAAAAAAAAAAAAAAIPhvAAAAAAAAAAAAAAAAAPAcwAAAAAAAAAAAAAAAAACAAIABAAAAAAAAAAAAAAAAAgAAAwAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAMAIYPAAAAAAAAAAAAAABwBgCHCwAAAAAAAAAAAAAAAAMAAxgAAAAAAAAAAAAAQIADAAcTAAAAAAAAAAAAAAD+AAAHIAAAAAAAAADwAQAAHgAAD2AAAAAAAAAA/x8AgAcAgHvAAAAAAAAAwAf4B8ABAIDhgQAAAAAAAHAAAOdvAAQAgIMAAAAAAIAfAAAAAAAMAADGAAAAAADwAwAAAAAAGAAAxAAAAAAAeAEAAAAAABgQAMQAAAAAAOAFAAAAAAAwGAAAAAAAACwgDAAAAAAAIAwAAAAAAAABAAQAAAAAAEAMAAAAAAAAAAAEAAAAAAAABgAAAAAAAAAADAAAAAAAAAYAAAAAAAAAAAwABAAAAIADAAAAAAAAQAAMAAAAAACAAQAAAAAAAAAADAAAAAAAgAEAAAAAAAAwAAwAAQAAAIABAAAAAAAAGOAPgAEAAACAAQAAAAAAYAzwDYABAAAAgAEAAAAAACAE8AbAHwAAAMAAAAAAAAAwBJADwAcAAADAAAAAAAAAMwH+ATAfAAAAQAAAAAAAABMBfgAA+AAAAAAAAAAAAACbAR8AAOAHAACIAAAAAAAA24EDwAEA/wNgjAAAAAAAAOuAAf4AAPD//4ABAAAAAIDvgMAfAAAA4PkDAQAAAADA98DgAAAAAGCYFwMAAAAA4HpgeAAAAABgDDwDAAAAAMA/YE4AAAAAcAd4BgAAAACADzBHAAAAAHAD+AYAAAAAAACYZwAAAAA4A+AMAAAAAAAAzmcAAAAAuAEAHAAAAAAAMMM7AAAAAIwAABgAAAAAAPjjPgAAAADMAIAxAAAAAAD8sQ4AAAAAbAAAMQAAAAAAPvwHAAAAADYAACYAAAAAAPf/AAAAAAAbAAAcAAAAAID/NwAAAAAACwAAOAAAAACADwAAAAAAgA0AAHAAAAAAAAAAAAAAAMAGAADgBgAAAAAAAAAAAABgBgAAwBwAAAAAAAAAAAAAcAMAAAA4AAAAAAAAAAAAADADAAAA8AAAAAAAAAAAAAAcAQAAAOMHAAAAAAAAAAAA+AEAAAA+PgAAAAAAAAAAALABAAAA+BAAAAAAAAAAAAAgAwAAAOAEAAAAAAAAAAAAYA8AAADABwAAAAAAAAAAAOAPAAAAAAAAAAAAAAAAAADgHQAAAAAAAAAAAAAAAAAA4D8AAAAAAAAAAAAAAAAAAOA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
15
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAgAMAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAAC8HwAAAAAAAAAAAAAAAAAAxDcAAAAAAAAAAAAAAAAAAAxwAAAAAAAAAAAAAAAAAOAPwAAAAAAAAAAAAAAAAADfA4ABAAAAAAAAAAAAAAAA/wHBAwAAAAAAAAAAAAAAAP4A4QcAAAAAAAAAAAAAAAB8gMEGAAAAAAAAAAAAAAAAHoCBDQAAAAAAAAAAAAAAFA+AARgAAAAAAAAAAAAAAJwPgAMQAAAAAAAAAAAAAAD4B4APcAAAAAAAAAD/HwAA/AGAfWAAAAAAAADAv/8DAB8AwOBAAAAAAAAA4ADgf8APAOCAYQAAAAAAgD8AAPD/AQBgAGMAAAAAAPgfAAAASAAIMABgAAAAAAC/AQAAAAAAGDAAAAAAAADA9wkAAAAAABgYAAAAAAAAeDwIAAAAAAAwDAAAAAAAAA4GDAAAAAAAYAwAAAAAAAAAAAwAAAAAAGAGAAAAAAAAAAAMAAQAAABgBgAAAAAAAwAADAAGAAAAQAIAAAAAAAEAAAwAAgAAAMADAAAAAIABAAA8AAIAAADAAwAAAACAAAAALAADAAAAwAEAAAAAAAAAAG4AAwAAAMAAAAAAAAAAAMAHgAEAAADAAAAAAAAAAAD8A4A/AAAAwAAAAAAAAAAA/gHAPwAAAMAAAAAAAAAAgGAA8A8AAAJgAAAAAAAAAMAAAPgfAAAHcAAAAAAAAABgAIA/fAAABzgAAAAAACAAYOD/D/AHAI4fAAAAAAAgADD8/wCA/8fsCwAAAAAAYACIDwAAAPj//wgAAAAAAGAIjAEAAAAA/zkYAAAAAABgBocABgAAAOMwGAAAAAAAPOIxAAYAAABhYBgAAAAAACDsGAAGAACAM+AZAAAAAAAAfA7ABgAAwBjAGwAAAAAAADMHgAYAAOAcwBgAAAAAAIDrA0AGAABgDoAZAAAAAACA/QF4BgAAYAaAMwAAAAAAwF8AfAYAADADACMAAAAAAMAPAEwDAAAAAQAjAAAAAAAAAADeAwAADgAAZgAAAAAAAAAA4gAAAD8AAGQAAAAAAAAAAAIAAMAfAAAMAAAAAAAAAAACAADwDgAAaAAAAAAAAAAAAAAAMAYAABgAAAAAAAAAAAAAAD4DAAAQAAAAAAAAAAAAAAD/AQAAsAAAAAAAAAAAAACA9QAAALABAAAAAAAAAAAAgD0AAABgAwAAAAAAAAAAAMAPAAAAYAMAAAAAAAAAAADAAQAAAGAGAAAAAAAAAAAAAAAAAADgDAAAAAAAAAAAAAAAAAAA4A8AAAAAAAAAAAAAAAAAAIA7AAAAAAAAAAAAAAAAAAAA+wAAAAAAAAAAAAAAAAAAAN8BAAAAAAAAAAAAAAAAAAD+AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
16
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAQAAAAAAAAAAAAAAAAAAfAcAAAAAAAAAAAAAAAAAAPwPAAAAAAAAAAAAAAAAAAAeGQAAAAAAAAAAAAAAAADgADAAAAAAAAAAAAAAAAAA8ABgAAAAAAAAAAAAAAAAAPAAwAAAAAAAAAAAAAAAAADwgOABAAAAAAAAAAAAAAAAfcDwAwAAAAAAAAAAAAAAAD/AYAcAAAAAAAAAAAAAAAAewMAGAAAAAAAAAAAAAADAAcAACAAAAAAAAAAAAAAA+AXAARgAAAAAAAAAAAAAAH4AwA8wAAAAAAAAAPgfAAAeAMA+IAAAAAAAAAA/OACABwBg4DAAAAAAAACAAwD/5wAAAMAxAAAAAAAAAAAY/G8AAACAMQAAAAAAAAwAEAAAAAAAABAAAAAAAMAPAAAAAAAAAAAAAAAAAADgAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAACAAAAAAAAAQAAAAAAAAAPggAAAAAACAGAAAAAIAHAAwIAAAAAAAAAgAAAADAAYADGAAAAAAAQAMAAAAA4ADwARgADAAAAAADAAAAAGAAeAAYAAwAAACAAQAAAAAwCAgAEAAMAAAAwAAAAAAAEAwAADAABgAAgAAAAAAAABAAAAA4AAYAAAAAAAAAAADQAgAAHAAPAAAAAAAAAAAAUAIAAA8APwAAADAAAAAAADACAP4AwD8AgAEwAAAAAAAQAAACAPB4AIABHAAAAAAAGAEAAAA+4AeAAwwAAAAAABgGAAPwH4D/AGMCAAAAAAB4AwAD/wEA8D9/AwAAAAAAvAMA4zEAAACABwMAAAAAAP4BAHMkAAAAgIcBAAAAAAB+AAAZbAAAAICGAAAAAAAAAAAAHWwAAADAxwAAAAAAAAAAgA1sAAAAwMcAAAAAAAAAAIAOzAAAAOBGAAAAAAAAAAAgBswAAABwZgAAAAAAAAAAOAfIAAAAGG8AAAAAAAAAAB8DiAEA+IdnAAAAAAAAAACPA5gBgPzDYwAAAAAAAABAhgGwAcAP/DMAAAAAAAAAQP8AsAHwxg8xAAAAAAAAAGB/ALgB+P8AGAAAAAAAAABgBwC4AVwbABkAAAAAAAAA8AMA+AF8AAABAAAAAAAAAPABAJABHACAAQAAAAAAAABAAACwAAAAgAUAAAAAAAAAAAAA8AAAAIAEAAAAAAAAAAAAAOAAAADABgAAAAAAAAAAAADAAAAAwAYAAAAAAAAAAAAAAAAAAGAGAAAAAAAAAAAAAAAAAAAwBgAAAAAAAAAAAAAAAAAAMAMAAAAAAAAAAAAAAAAAAOAHAAAAAAAAAAAAAAAAAADADQAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIBzAAAAAAAAAAAAAAAAAACA/gAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
17
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAPwBAAAAAAAAAAAAAAAAAAD8AwAAAAAAAAAAAAAAAAAA+A8AAAAAAAAAAAAAAAAAIIAcAAAAAAAAAAAAAAAAAKgBOAAAAAAAAAAAAAAAAACsAHAAAAAAAAAAAAAAAAAApgBgAAAAAAAAAAAAAAAAAP7A8AAAAAAAAAAAAAAAAMD8wPABAAAAAAAAAAAAAABAGGAgAwAAAAAAAAAAAAAAAABgYAMAAAAAAAAAAAAAAAAC4AAGAAAAAAAAAAAAAAAAAuAADAAAAAAAAAAAAAAATwPgBxgAAAAAAAAA4A8AwH8DYB4YAAAAAAAAAPx/AMD/AQBwCAAAAAAAAAAP4P//cQAA4BgAAAAAAACAAwD/PwAAEMAYAAAAAAAA4AAAwAAAAhiACAAAAAAAAHAAAAAAAAYIAAAAAAAAAAAeAAAAAAAECAAAAAAAAADgBwAAAAAADAwAAAAAAAD8+QcAAAAAAAgEAAAAAADg/8EHAAAAAAAIBAAAAAAAfACAGAAAAAAAAAYAAAAAgH8AcBgAAAAAAAACAAAAAMABAD8YABAAAAAAAwAAAAAAAAADEAAQAAAAAAEAAAAAAAAAATAAGAAAAIABAAAAAAAAAAEwABgAAADAAAAAAAAACHAAMAAYAAAA4AAAAAAAAA8/AGAAGAAAAGAAAAAAAMAAAwDgAAwAQAAwAAAAAABgAAAA8AB8AEAAMAAAAAAAMAAAADAAfABAADgAAAAAABgAAAAcAP4AQAAcAAAAAAAMDgDwA8DcBwAABgAAAAAADAcA+AHwD3/gCAMAAAAAAAwDABgAvwP4f44BAAAAAADGAQAA4OMBAOABAAAAAAAAYwAACGZ+AADg4AAAAAAAADkAAAifDwAAYPAAAAAAAAAIAAAIsAMAAOD4AAAAAAAAAAAAiDAGAADw3AAAAAAAAAAAAMhgBgAAcM4AAAAAAAAAAADIwAwAAHjPAAAAAAAAAAAAzIAJAACYzwAAAAAAAAAAAEyAGQAAzuwAAAAAAAAAAABMABMAeMbMAAAAAAAAAAAATAAjAB7nZwAAAAAAAAAAAGQAZwAH42MAAAAAAAAAAIBnAGYAjzk+AAAAAAAAAACAYwBsAJj9HwAAAAAAAAAAgDAAbACACwAAAAAAAAAAAIAdAG4AkAEAAAAAAAAAAAAgDgBOANgBAAAAAAAAAAAAIAcAXgDsAAAAAAAAAAAAALABAEwAYgAAAAAAAAAAAAAwAABMADMAAAAAAAAAAAAAAAAAQIAxAAAAAAAAAAAAAAAAAECAGQAAAAAAAAAAAAAAAAAAgA0AAAAAAAAAAAAAAAAAAIANAAAAAAAAAAAAAAAAAAAAGwAAAAAAAAAAAAAAAAAAACMAAAAAAAAAAAAAAAAAAIBjAAAAAAAAAAAAAAAAAAAAwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
18
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAAAAAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAA+N8BAAAAAAAAAAAAAAAAAMwBBwAAAAAAAAAAAAAAAABmAA4AAAAAAAAAAAAAAAAAPgAYAAAAAAAAAAAAAAAAAB4QPgAAAAAAAAAAAAAAAMAOGG4AAAAAAAAAAAAAAABgBxjIAAAAAAAAAAAAAAAA4AMYCAAAAAAAAAAAAAAAAPABGAADAAAAAAAAAAAAAADwATAABgAAAAAAAAAAAAAA8AHwAQQAAAAAAAAAAAAAAOAB8A8EAAAAAAAAAAAAAAD2Axg4BAAAAAAAAAAADAAA/wAIcAQAAAAAAAAAwP8AwH8ADOAEAAAAAAAAAPD/f/9/AAzAAQAAAAAAAAA8AP7/fwAMAAAAAAAAAAAADwAAwHcABAAAAAAAAAAAgAMAAAAwAAQAAAAAAAAAAMABAAAAAAAEAAAAAAAAAADgAAAAAAAABgAAAAAAAAAAcAAAAAAAAAYAAAAAAAAAADgAIAAAAAAGAAAAAAAAAAA+ACAAAAAAAgAAAAAAgAPANwBgAAAAGAMAAAAAAP///zEAYAAAADADAAAAAMAP/P83AGAAAACwAQAAAADwAADgIwFAAAAAoAEAAAAAGAAAAGABQAAAAOAAAAAAAAAAAABgBsAAAADgAAAAAAAAAAAAwAzAAIAA4AAAAAAAAAAAAMAZQADAAPAAAAAAAAAEAAAAB2AAwAD4AAAAAADABQAAAB7wAMAAeAAAAABAYCAAAAAe8AHAAHgAAAAAQBwwAAAABvgfwPg/AAAAAEAWAAAAAAMe/v9/fwAAAABABgAAAIADB+P/l2MAAAAAwAYAAADwgQ8DgIFhAAAAAMAGAAAAfOAMA4DBwAAAAACAAQAAAAY44A+AwsMBAAAAAAEAAAAGHvAfgGPHAQAAAAAAAAAADAfwe4BzPgMAAAAAAAAAABgDzPuAM3gGAAAAAAAAAAAwAc7PgRvgBAAAAAAAAAAAcALPnIMIwAQAAAAAAAAAAGAG/zjPDOAAAAAAAAAAAADADh/x/QxwAAAAAAAAAAAAgB0ewycGvAMAAAAAAAAAAIA7PAcPB84BAAAAAAAAAAAAc/AO/ON3AAAAAAAAAAAAAGfAc/D8PwAAAAAAAAAAAABugMcAegwAAAAAAAAAAAAAbADPAD8HAAAAAAAAAAAAAG4AnoHpAwAAAAAAAAAAAABmAHzH/wEAAAAAAAAAAAAAcADw5h8AAAAAAAAAAAAAAHYA4I0AAAAAAAAAAAAAAABsAGAMAAAAAAAAAAAAAAAAfADAHwAAAAAAAAAAAAAAAHgAgB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
19
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAAAAAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAA+N8BAAAAAAAAAAAAAAAAAMwBBwAAAAAAAAAAAAAAAABmAA4AAAAAAAAAAAAAAAAAPgAYAAAAAAAAAAAAAAAAAB4QPgAAAAAAAAAAAAAAAMAOGG4AAAAAAAAAAAAAAABgBxhIAAAAAAAAAAAAAAAA4AMYCAAAAAAAAAAAAAAAAPABGAADAAAAAAAAAAAAAADwATAABgAAAAAAAAAAAAAA8AHwAQQAAAAAAAAAAAAAAOAB8A8EAAAAAAAAAAAAAAD2Axg4BAAAAAAAAAAADAAA/wAIcAQAAAAAAAAAwP8AwH8ADOAEAAAAAAAAAPD/f/9/AAzAAwAAAAAAAAA8AP7/fwAMAAAAAAAAAAAADwAAwHcABAAAAAAAAAAAgAMAAAAwAAQAAAAAAAAAAMABAAAAAAAEAAAAAAAAAADgAAAAAAAABgAAAAAAAAAAcAAAAAAAAAYAAAAAAAAAADgAMAAAAAAGAAAAAAAAAAA+ACAAAAAAAgAAAAAAgAPANwBgAAAAGAMAAAAAAP///zEAYAAAADADAAAAAMAP/P83AGAAAACwAQAAAADwAADgIwBAAAAAoAEAAAAAGAAAAGAAQAAAAOAAAAAAAAAAAABgBsAAAADgAAAAAAAAAAAAwAzAAIAA4AAAAAAAAAAAAMAZQADAAPAAAAAAAAAEAAAAB2AAwAD4AAAAAADABwAAAB7wAMAAeAAAAABAYAAAAAAe8AHAAHgAAAAAQBwQAAAABvgfwPg/AAAAAEAUAAAAAAMe/v9/fwAAAABABgAAAIADB+P/l2EAAAAAwAYAAADwgQ8DgIFhAAAAAMAGAAAAfOAMA4DBwAAAAACAAQAAAAY44A+AwsMBAAAAAAEAAAAGHvAfgGPHAQAAAAAAAAAADAf0e4BzPgMAAAAAAAAAABgDzPuAM3gGAAAAAAAAAAAwAc7PgRvgBAAAAAAAAAAAcALPnIMIwAQAAAAAAAAAAGAGzzjPDOAAAAAAAAAAAADADp/x/QxwAAAAAAAAAAAAgB2ewycGnAMAAAAAAAAAAIA7PAcOB84BAAAAAAAAAAAAc/Ae/ON3AAAAAAAAAAAAAGfAc/D8PwAAAAAAAAAAAABugMcAegwAAAAAAAAAAAAAbADPAD8HAAAAAAAAAAAAAG4AnoHpAwAAAAAAAAAAAABmAHzH/wEAAAAAAAAAAAAAcADw5h8AAAAAAAAAAAAAAHYA4I0AAAAAAAAAAAAAAABsAGAMAAAAAAAAAAAAAAAAfADAHwAAAAAAAAAAAAAAAHgAgB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
20
+ ];
@@ -8,6 +8,7 @@ export const receiverStyleRegistry = [
8
8
  { id: 'cyclone', maxRows: 14, minRows: 7 },
9
9
  { id: 'nebula', maxRows: 14, minRows: 7 },
10
10
  { id: 'lava-lamp', maxRows: 14, minRows: 7 },
11
+ { id: 'gallop-fine', maxRows: 18, minRows: 7 },
11
12
  { id: 'motion-contour', maxRows: 14, minRows: 8 },
12
13
  { id: 'ribbon', maxRows: 12, minRows: 7 },
13
14
  { id: 'braille-wave', maxRows: 12, minRows: 7 },
@@ -30,7 +31,7 @@ export const receiverStyleRegistry = [
30
31
  { id: 'rotozoomer', maxRows: 14, minRows: 7 },
31
32
  { id: 'mesh', maxRows: 14, minRows: 7 },
32
33
  { id: 'hologram', maxRows: 12, minRows: 7 },
33
- { id: 'mirror', maxRows: 12, minRows: 7 },
34
+ { id: 'equalizer', maxRows: 12, minRows: 7 },
34
35
  { id: 'tunnel', maxRows: 14, minRows: 7 },
35
36
  { id: 'kaleidoscope', maxRows: 14, minRows: 7 },
36
37
  { id: 'phosphene', maxRows: 14, minRows: 7 },
@@ -60,12 +61,21 @@ export const receiverStyleRegistry = [
60
61
  { id: 'skyline', maxRows: 14, minRows: 7 },
61
62
  { id: 'golden-gate', maxRows: 14, minRows: 7 },
62
63
  { id: 'manhattan', maxRows: 14, minRows: 7 },
63
- { id: 'alpine', maxRows: 14, minRows: 7 }
64
+ { id: 'alpine', maxRows: 14, minRows: 7 },
65
+ { id: 'crossfade', maxRows: 14, minRows: 7 },
64
66
  ];
65
67
  export const receiverStyleNames = receiverStyleRegistry.map(style => style.id);
66
- export const defaultReceiverStyle = 'ultracode';
68
+ export const defaultReceiverStyle = 'gallop-fine';
67
69
  export const receiverStyleMetadata = Object.fromEntries(receiverStyleRegistry.map(style => [style.id, style]));
68
70
  const retiredReceiverStyleNames = [
71
+ 'duplex-wave', 'color-drift', 'undertow', 'cube-tide', 'parallax-slabs', 'voxel-swell',
72
+ 'dot-current', 'flip-matrix', 'raster-harp',
73
+ 'spin-lattice', 'packet-storm', 'spark-chamber',
74
+ 'magnetic-bloom', 'liquid-chrome', 'vector-braid', 'halftone-orbit',
75
+ 'sonic-fan', 'raster-disc', 'diamond-current', 'velvet-vortex',
76
+ 'din-stack', 'bass-shutters', 'velvet-dither', 'chrome-sweep', 'prism-steps', 'stereo-iris',
77
+ 'phosphor-marquee', 'ascii-drive', 'prism-bank', 'gallop', 'gallop-ultrafine',
78
+ 'gallop-solid', 'gallop-shaded', 'gallop-dither', 'gallop-hatch', 'gallop-scanline',
69
79
  'scope', 'spectrum', 'oscilloscope', 'sdr', 'signal', 'retro', 'neon', 'waterfall',
70
80
  'cassette', 'casette', 'stars', 'radio-waves', 'raindrops', 'vinyl', 'soundwave',
71
81
  'spectrum-3d', 'tuning-dial', 'rf-constellation', 'rf-constelation', 'sphere', 'mobius',
@@ -86,14 +96,11 @@ const retiredReceiverStyleNames = [
86
96
  'harmonic-harp', 'neon-transit', 'lantern-drift', 'murmuration', 'koi-shoal',
87
97
  'calligraphy', 'aperture-bloom', 'kintsugi', 'sonic-loom', 'river-delta',
88
98
  'peacock-plume', 'eclipse-corona', 'cloud-chamber', 'origami-tide', 'tv-static',
89
- 'sunspot', 'plasma', 'metaballs', 'moonlit-tide', 'motion-blob', 'clifford', 'paris',
99
+ 'sunspot', 'plasma', 'metaballs', 'moonlit-tide', 'motion-blob', 'clifford', 'mirror', 'paris',
90
100
  'kyoto', 'sahara'
91
101
  ];
92
102
  const retiredReceiverStyles = new Set(retiredReceiverStyleNames);
93
103
  export function migrateReceiverStyle(value) {
94
- if (value === 'equalizer') {
95
- return 'ultracode';
96
- }
97
104
  if (value === 'sumi-mountains' || value === 'moonlit-tide') {
98
105
  return 'sumi-ocean';
99
106
  }