@pavus/snake-game 1.0.0 → 1.1.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.
@@ -139,6 +139,6 @@ export const MusicSettings = ({ initial, onApply, onCancel, accentColor, tracks
139
139
  const idx = scrollOffset + i;
140
140
  const isFocused = idx === safeFocus;
141
141
  return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: isFocused ? accent : undefined, dimColor: !isFocused, children: isFocused ? '▶' : ' ' }), _jsx(Text, { color: isFocused ? accent : undefined, bold: isFocused, dimColor: !isFocused, children: track.title }), _jsxs(Text, { dimColor: true, children: ["\u2014 ", track.artist] })] }, track.id));
142
- }), scrollOffset + VISIBLE_ROWS < filtered.length && (_jsxs(Text, { dimColor: true, children: [" \u2193 ", filtered.length - scrollOffset - VISIBLE_ROWS, " more"] }))] })), _jsxs(Box, { marginTop: 1, gap: 2, children: [_jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate" }), _jsx(Text, { dimColor: true, children: "Enter select" }), _jsx(Text, { dimColor: true, children: "Esc cancel" })] })] }))] }));
142
+ }), scrollOffset + VISIBLE_ROWS < filtered.length && (_jsxs(Text, { dimColor: true, children: [" \u2193 ", filtered.length - scrollOffset - VISIBLE_ROWS, " more"] }))] })), _jsxs(Box, { marginTop: 1, gap: 2, children: [_jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate" }), _jsx(Text, { dimColor: true, children: "Enter select" }), _jsx(Text, { dimColor: true, children: "Esc cancel" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "tip: use \"[\" or \"]\" to go back or forward songs" }) })] }))] }));
143
143
  };
144
144
  //# sourceMappingURL=MusicSettings.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MusicSettings.js","sourceRoot":"","sources":["../../src/MusicSettings.tsx"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,YAAY,EACZ,cAAc,EACd,gBAAgB,EAEhB,YAAY,GACb,MAAM,uBAAuB,CAAC;AAkB/B,SAAS,eAAe,CAAC,KAAgB;IACvC,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC;AACzG,CAAC;AAED,SAAS,gBAAgB;IACvB,OAAO,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,cAAc,CAAC,SAAS,EAAE,CAAC;AACvG,CAAC;AAED,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,MAAM,WAAW,GAAG,GAAG,CAAC;AAExB,SAAS,MAAM,CAAC,CAAS;IACvB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAClC,OAAO,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC;AACnC,CAAC;AAED,SAAS,IAAI,CAAC,CAAS;IACrB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAID,MAAM,MAAM,GAAG;IACb,EAAE,GAAG,EAAE,aAAsB,EAAE,KAAK,EAAE,aAAa,EAAE;IACrD,EAAE,GAAG,EAAE,YAAqB,EAAE,KAAK,EAAE,kBAAkB,EAAE;IACzD,EAAE,GAAG,EAAE,WAAqB,EAAE,KAAK,EAAE,iBAAiB,EAAE;CACzD,CAAC;AAWF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAsB,EAAE,EAAE;IACvG,MAAM,MAAM,GAAG,WAAW,IAAI,cAAc,CAAC,MAAM,CAAC;IACpD,MAAM,OAAO,GAAG,MAAM,IAAI,YAAY,CAAC;IACvC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAU,QAAQ,CAAC,CAAC;IAC1D,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAChD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC;QACrC,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,SAAS,EAAE,OAAO,CAAC,SAAS;KAC7B,CAAC,CAAC;IACH,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAgB,OAAO,CAAC,KAAK,CAAC,CAAC;IAEjF,sBAAsB;IACtB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,MAAM,GAAG,OAAO,CACpB,GAAG,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EACjE,CAAC,OAAO,CAAC,CACV,CAAC;IACF,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE;QAChD,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,gBAAgB,CAAC,CAAC;QAC/D,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE;QAC5B,IAAI,CAAC,KAAK;YAAE,OAAO,MAAM,CAAC;QAC1B,MAAM,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACvG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IAEpB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACzE,IAAI,SAAS,GAAG,SAAS,CAAC,OAAO;QAAE,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC;SAC5D,IAAI,SAAS,IAAI,SAAS,CAAC,OAAO,GAAG,YAAY;QAAE,SAAS,CAAC,OAAO,GAAG,SAAS,GAAG,YAAY,GAAG,CAAC,CAAC;IACzG,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,CAAC;IACvC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC;IAE1E,MAAM,KAAK,GAAG,CAAC,KAAoB,EAAE,EAAE;QACrC,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACjC,CAAC,CAAC;IAEF,QAAQ,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACtB,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YAAC,QAAQ,EAAE,CAAC;YAAC,OAAO;QAAC,CAAC;QAEvC,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;YACZ,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YACzD,OAAO;QACT,CAAC;QAED,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,IAAI,GAAG,CAAC,OAAO,EAAI,CAAC;gBAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YACxE,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;gBAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YACxF,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;gBACpC,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;gBACjC,IAAI,CAAC,KAAK;oBAAE,OAAO;gBACnB,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC;gBACzD,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;gBACvE,OAAO;YACT,CAAC;YACD,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBAAC,KAAK,CAAC,aAAa,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;QACnD,CAAC;QAED,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;YACzB,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBACf,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;gBAClC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC;gBAChE,gBAAgB,CAAC,GAAG,CAAC,CAAC;gBACtB,KAAK,CAAC,GAAG,CAAC,CAAC;gBACX,OAAO;YACT,CAAC;YACD,IAAI,GAAG,CAAC,OAAO,EAAI,CAAC;gBAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YACvG,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;gBAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YAEzH,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBAChC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gBAChC,aAAa,CAAC,CAAC,CAAC,CAAC;gBAAC,SAAS,CAAC,OAAO,GAAG,CAAC,CAAC;gBACxC,OAAO;YACT,CAAC;YACD,IAAI,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1D,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;gBAC3B,aAAa,CAAC,CAAC,CAAC,CAAC;gBAAC,SAAS,CAAC,OAAO,GAAG,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,QAAQ,EAAE,CAAC,aAErC,MAAC,GAAG,IAAC,GAAG,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,aAC1B,KAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAE,MAAM,+BAAuB,EAC/C,MAAC,GAAG,IAAC,GAAG,EAAE,CAAC,aACT,KAAC,IAAI,IACH,IAAI,EAAE,OAAO,KAAK,QAAQ,EAC1B,KAAK,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAChD,QAAQ,EAAE,OAAO,KAAK,QAAQ,uBAGzB,EACP,KAAC,IAAI,IAAC,QAAQ,6BAAS,EACvB,KAAC,IAAI,IACH,IAAI,EAAE,OAAO,KAAK,SAAS,EAC3B,KAAK,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EACjD,QAAQ,EAAE,OAAO,KAAK,SAAS,wBAG1B,IACH,EACN,KAAC,IAAI,IAAC,QAAQ,oCAAqB,IAC/B,EAEL,OAAO,KAAK,SAAS,IAAI,CACxB,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,GAAG,EAAE,CAAC,aAC/B,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;wBACvB,MAAM,SAAS,GAAG,CAAC,KAAK,UAAU,CAAC;wBACnC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;wBAC/B,OAAO,CACL,MAAC,GAAG,IAAiB,GAAG,EAAE,CAAC,aACzB,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,SAAS,YAC9D,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GACjB,EACP,KAAC,GAAG,IAAC,KAAK,EAAE,EAAE,YACZ,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,SAAS,YAC9D,KAAK,CAAC,KAAK,GACP,GACH,EACN,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,SAAS,YAC9D,MAAM,CAAC,GAAG,CAAC,GACP,EACP,KAAC,IAAI,IAAC,QAAQ,kBAAE,MAAM,CAAC,GAAG,CAAC,GAAQ,KAZ3B,KAAK,CAAC,GAAG,CAab,CACP,CAAC;oBACJ,CAAC,CAAC,EACF,MAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,aACvB,KAAC,IAAI,IAAC,QAAQ,gDAAuB,EACrC,KAAC,IAAI,IAAC,QAAQ,2CAAkB,EAChC,KAAC,IAAI,IAAC,QAAQ,kCAAmB,EACjC,KAAC,IAAI,IAAC,QAAQ,iCAAkB,IAC5B,IACF,CACP,EAEA,OAAO,KAAK,QAAQ,IAAI,CACvB,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,MAAC,GAAG,IAAC,GAAG,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,aAC1B,KAAC,IAAI,IAAC,QAAQ,mCAAoB,EAClC,KAAC,IAAI,IAAC,KAAK,EAAE,MAAM,YAAG,aAAa,CAAC,KAAK,GAAQ,EACjD,MAAC,IAAI,IAAC,QAAQ,8BAAI,aAAa,CAAC,MAAM,IAAQ,IAC1C,EACN,MAAC,GAAG,IAAC,GAAG,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,aAC1B,KAAC,IAAI,IAAC,QAAQ,8BAAe,EAC7B,KAAC,IAAI,IAAC,KAAK,EAAE,MAAM,YAAG,KAAK,IAAI,GAAG,GAAQ,EAC1C,KAAC,IAAI,IAAC,QAAQ,6BAAS,IACnB,EACL,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CACvB,KAAC,IAAI,IAAC,QAAQ,sDAAuC,CACtD,CAAC,CAAC,CAAC,CACF,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACxB,YAAY,GAAG,CAAC,IAAI,MAAC,IAAI,IAAC,QAAQ,gCAAM,YAAY,aAAa,EACjE,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;gCACxB,MAAM,GAAG,GAAG,YAAY,GAAG,CAAC,CAAC;gCAC7B,MAAM,SAAS,GAAG,GAAG,KAAK,SAAS,CAAC;gCACpC,OAAO,CACL,MAAC,GAAG,IAAgB,GAAG,EAAE,CAAC,aACxB,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,SAAS,YAC9D,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GACjB,EACP,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,SAAS,YAC/E,KAAK,CAAC,KAAK,GACP,EACP,MAAC,IAAI,IAAC,QAAQ,8BAAI,KAAK,CAAC,MAAM,IAAQ,KAP9B,KAAK,CAAC,EAAE,CAQZ,CACP,CAAC;4BACJ,CAAC,CAAC,EACD,YAAY,GAAG,YAAY,GAAG,QAAQ,CAAC,MAAM,IAAI,CAChD,MAAC,IAAI,IAAC,QAAQ,gCAAM,QAAQ,CAAC,MAAM,GAAG,YAAY,GAAG,YAAY,aAAa,CAC/E,IACG,CACP,EACD,MAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,aACvB,KAAC,IAAI,IAAC,QAAQ,4CAAmB,EACjC,KAAC,IAAI,IAAC,QAAQ,mCAAoB,EAClC,KAAC,IAAI,IAAC,QAAQ,iCAAkB,IAC5B,IACF,CACP,IACG,CACP,CAAC;AACJ,CAAC,CAAC","sourcesContent":["/**\n * MusicSettings — In-game music configuration panel for Snake.\n *\n * Two sections (Tab to switch):\n * Volumes — BGM / Tink / SFX, adjust with ← →\n * Tracks — searchable list from freemidi.org\n *\n * Esc / Enter on a track closes and applies changes.\n */\n\nimport { Box, Text, useInput } from 'ink';\nimport { useState, useMemo, useRef } from 'react';\nimport { DEFAULT_COLORS } from './types.js';\nimport {\n MIDI_CATALOG,\n FALLBACK_TRACK,\n DEFAULT_TRACK_ID,\n type MidiTrack,\n freemidiUrls,\n} from './freemidi-catalog.js';\n\nexport type { MidiTrack };\n\nexport interface SelectedTrack {\n title: string;\n artist: string;\n url: string;\n downloadPage?: string;\n}\n\nexport interface MusicConfig {\n track: SelectedTrack;\n musicVolume: number;\n tinkVolume: number;\n sfxVolume: number;\n}\n\nfunction trackToSelected(track: MidiTrack): SelectedTrack {\n const urls = freemidiUrls(track);\n return { title: track.title, artist: track.artist, url: urls.getter, downloadPage: urls.downloadPage };\n}\n\nfunction fallbackSelected(): SelectedTrack {\n return { title: FALLBACK_TRACK.title, artist: FALLBACK_TRACK.artist, url: FALLBACK_TRACK.directUrl };\n}\n\nconst VISIBLE_ROWS = 8;\nconst VOLUME_STEP = 0.1;\n\nfunction volBar(v: number): string {\n const filled = Math.round(v * 10);\n return '█'.repeat(filled) + '░'.repeat(10 - filled);\n}\n\nfunction fmtPct(v: number): string {\n return `${Math.round(v * 100)}%`;\n}\n\nfunction snap(v: number): number {\n return Math.max(0, Math.min(1, Math.round(v * 10) / 10));\n}\n\ntype Section = 'volumes' | 'tracks';\n\nconst VOICES = [\n { key: 'musicVolume' as const, label: 'Music (BGM)' },\n { key: 'tinkVolume' as const, label: 'Tink (per frame)' },\n { key: 'sfxVolume' as const, label: 'SFX (eat / die)' },\n];\n\ninterface MusicSettingsProps {\n initial: MusicConfig;\n onApply: (config: MusicConfig) => void;\n onCancel: () => void;\n accentColor?: string;\n /** Track list to show in the browser. Defaults to the built-in MIDI_CATALOG. */\n tracks?: MidiTrack[];\n}\n\nexport const MusicSettings = ({ initial, onApply, onCancel, accentColor, tracks }: MusicSettingsProps) => {\n const accent = accentColor ?? DEFAULT_COLORS.accent;\n const catalog = tracks ?? MIDI_CATALOG;\n const [section, setSection] = useState<Section>('tracks');\n const [voiceFocus, setVoiceFocus] = useState(0);\n const [volumes, setVolumes] = useState({\n musicVolume: initial.musicVolume,\n tinkVolume: initial.tinkVolume,\n sfxVolume: initial.sfxVolume,\n });\n const [selectedTrack, setSelectedTrack] = useState<SelectedTrack>(initial.track);\n\n // Track browser state\n const [query, setQuery] = useState('');\n const scrollRef = useRef(0);\n const sorted = useMemo(\n () => [...catalog].sort((a, b) => a.title.localeCompare(b.title)),\n [catalog],\n );\n const [trackFocus, setTrackFocus] = useState(() => {\n const idx = sorted.findIndex((t) => t.id === DEFAULT_TRACK_ID);\n return idx >= 0 ? idx : 0;\n });\n const filtered = useMemo(() => {\n if (!query) return sorted;\n const q = query.toLowerCase();\n return sorted.filter((t) => t.title.toLowerCase().includes(q) || t.artist.toLowerCase().includes(q));\n }, [query, sorted]);\n\n const safeFocus = Math.min(trackFocus, Math.max(0, filtered.length - 1));\n if (safeFocus < scrollRef.current) scrollRef.current = safeFocus;\n else if (safeFocus >= scrollRef.current + VISIBLE_ROWS) scrollRef.current = safeFocus - VISIBLE_ROWS + 1;\n const scrollOffset = scrollRef.current;\n const visible = filtered.slice(scrollOffset, scrollOffset + VISIBLE_ROWS);\n\n const apply = (track: SelectedTrack) => {\n onApply({ track, ...volumes });\n };\n\n useInput((input, key) => {\n if (key.escape) { onCancel(); return; }\n\n if (key.tab) {\n setSection((s) => s === 'tracks' ? 'volumes' : 'tracks');\n return;\n }\n\n if (section === 'volumes') {\n if (key.upArrow) { setVoiceFocus((f) => Math.max(0, f - 1)); return; }\n if (key.downArrow) { setVoiceFocus((f) => Math.min(VOICES.length - 1, f + 1)); return; }\n if (key.leftArrow || key.rightArrow) {\n const voice = VOICES[voiceFocus];\n if (!voice) return;\n const delta = key.leftArrow ? -VOLUME_STEP : VOLUME_STEP;\n setVolumes((v) => ({ ...v, [voice.key]: snap(v[voice.key] + delta) }));\n return;\n }\n if (key.return) { apply(selectedTrack); return; }\n }\n\n if (section === 'tracks') {\n if (key.return) {\n const track = filtered[safeFocus];\n const sel = track ? trackToSelected(track) : fallbackSelected();\n setSelectedTrack(sel);\n apply(sel);\n return;\n }\n if (key.upArrow) { setTrackFocus((f) => Math.max(0, Math.min(f, filtered.length - 1) - 1)); return; }\n if (key.downArrow) { setTrackFocus((f) => Math.min(filtered.length - 1, Math.min(f, filtered.length - 1) + 1)); return; }\n\n if (key.backspace || key.delete) {\n setQuery((q) => q.slice(0, -1));\n setTrackFocus(0); scrollRef.current = 0;\n return;\n }\n if (input && !key.ctrl && !key.meta && input.length === 1) {\n setQuery((q) => q + input);\n setTrackFocus(0); scrollRef.current = 0;\n }\n }\n });\n\n return (\n <Box flexDirection=\"column\" paddingX={1}>\n {/* Header */}\n <Box gap={3} marginBottom={1}>\n <Text bold color={accent}>Music Settings</Text>\n <Box gap={1}>\n <Text\n bold={section === 'tracks'}\n color={section === 'tracks' ? accent : undefined}\n dimColor={section !== 'tracks'}\n >\n Tracks\n </Text>\n <Text dimColor>·</Text>\n <Text\n bold={section === 'volumes'}\n color={section === 'volumes' ? accent : undefined}\n dimColor={section !== 'volumes'}\n >\n Volumes\n </Text>\n </Box>\n <Text dimColor>Tab to switch</Text>\n </Box>\n\n {section === 'volumes' && (\n <Box flexDirection=\"column\" gap={0}>\n {VOICES.map((voice, i) => {\n const isFocused = i === voiceFocus;\n const val = volumes[voice.key];\n return (\n <Box key={voice.key} gap={2}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {isFocused ? '▶' : ' '}\n </Text>\n <Box width={18}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {voice.label}\n </Text>\n </Box>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {volBar(val)}\n </Text>\n <Text dimColor>{fmtPct(val)}</Text>\n </Box>\n );\n })}\n <Box marginTop={1} gap={2}>\n <Text dimColor>↑↓ select voice</Text>\n <Text dimColor>← → adjust</Text>\n <Text dimColor>Enter apply</Text>\n <Text dimColor>Esc cancel</Text>\n </Box>\n </Box>\n )}\n\n {section === 'tracks' && (\n <Box flexDirection=\"column\">\n <Box gap={1} marginBottom={1}>\n <Text dimColor>Now playing:</Text>\n <Text color={accent}>{selectedTrack.title}</Text>\n <Text dimColor>— {selectedTrack.artist}</Text>\n </Box>\n <Box gap={1} marginBottom={1}>\n <Text dimColor>Search:</Text>\n <Text color={accent}>{query || ' '}</Text>\n <Text dimColor>▌</Text>\n </Box>\n {filtered.length === 0 ? (\n <Text dimColor>No matches. Backspace to clear.</Text>\n ) : (\n <Box flexDirection=\"column\">\n {scrollOffset > 0 && <Text dimColor> ↑ {scrollOffset} more</Text>}\n {visible.map((track, i) => {\n const idx = scrollOffset + i;\n const isFocused = idx === safeFocus;\n return (\n <Box key={track.id} gap={1}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {isFocused ? '▶' : ' '}\n </Text>\n <Text color={isFocused ? accent : undefined} bold={isFocused} dimColor={!isFocused}>\n {track.title}\n </Text>\n <Text dimColor>— {track.artist}</Text>\n </Box>\n );\n })}\n {scrollOffset + VISIBLE_ROWS < filtered.length && (\n <Text dimColor> ↓ {filtered.length - scrollOffset - VISIBLE_ROWS} more</Text>\n )}\n </Box>\n )}\n <Box marginTop={1} gap={2}>\n <Text dimColor>↑↓ navigate</Text>\n <Text dimColor>Enter select</Text>\n <Text dimColor>Esc cancel</Text>\n </Box>\n </Box>\n )}\n </Box>\n );\n};\n"]}
1
+ {"version":3,"file":"MusicSettings.js","sourceRoot":"","sources":["../../src/MusicSettings.tsx"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,YAAY,EACZ,cAAc,EACd,gBAAgB,EAEhB,YAAY,GACb,MAAM,uBAAuB,CAAC;AAkB/B,SAAS,eAAe,CAAC,KAAgB;IACvC,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC;AACzG,CAAC;AAED,SAAS,gBAAgB;IACvB,OAAO,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,cAAc,CAAC,SAAS,EAAE,CAAC;AACvG,CAAC;AAED,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,MAAM,WAAW,GAAG,GAAG,CAAC;AAExB,SAAS,MAAM,CAAC,CAAS;IACvB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAClC,OAAO,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC;AACnC,CAAC;AAED,SAAS,IAAI,CAAC,CAAS;IACrB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAID,MAAM,MAAM,GAAG;IACb,EAAE,GAAG,EAAE,aAAsB,EAAE,KAAK,EAAE,aAAa,EAAE;IACrD,EAAE,GAAG,EAAE,YAAqB,EAAE,KAAK,EAAE,kBAAkB,EAAE;IACzD,EAAE,GAAG,EAAE,WAAqB,EAAE,KAAK,EAAE,iBAAiB,EAAE;CACzD,CAAC;AAWF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAsB,EAAE,EAAE;IACvG,MAAM,MAAM,GAAG,WAAW,IAAI,cAAc,CAAC,MAAM,CAAC;IACpD,MAAM,OAAO,GAAG,MAAM,IAAI,YAAY,CAAC;IACvC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAU,QAAQ,CAAC,CAAC;IAC1D,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAChD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC;QACrC,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,SAAS,EAAE,OAAO,CAAC,SAAS;KAC7B,CAAC,CAAC;IACH,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAgB,OAAO,CAAC,KAAK,CAAC,CAAC;IAEjF,sBAAsB;IACtB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,MAAM,GAAG,OAAO,CACpB,GAAG,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EACjE,CAAC,OAAO,CAAC,CACV,CAAC;IACF,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE;QAChD,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,gBAAgB,CAAC,CAAC;QAC/D,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE;QAC5B,IAAI,CAAC,KAAK;YAAE,OAAO,MAAM,CAAC;QAC1B,MAAM,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACvG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IAEpB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACzE,IAAI,SAAS,GAAG,SAAS,CAAC,OAAO;QAAE,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC;SAC5D,IAAI,SAAS,IAAI,SAAS,CAAC,OAAO,GAAG,YAAY;QAAE,SAAS,CAAC,OAAO,GAAG,SAAS,GAAG,YAAY,GAAG,CAAC,CAAC;IACzG,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,CAAC;IACvC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC;IAE1E,MAAM,KAAK,GAAG,CAAC,KAAoB,EAAE,EAAE;QACrC,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACjC,CAAC,CAAC;IAEF,QAAQ,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACtB,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YAAC,QAAQ,EAAE,CAAC;YAAC,OAAO;QAAC,CAAC;QAEvC,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;YACZ,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YACzD,OAAO;QACT,CAAC;QAED,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,IAAI,GAAG,CAAC,OAAO,EAAI,CAAC;gBAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YACxE,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;gBAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YACxF,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;gBACpC,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;gBACjC,IAAI,CAAC,KAAK;oBAAE,OAAO;gBACnB,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC;gBACzD,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;gBACvE,OAAO;YACT,CAAC;YACD,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBAAC,KAAK,CAAC,aAAa,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;QACnD,CAAC;QAED,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;YACzB,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBACf,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;gBAClC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC;gBAChE,gBAAgB,CAAC,GAAG,CAAC,CAAC;gBACtB,KAAK,CAAC,GAAG,CAAC,CAAC;gBACX,OAAO;YACT,CAAC;YACD,IAAI,GAAG,CAAC,OAAO,EAAI,CAAC;gBAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YACvG,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;gBAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YAEzH,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBAChC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gBAChC,aAAa,CAAC,CAAC,CAAC,CAAC;gBAAC,SAAS,CAAC,OAAO,GAAG,CAAC,CAAC;gBACxC,OAAO;YACT,CAAC;YACD,IAAI,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1D,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;gBAC3B,aAAa,CAAC,CAAC,CAAC,CAAC;gBAAC,SAAS,CAAC,OAAO,GAAG,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,QAAQ,EAAE,CAAC,aAErC,MAAC,GAAG,IAAC,GAAG,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,aAC1B,KAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAE,MAAM,+BAAuB,EAC/C,MAAC,GAAG,IAAC,GAAG,EAAE,CAAC,aACT,KAAC,IAAI,IACH,IAAI,EAAE,OAAO,KAAK,QAAQ,EAC1B,KAAK,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAChD,QAAQ,EAAE,OAAO,KAAK,QAAQ,uBAGzB,EACP,KAAC,IAAI,IAAC,QAAQ,6BAAS,EACvB,KAAC,IAAI,IACH,IAAI,EAAE,OAAO,KAAK,SAAS,EAC3B,KAAK,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EACjD,QAAQ,EAAE,OAAO,KAAK,SAAS,wBAG1B,IACH,EACN,KAAC,IAAI,IAAC,QAAQ,oCAAqB,IAC/B,EAEL,OAAO,KAAK,SAAS,IAAI,CACxB,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,GAAG,EAAE,CAAC,aAC/B,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;wBACvB,MAAM,SAAS,GAAG,CAAC,KAAK,UAAU,CAAC;wBACnC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;wBAC/B,OAAO,CACL,MAAC,GAAG,IAAiB,GAAG,EAAE,CAAC,aACzB,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,SAAS,YAC9D,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GACjB,EACP,KAAC,GAAG,IAAC,KAAK,EAAE,EAAE,YACZ,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,SAAS,YAC9D,KAAK,CAAC,KAAK,GACP,GACH,EACN,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,SAAS,YAC9D,MAAM,CAAC,GAAG,CAAC,GACP,EACP,KAAC,IAAI,IAAC,QAAQ,kBAAE,MAAM,CAAC,GAAG,CAAC,GAAQ,KAZ3B,KAAK,CAAC,GAAG,CAab,CACP,CAAC;oBACJ,CAAC,CAAC,EACF,MAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,aACvB,KAAC,IAAI,IAAC,QAAQ,gDAAuB,EACrC,KAAC,IAAI,IAAC,QAAQ,2CAAkB,EAChC,KAAC,IAAI,IAAC,QAAQ,kCAAmB,EACjC,KAAC,IAAI,IAAC,QAAQ,iCAAkB,IAC5B,IACF,CACP,EAEA,OAAO,KAAK,QAAQ,IAAI,CACvB,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,MAAC,GAAG,IAAC,GAAG,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,aAC1B,KAAC,IAAI,IAAC,QAAQ,mCAAoB,EAClC,KAAC,IAAI,IAAC,KAAK,EAAE,MAAM,YAAG,aAAa,CAAC,KAAK,GAAQ,EACjD,MAAC,IAAI,IAAC,QAAQ,8BAAI,aAAa,CAAC,MAAM,IAAQ,IAC1C,EACN,MAAC,GAAG,IAAC,GAAG,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,aAC1B,KAAC,IAAI,IAAC,QAAQ,8BAAe,EAC7B,KAAC,IAAI,IAAC,KAAK,EAAE,MAAM,YAAG,KAAK,IAAI,GAAG,GAAQ,EAC1C,KAAC,IAAI,IAAC,QAAQ,6BAAS,IACnB,EACL,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CACvB,KAAC,IAAI,IAAC,QAAQ,sDAAuC,CACtD,CAAC,CAAC,CAAC,CACF,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACxB,YAAY,GAAG,CAAC,IAAI,MAAC,IAAI,IAAC,QAAQ,gCAAM,YAAY,aAAa,EACjE,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;gCACxB,MAAM,GAAG,GAAG,YAAY,GAAG,CAAC,CAAC;gCAC7B,MAAM,SAAS,GAAG,GAAG,KAAK,SAAS,CAAC;gCACpC,OAAO,CACL,MAAC,GAAG,IAAgB,GAAG,EAAE,CAAC,aACxB,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,SAAS,YAC9D,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GACjB,EACP,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,SAAS,YAC/E,KAAK,CAAC,KAAK,GACP,EACP,MAAC,IAAI,IAAC,QAAQ,8BAAI,KAAK,CAAC,MAAM,IAAQ,KAP9B,KAAK,CAAC,EAAE,CAQZ,CACP,CAAC;4BACJ,CAAC,CAAC,EACD,YAAY,GAAG,YAAY,GAAG,QAAQ,CAAC,MAAM,IAAI,CAChD,MAAC,IAAI,IAAC,QAAQ,gCAAM,QAAQ,CAAC,MAAM,GAAG,YAAY,GAAG,YAAY,aAAa,CAC/E,IACG,CACP,EACD,MAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,aACvB,KAAC,IAAI,IAAC,QAAQ,4CAAmB,EACjC,KAAC,IAAI,IAAC,QAAQ,mCAAoB,EAClC,KAAC,IAAI,IAAC,QAAQ,iCAAkB,IAC5B,EACN,KAAC,GAAG,IAAC,SAAS,EAAE,CAAC,YACf,KAAC,IAAI,IAAC,QAAQ,0EAAuD,GACjE,IACF,CACP,IACG,CACP,CAAC;AACJ,CAAC,CAAC","sourcesContent":["/**\n * MusicSettings — In-game music configuration panel for Snake.\n *\n * Two sections (Tab to switch):\n * Volumes — BGM / Tink / SFX, adjust with ← →\n * Tracks — searchable list from freemidi.org\n *\n * Esc / Enter on a track closes and applies changes.\n */\n\nimport { Box, Text, useInput } from 'ink';\nimport { useState, useMemo, useRef } from 'react';\nimport { DEFAULT_COLORS } from './types.js';\nimport {\n MIDI_CATALOG,\n FALLBACK_TRACK,\n DEFAULT_TRACK_ID,\n type MidiTrack,\n freemidiUrls,\n} from './freemidi-catalog.js';\n\nexport type { MidiTrack };\n\nexport interface SelectedTrack {\n title: string;\n artist: string;\n url: string;\n downloadPage?: string;\n}\n\nexport interface MusicConfig {\n track: SelectedTrack;\n musicVolume: number;\n tinkVolume: number;\n sfxVolume: number;\n}\n\nfunction trackToSelected(track: MidiTrack): SelectedTrack {\n const urls = freemidiUrls(track);\n return { title: track.title, artist: track.artist, url: urls.getter, downloadPage: urls.downloadPage };\n}\n\nfunction fallbackSelected(): SelectedTrack {\n return { title: FALLBACK_TRACK.title, artist: FALLBACK_TRACK.artist, url: FALLBACK_TRACK.directUrl };\n}\n\nconst VISIBLE_ROWS = 8;\nconst VOLUME_STEP = 0.1;\n\nfunction volBar(v: number): string {\n const filled = Math.round(v * 10);\n return '█'.repeat(filled) + '░'.repeat(10 - filled);\n}\n\nfunction fmtPct(v: number): string {\n return `${Math.round(v * 100)}%`;\n}\n\nfunction snap(v: number): number {\n return Math.max(0, Math.min(1, Math.round(v * 10) / 10));\n}\n\ntype Section = 'volumes' | 'tracks';\n\nconst VOICES = [\n { key: 'musicVolume' as const, label: 'Music (BGM)' },\n { key: 'tinkVolume' as const, label: 'Tink (per frame)' },\n { key: 'sfxVolume' as const, label: 'SFX (eat / die)' },\n];\n\ninterface MusicSettingsProps {\n initial: MusicConfig;\n onApply: (config: MusicConfig) => void;\n onCancel: () => void;\n accentColor?: string;\n /** Track list to show in the browser. Defaults to the built-in MIDI_CATALOG. */\n tracks?: MidiTrack[];\n}\n\nexport const MusicSettings = ({ initial, onApply, onCancel, accentColor, tracks }: MusicSettingsProps) => {\n const accent = accentColor ?? DEFAULT_COLORS.accent;\n const catalog = tracks ?? MIDI_CATALOG;\n const [section, setSection] = useState<Section>('tracks');\n const [voiceFocus, setVoiceFocus] = useState(0);\n const [volumes, setVolumes] = useState({\n musicVolume: initial.musicVolume,\n tinkVolume: initial.tinkVolume,\n sfxVolume: initial.sfxVolume,\n });\n const [selectedTrack, setSelectedTrack] = useState<SelectedTrack>(initial.track);\n\n // Track browser state\n const [query, setQuery] = useState('');\n const scrollRef = useRef(0);\n const sorted = useMemo(\n () => [...catalog].sort((a, b) => a.title.localeCompare(b.title)),\n [catalog],\n );\n const [trackFocus, setTrackFocus] = useState(() => {\n const idx = sorted.findIndex((t) => t.id === DEFAULT_TRACK_ID);\n return idx >= 0 ? idx : 0;\n });\n const filtered = useMemo(() => {\n if (!query) return sorted;\n const q = query.toLowerCase();\n return sorted.filter((t) => t.title.toLowerCase().includes(q) || t.artist.toLowerCase().includes(q));\n }, [query, sorted]);\n\n const safeFocus = Math.min(trackFocus, Math.max(0, filtered.length - 1));\n if (safeFocus < scrollRef.current) scrollRef.current = safeFocus;\n else if (safeFocus >= scrollRef.current + VISIBLE_ROWS) scrollRef.current = safeFocus - VISIBLE_ROWS + 1;\n const scrollOffset = scrollRef.current;\n const visible = filtered.slice(scrollOffset, scrollOffset + VISIBLE_ROWS);\n\n const apply = (track: SelectedTrack) => {\n onApply({ track, ...volumes });\n };\n\n useInput((input, key) => {\n if (key.escape) { onCancel(); return; }\n\n if (key.tab) {\n setSection((s) => s === 'tracks' ? 'volumes' : 'tracks');\n return;\n }\n\n if (section === 'volumes') {\n if (key.upArrow) { setVoiceFocus((f) => Math.max(0, f - 1)); return; }\n if (key.downArrow) { setVoiceFocus((f) => Math.min(VOICES.length - 1, f + 1)); return; }\n if (key.leftArrow || key.rightArrow) {\n const voice = VOICES[voiceFocus];\n if (!voice) return;\n const delta = key.leftArrow ? -VOLUME_STEP : VOLUME_STEP;\n setVolumes((v) => ({ ...v, [voice.key]: snap(v[voice.key] + delta) }));\n return;\n }\n if (key.return) { apply(selectedTrack); return; }\n }\n\n if (section === 'tracks') {\n if (key.return) {\n const track = filtered[safeFocus];\n const sel = track ? trackToSelected(track) : fallbackSelected();\n setSelectedTrack(sel);\n apply(sel);\n return;\n }\n if (key.upArrow) { setTrackFocus((f) => Math.max(0, Math.min(f, filtered.length - 1) - 1)); return; }\n if (key.downArrow) { setTrackFocus((f) => Math.min(filtered.length - 1, Math.min(f, filtered.length - 1) + 1)); return; }\n\n if (key.backspace || key.delete) {\n setQuery((q) => q.slice(0, -1));\n setTrackFocus(0); scrollRef.current = 0;\n return;\n }\n if (input && !key.ctrl && !key.meta && input.length === 1) {\n setQuery((q) => q + input);\n setTrackFocus(0); scrollRef.current = 0;\n }\n }\n });\n\n return (\n <Box flexDirection=\"column\" paddingX={1}>\n {/* Header */}\n <Box gap={3} marginBottom={1}>\n <Text bold color={accent}>Music Settings</Text>\n <Box gap={1}>\n <Text\n bold={section === 'tracks'}\n color={section === 'tracks' ? accent : undefined}\n dimColor={section !== 'tracks'}\n >\n Tracks\n </Text>\n <Text dimColor>·</Text>\n <Text\n bold={section === 'volumes'}\n color={section === 'volumes' ? accent : undefined}\n dimColor={section !== 'volumes'}\n >\n Volumes\n </Text>\n </Box>\n <Text dimColor>Tab to switch</Text>\n </Box>\n\n {section === 'volumes' && (\n <Box flexDirection=\"column\" gap={0}>\n {VOICES.map((voice, i) => {\n const isFocused = i === voiceFocus;\n const val = volumes[voice.key];\n return (\n <Box key={voice.key} gap={2}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {isFocused ? '▶' : ' '}\n </Text>\n <Box width={18}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {voice.label}\n </Text>\n </Box>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {volBar(val)}\n </Text>\n <Text dimColor>{fmtPct(val)}</Text>\n </Box>\n );\n })}\n <Box marginTop={1} gap={2}>\n <Text dimColor>↑↓ select voice</Text>\n <Text dimColor>← → adjust</Text>\n <Text dimColor>Enter apply</Text>\n <Text dimColor>Esc cancel</Text>\n </Box>\n </Box>\n )}\n\n {section === 'tracks' && (\n <Box flexDirection=\"column\">\n <Box gap={1} marginBottom={1}>\n <Text dimColor>Now playing:</Text>\n <Text color={accent}>{selectedTrack.title}</Text>\n <Text dimColor>— {selectedTrack.artist}</Text>\n </Box>\n <Box gap={1} marginBottom={1}>\n <Text dimColor>Search:</Text>\n <Text color={accent}>{query || ' '}</Text>\n <Text dimColor>▌</Text>\n </Box>\n {filtered.length === 0 ? (\n <Text dimColor>No matches. Backspace to clear.</Text>\n ) : (\n <Box flexDirection=\"column\">\n {scrollOffset > 0 && <Text dimColor> ↑ {scrollOffset} more</Text>}\n {visible.map((track, i) => {\n const idx = scrollOffset + i;\n const isFocused = idx === safeFocus;\n return (\n <Box key={track.id} gap={1}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {isFocused ? '▶' : ' '}\n </Text>\n <Text color={isFocused ? accent : undefined} bold={isFocused} dimColor={!isFocused}>\n {track.title}\n </Text>\n <Text dimColor>— {track.artist}</Text>\n </Box>\n );\n })}\n {scrollOffset + VISIBLE_ROWS < filtered.length && (\n <Text dimColor> ↓ {filtered.length - scrollOffset - VISIBLE_ROWS} more</Text>\n )}\n </Box>\n )}\n <Box marginTop={1} gap={2}>\n <Text dimColor>↑↓ navigate</Text>\n <Text dimColor>Enter select</Text>\n <Text dimColor>Esc cancel</Text>\n </Box>\n <Box marginTop={1}>\n <Text dimColor>tip: use \"[\" or \"]\" to go back or forward songs</Text>\n </Box>\n </Box>\n )}\n </Box>\n );\n};\n"]}
@@ -7,8 +7,8 @@
7
7
  * the game loop never captures stale closure values.
8
8
  */
9
9
  import { type MidiTrack } from './freemidi-catalog.js';
10
- import { type SnakeColors } from './types.js';
11
- export type { SnakeColors };
10
+ import { type SnakeColors, type SnakeKeybindings } from './types.js';
11
+ export type { SnakeColors, SnakeKeybindings };
12
12
  interface SnakeGameProps {
13
13
  onExit?: () => void;
14
14
  /** Enable or disable all audio (default: true) */
@@ -34,5 +34,12 @@ interface SnakeGameProps {
34
34
  * Defaults to the built-in MIDI_CATALOG from freemidi.org.
35
35
  */
36
36
  tracks?: MidiTrack[];
37
+ /**
38
+ * Override individual key bindings.
39
+ * Each action accepts an array of keys. Use plain characters ('w', ' ', 'r')
40
+ * or arrow-key tokens: 'up', 'down', 'left', 'right', 'return', 'escape'.
41
+ * Defaults include both WASD and arrow keys for movement.
42
+ */
43
+ keybindings?: SnakeKeybindings;
37
44
  }
38
- export declare const SnakeGame: ({ onExit, music, colors, cacheDir, settingsFile, width, height, tracks, }?: SnakeGameProps) => import("react/jsx-runtime").JSX.Element;
45
+ export declare const SnakeGame: ({ onExit, music, colors, cacheDir, settingsFile, width, height, tracks, keybindings, }?: SnakeGameProps) => import("react/jsx-runtime").JSX.Element;
@@ -13,8 +13,30 @@ import { getSnakeHighScore, setSnakeHighScore, getSnakeMusicVolume, setSnakeMusi
13
13
  import { warmNotes, playNote, playSystemSound, SYSTEM_SOUNDS, startBgMusic, stopBgMusic, setMusicVolume, } from './snake-audio.js';
14
14
  import { MusicSettings } from './MusicSettings.js';
15
15
  import { MIDI_CATALOG, DEFAULT_TRACK_ID, freemidiUrls } from './freemidi-catalog.js';
16
- import { resolveColors } from './types.js';
16
+ import { resolveColors, resolveKeybindings } from './types.js';
17
17
  const DEFAULT_BPM = 120;
18
+ // Maps keybinding token strings to Ink Key property names
19
+ const ARROW_KEY_MAP = {
20
+ up: 'upArrow', down: 'downArrow', left: 'leftArrow', right: 'rightArrow',
21
+ return: 'return', escape: 'escape', tab: 'tab',
22
+ };
23
+ function pressed(bindings, input, key) {
24
+ return bindings.some((b) => {
25
+ const prop = ARROW_KEY_MAP[b];
26
+ return prop ? key[prop] === true : input === b;
27
+ });
28
+ }
29
+ const KEY_LABELS = {
30
+ up: '↑', down: '↓', left: '←', right: '→',
31
+ return: '↵', escape: 'Esc', tab: 'Tab', ' ': 'Space',
32
+ };
33
+ function randomOtherTrack(catalog, currentUrl) {
34
+ const others = catalog.filter((t) => freemidiUrls(t).getter !== currentUrl);
35
+ return others.length > 0 ? (others[Math.floor(Math.random() * others.length)] ?? null) : null;
36
+ }
37
+ function displayKey(k) {
38
+ return KEY_LABELS[k] ?? k.toUpperCase();
39
+ }
18
40
  function resolveDefaultTrack(catalog) {
19
41
  const entry = catalog.find((t) => t.id === DEFAULT_TRACK_ID) ?? catalog[0];
20
42
  if (!entry)
@@ -62,8 +84,9 @@ function makeInitial(w, h) {
62
84
  started: false,
63
85
  };
64
86
  }
65
- export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile, width = 20, height = 10, tracks, } = {}) => {
87
+ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile, width = 20, height = 10, tracks, keybindings, } = {}) => {
66
88
  const c = resolveColors(colors);
89
+ const kb = resolveKeybindings(keybindings);
67
90
  const catalog = tracks ?? MIDI_CATALOG;
68
91
  const stateRef = useRef(makeInitial(width, height));
69
92
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
@@ -73,10 +96,22 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
73
96
  const [bpm, setBpm] = useState(DEFAULT_BPM);
74
97
  const [track, setTrack] = useState(() => resolveDefaultTrack(catalog));
75
98
  const [showSettings, setShowSettings] = useState(false);
99
+ const autoPlayRef = useRef(true);
100
+ const nextTrackRef = useRef(() => { });
76
101
  const [beatPhase, setBeatPhase] = useState(0);
77
102
  const [musicVolume, setMusicVolumeState] = useState(() => getSnakeMusicVolume(settingsFile));
78
103
  const [tinkVolume, setTinkVolumeState] = useState(() => getSnakeTinkVolume(settingsFile));
79
104
  const [sfxVolume, setSfxVolumeState] = useState(() => getSnakeSfxVolume(settingsFile));
105
+ // Keep nextTrackRef pointed at a stable "advance to next track" callback
106
+ useEffect(() => {
107
+ nextTrackRef.current = () => {
108
+ const entry = randomOtherTrack(catalog, track.url);
109
+ if (entry) {
110
+ const urls = freemidiUrls(entry);
111
+ setTrack({ title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage });
112
+ }
113
+ };
114
+ }, [track, catalog]); // eslint-disable-line react-hooks/exhaustive-deps
80
115
  useEffect(() => {
81
116
  if (!music)
82
117
  return;
@@ -84,12 +119,28 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
84
119
  stopBgMusic(bgMusic.current);
85
120
  bgMusic.current = null;
86
121
  setBpm(DEFAULT_BPM);
122
+ autoPlayRef.current = true;
123
+ let cancelled = false;
87
124
  void startBgMusic(track.url, track.downloadPage, musicVolume, cacheDir).then((handle) => {
125
+ if (cancelled) {
126
+ stopBgMusic(handle);
127
+ return;
128
+ }
88
129
  bgMusic.current = handle;
89
- if (handle)
130
+ if (handle) {
90
131
  setBpm(handle.bpm);
132
+ handle.proc.on('exit', (_code, signal) => {
133
+ if (signal == null && autoPlayRef.current)
134
+ nextTrackRef.current();
135
+ });
136
+ }
91
137
  });
92
- return () => { stopBgMusic(bgMusic.current); };
138
+ return () => {
139
+ cancelled = true;
140
+ autoPlayRef.current = false;
141
+ stopBgMusic(bgMusic.current);
142
+ bgMusic.current = null;
143
+ };
93
144
  }, [track, music]); // eslint-disable-line react-hooks/exhaustive-deps
94
145
  useEffect(() => {
95
146
  const saved = getSnakeHighScore(settingsFile);
@@ -157,15 +208,15 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
157
208
  }, tickMs);
158
209
  return () => clearInterval(id);
159
210
  }, [bpm]);
160
- useInput((input) => {
211
+ useInput((input, key) => {
161
212
  if (showSettings)
162
213
  return;
163
214
  const g = stateRef.current;
164
- if (input === 'q' && onExit) {
215
+ if (pressed(kb.quit, input, key) && onExit) {
165
216
  onExit();
166
217
  return;
167
218
  }
168
- if (input === 'm' && music) {
219
+ if (pressed(kb.music, input, key) && music) {
169
220
  if (g.started && !g.gameOver && !g.paused) {
170
221
  stateRef.current = { ...g, paused: true };
171
222
  forceUpdate();
@@ -173,29 +224,48 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
173
224
  setShowSettings(true);
174
225
  return;
175
226
  }
227
+ if (music) {
228
+ const goToTrack = (entry) => {
229
+ const urls = freemidiUrls(entry);
230
+ setTrack({ title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage });
231
+ };
232
+ if (pressed(kb.nextTrack, input, key)) {
233
+ const entry = randomOtherTrack(catalog, track.url);
234
+ if (entry)
235
+ goToTrack(entry);
236
+ return;
237
+ }
238
+ if (pressed(kb.prevTrack, input, key)) {
239
+ const idx = catalog.findIndex((t) => freemidiUrls(t).getter === track.url);
240
+ const entry = catalog[(idx - 1 + catalog.length) % catalog.length];
241
+ if (entry)
242
+ goToTrack(entry);
243
+ return;
244
+ }
245
+ }
176
246
  if (!g.started || g.gameOver) {
177
- if (input === 'r' || input === ' ')
247
+ if (pressed(kb.restart, input, key) || pressed(kb.pause, input, key))
178
248
  reset();
179
249
  return;
180
250
  }
181
- if (input === ' ') {
251
+ if (pressed(kb.pause, input, key)) {
182
252
  stateRef.current = { ...g, paused: !g.paused };
183
253
  forceUpdate();
184
254
  return;
185
255
  }
186
- if (input === 'r') {
256
+ if (pressed(kb.restart, input, key)) {
187
257
  reset();
188
258
  return;
189
259
  }
190
260
  const lastDir = g.dirQueue[g.dirQueue.length - 1] ?? g.dir;
191
261
  let next = null;
192
- if (input === 'w' && lastDir.dy !== 1)
262
+ if (pressed(kb.up, input, key) && lastDir.dy !== 1)
193
263
  next = DIRS.up;
194
- else if (input === 's' && lastDir.dy !== -1)
264
+ else if (pressed(kb.down, input, key) && lastDir.dy !== -1)
195
265
  next = DIRS.down;
196
- else if (input === 'a' && lastDir.dx !== 1)
266
+ else if (pressed(kb.left, input, key) && lastDir.dx !== 1)
197
267
  next = DIRS.left;
198
- else if (input === 'd' && lastDir.dx !== -1)
268
+ else if (pressed(kb.right, input, key) && lastDir.dx !== -1)
199
269
  next = DIRS.right;
200
270
  if (next && g.dirQueue.length < 2) {
201
271
  stateRef.current = { ...g, dirQueue: [...g.dirQueue, next] };
@@ -205,6 +275,10 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
205
275
  return (_jsx(MusicSettings, { initial: { track, musicVolume, tinkVolume, sfxVolume }, accentColor: c.accent, tracks: catalog, onApply: (cfg) => {
206
276
  if (cfg.musicVolume !== musicVolume && bgMusic.current) {
207
277
  bgMusic.current = setMusicVolume(bgMusic.current, cfg.musicVolume);
278
+ bgMusic.current.proc.on('exit', (_code, signal) => {
279
+ if (signal == null && autoPlayRef.current)
280
+ nextTrackRef.current();
281
+ });
208
282
  }
209
283
  if (cfg.musicVolume !== musicVolume) {
210
284
  setSnakeMusicVolume(cfg.musicVolume, settingsFile);
@@ -233,7 +307,7 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
233
307
  return 'food';
234
308
  return 'empty';
235
309
  }));
236
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { bold: true, color: c.accent, children: "Snake" }), _jsxs(Text, { dimColor: true, children: ["Score: ", _jsx(Text, { bold: true, children: score })] }), highScore > 0 && _jsxs(Text, { dimColor: true, children: ["Best: ", _jsx(Text, { bold: true, children: highScore })] }), paused && _jsx(Text, { color: "yellow", children: " PAUSED" })] }), music && (_jsxs(_Fragment, { children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "\u266A" }), _jsx(Text, { dimColor: true, children: track.title }), _jsx(Text, { dimColor: true, children: "\u2014" }), _jsx(Text, { dimColor: true, children: track.artist })] }), _jsx(MusicVisualizer, { beatPhase: beatPhase, active: started && !paused && !gameOver, beatColors: c.beat })] })), _jsx(Box, { height: 1 }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: '┌' + '──'.repeat(width) + '┐' }), cells.map((row, y) => (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "\u2502" }), row.map((cell, x) => {
310
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { bold: true, color: c.accent, children: "Snake" }), _jsxs(Text, { color: "white", children: ["Score: ", _jsx(Text, { bold: true, children: score })] }), highScore > 0 && _jsxs(Text, { color: "white", children: ["Best: ", _jsx(Text, { bold: true, children: highScore })] }), paused && _jsx(Text, { color: "yellow", children: " PAUSED" })] }), music && (_jsxs(_Fragment, { children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "\u266A" }), _jsx(Text, { dimColor: true, children: track.title }), _jsx(Text, { dimColor: true, children: "\u2014" }), _jsx(Text, { dimColor: true, children: track.artist })] }), _jsx(MusicVisualizer, { beatPhase: beatPhase, active: started && !paused && !gameOver, beatColors: c.beat })] })), _jsx(Box, { height: 1 }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: '┌' + '──'.repeat(width) + '┐' }), cells.map((row, y) => (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "\u2502" }), row.map((cell, x) => {
237
311
  if (cell === 'head')
238
312
  return _jsx(Text, { color: c.head, bold: true, children: '● ' }, x);
239
313
  if (cell === 'body')
@@ -241,6 +315,6 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
241
315
  if (cell === 'food')
242
316
  return _jsx(Text, { color: c.food, children: '◆ ' }, x);
243
317
  return _jsx(Text, { dimColor: true, children: '· ' }, x);
244
- }), _jsx(Text, { dimColor: true, children: "\u2502" })] }, y))), _jsx(Text, { dimColor: true, children: '└' + '──'.repeat(width) + '┘' })] }), _jsx(Box, { height: 1 }), !started && (_jsxs(Text, { dimColor: true, children: ["Press ", _jsx(Text, { bold: true, color: c.accent, children: "Space" }), " or", ' ', _jsx(Text, { bold: true, color: c.accent, children: "R" }), " to start"] })), gameOver && (_jsxs(Text, { children: [_jsx(Text, { color: "red", children: "Game over! " }), _jsx(Text, { dimColor: true, children: "Press " }), _jsx(Text, { bold: true, color: c.accent, children: "R" }), _jsx(Text, { dimColor: true, children: " to restart" })] })), started && !gameOver && (_jsxs(Text, { dimColor: true, children: ["WASD to move \u00B7 Space to pause \u00B7 R to restart", music ? ' · M for music' : ''] })), !started && music && _jsx(Text, { dimColor: true, children: "M to change music" }), onExit && _jsxs(Text, { dimColor: true, children: ["Press ", _jsx(Text, { bold: true, children: "Q" }), " to exit"] })] }));
318
+ }), _jsx(Text, { dimColor: true, children: "\u2502" })] }, y))), _jsx(Text, { dimColor: true, children: '└' + '──'.repeat(width) + '┘' })] }), _jsx(Box, { height: 1 }), !started && (_jsxs(Text, { dimColor: true, children: ["Press ", _jsx(Text, { bold: true, color: c.accent, children: "Space" }), " or", ' ', _jsx(Text, { bold: true, color: c.accent, children: "R" }), " to start"] })), gameOver && (_jsxs(Text, { children: [_jsx(Text, { color: "red", children: "Game over! " }), _jsx(Text, { dimColor: true, children: "Press " }), _jsx(Text, { bold: true, color: c.accent, children: "R" }), _jsx(Text, { dimColor: true, children: " to restart" })] })), started && !gameOver && (_jsxs(Text, { dimColor: true, children: [displayKey(kb.up[0]), "/", displayKey(kb.down[0]), "/", displayKey(kb.left[0]), "/", displayKey(kb.right[0]), " to move", ' · ', displayKey(kb.pause[0]), " to pause", ' · ', displayKey(kb.restart[0]), " to restart", music ? ` · ${displayKey(kb.music[0])} menu` : ''] })), !started && music && _jsxs(Text, { dimColor: true, children: [displayKey(kb.music[0]), " to change music"] }), onExit && _jsxs(Text, { dimColor: true, children: ["Press ", _jsx(Text, { bold: true, children: displayKey(kb.quit[0]) }), " to exit"] })] }));
245
319
  };
246
320
  //# sourceMappingURL=SnakeGame.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SnakeGame.js","sourceRoot":"","sources":["../../src/SnakeGame.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * SnakeGame — Playable Snake in the terminal.\n *\n * Controls: WASD to move · Space to pause · R to restart · M to change music\n *\n * Uses a ref-backed game state with a single persistent interval so\n * the game loop never captures stale closure values.\n */\n\nimport { Box, Text, useInput } from 'ink';\nimport { useReducer, useRef, useEffect, useState } from 'react';\nimport {\n getSnakeHighScore,\n setSnakeHighScore,\n getSnakeMusicVolume,\n setSnakeMusicVolume,\n getSnakeTinkVolume,\n setSnakeTinkVolume,\n getSnakeSfxVolume,\n setSnakeSfxVolume,\n} from './settings.js';\nimport {\n warmNotes, playNote, playSystemSound, SYSTEM_SOUNDS,\n startBgMusic, stopBgMusic, setMusicVolume, type BgMusicHandle,\n} from './snake-audio.js';\nimport { MusicSettings, type MusicConfig, type SelectedTrack } from './MusicSettings.js';\nimport { MIDI_CATALOG, DEFAULT_TRACK_ID, freemidiUrls, type MidiTrack } from './freemidi-catalog.js';\nimport { type SnakeColors, resolveColors } from './types.js';\n\nexport type { SnakeColors };\n\nconst DEFAULT_BPM = 120;\n\nfunction resolveDefaultTrack(catalog: MidiTrack[]): SelectedTrack {\n const entry = catalog.find((t) => t.id === DEFAULT_TRACK_ID) ?? catalog[0];\n if (!entry) return { title: 'No tracks', artist: '', url: '' };\n const urls = freemidiUrls(entry);\n return { title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage };\n}\n\n// ── Music visualizer ──────────────────────────────────────────────────\n\nfunction MusicVisualizer({\n beatPhase,\n active,\n beatColors,\n}: {\n beatPhase: number;\n active: boolean;\n beatColors: [string, string, string, string];\n}) {\n const beat = Math.floor(beatPhase / 4) % 4;\n\n return (\n <Box>\n {([0, 1, 2, 3] as const).map((b) => {\n const isCurrent = active && b === beat;\n return (\n <Text key={b} color={isCurrent ? beatColors[b] : undefined} dimColor={!isCurrent}>\n {isCurrent ? '●' : '○'}{' '}\n </Text>\n );\n })}\n </Box>\n );\n}\n\ntype Point = { x: number; y: number };\ntype Dir = { dx: number; dy: number };\n\nconst DIRS = {\n up: { dx: 0, dy: -1 },\n down: { dx: 0, dy: 1 },\n left: { dx: -1, dy: 0 },\n right: { dx: 1, dy: 0 },\n} as const;\n\nfunction randomFood(snake: Point[], w: number, h: number): Point {\n const free: Point[] = [];\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n if (!snake.some((s) => s.x === x && s.y === y)) {\n free.push({ x, y });\n }\n }\n }\n return free[Math.floor(Math.random() * free.length)] ?? { x: 0, y: 0 };\n}\n\ntype GameState = {\n snake: Point[];\n dir: Dir;\n dirQueue: Dir[];\n food: Point;\n score: number;\n gameOver: boolean;\n paused: boolean;\n started: boolean;\n};\n\nfunction makeInitial(w: number, h: number): GameState {\n const cx = Math.floor(w / 2);\n const cy = Math.floor(h / 2);\n const snake = [{ x: cx, y: cy }, { x: cx - 1, y: cy }, { x: cx - 2, y: cy }];\n return {\n snake,\n dir: DIRS.right,\n dirQueue: [],\n food: randomFood(snake, w, h),\n score: 0,\n gameOver: false,\n paused: false,\n started: false,\n };\n}\n\ninterface SnakeGameProps {\n onExit?: () => void;\n /** Enable or disable all audio (default: true) */\n music?: boolean;\n /** Override any of the game's colors */\n colors?: SnakeColors;\n /**\n * Directory for cached audio files (synthesized WAVs and per-note tinks).\n * Defaults to the OS temp directory.\n */\n cacheDir?: string;\n /**\n * Path to the JSON file used to persist settings (high score, volumes).\n * Defaults to ~/.snake-game.json\n */\n settingsFile?: string;\n /** Grid width in cells (default: 20) */\n width?: number;\n /** Grid height in cells (default: 10) */\n height?: number;\n /**\n * Track list shown in the music browser.\n * Defaults to the built-in MIDI_CATALOG from freemidi.org.\n */\n tracks?: MidiTrack[];\n}\n\nexport const SnakeGame = ({\n onExit,\n music = true,\n colors,\n cacheDir,\n settingsFile,\n width = 20,\n height = 10,\n tracks,\n}: SnakeGameProps = {}) => {\n const c = resolveColors(colors);\n const catalog = tracks ?? MIDI_CATALOG;\n\n const stateRef = useRef<GameState>(makeInitial(width, height));\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const highScoreRef = useRef(0);\n const [highScore, setHighScore] = useState(0);\n const bgMusic = useRef<BgMusicHandle | null>(null);\n const [bpm, setBpm] = useState(DEFAULT_BPM);\n const [track, setTrack] = useState<SelectedTrack>(() => resolveDefaultTrack(catalog));\n const [showSettings, setShowSettings] = useState(false);\n const [beatPhase, setBeatPhase] = useState(0);\n const [musicVolume, setMusicVolumeState] = useState(() => getSnakeMusicVolume(settingsFile));\n const [tinkVolume, setTinkVolumeState] = useState(() => getSnakeTinkVolume(settingsFile));\n const [sfxVolume, setSfxVolumeState] = useState(() => getSnakeSfxVolume(settingsFile));\n\n useEffect(() => {\n if (!music) return;\n warmNotes([69], cacheDir);\n stopBgMusic(bgMusic.current);\n bgMusic.current = null;\n setBpm(DEFAULT_BPM);\n void startBgMusic(track.url, track.downloadPage, musicVolume, cacheDir).then((handle) => {\n bgMusic.current = handle;\n if (handle) setBpm(handle.bpm);\n });\n return () => { stopBgMusic(bgMusic.current); };\n }, [track, music]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n const saved = getSnakeHighScore(settingsFile);\n highScoreRef.current = saved;\n setHighScore(saved);\n }, []);\n\n const reset = () => {\n stateRef.current = { ...makeInitial(width, height), started: true };\n forceUpdate();\n };\n\n // Beat phase for music visualizer — runs independently of game state\n useEffect(() => {\n if (!music) return;\n const tickMs = Math.round((60_000 / bpm) / 4);\n const id = setInterval(() => setBeatPhase((p) => (p + 1) % 16), tickMs);\n return () => clearInterval(id);\n }, [bpm, music]);\n\n // Game loop — interval recreates when bpm changes (once synthesis completes)\n useEffect(() => {\n const tickMs = Math.round((60_000 / bpm) / 4); // one 16th note\n const id = setInterval(() => {\n const g = stateRef.current;\n if (!g.started || g.gameOver || g.paused) return;\n\n const [nextDir, ...restQueue] = g.dirQueue.length > 0 ? g.dirQueue : [g.dir];\n const dir = nextDir;\n const head = { x: g.snake[0].x + dir.dx, y: g.snake[0].y + dir.dy };\n\n if (\n head.x < 0 ||\n head.x >= width ||\n head.y < 0 ||\n head.y >= height ||\n g.snake.some((s) => s.x === head.x && s.y === head.y)\n ) {\n stateRef.current = { ...g, dir, dirQueue: restQueue, gameOver: true };\n if (music) playSystemSound(SYSTEM_SOUNDS.die, sfxVolume);\n forceUpdate();\n return;\n }\n\n const ate = head.x === g.food.x && head.y === g.food.y;\n const snake = ate ? [head, ...g.snake] : [head, ...g.snake.slice(0, -1)];\n const newScore = ate ? g.score + 1 : g.score;\n\n if (ate) {\n if (newScore > highScoreRef.current) {\n highScoreRef.current = newScore;\n setSnakeHighScore(newScore, settingsFile);\n setHighScore(newScore);\n }\n if (music) playSystemSound(SYSTEM_SOUNDS.eat, sfxVolume);\n } else {\n if (music) playNote(69, tinkVolume, cacheDir);\n }\n\n stateRef.current = {\n ...g,\n snake,\n dir,\n dirQueue: restQueue,\n food: ate ? randomFood(snake, width, height) : g.food,\n score: newScore,\n };\n forceUpdate();\n }, tickMs);\n\n return () => clearInterval(id);\n }, [bpm]);\n\n useInput((input) => {\n if (showSettings) return;\n\n const g = stateRef.current;\n\n if (input === 'q' && onExit) { onExit(); return; }\n\n if (input === 'm' && music) {\n if (g.started && !g.gameOver && !g.paused) {\n stateRef.current = { ...g, paused: true };\n forceUpdate();\n }\n setShowSettings(true);\n return;\n }\n\n if (!g.started || g.gameOver) {\n if (input === 'r' || input === ' ') reset();\n return;\n }\n\n if (input === ' ') { stateRef.current = { ...g, paused: !g.paused }; forceUpdate(); return; }\n if (input === 'r') { reset(); return; }\n\n const lastDir = g.dirQueue[g.dirQueue.length - 1] ?? g.dir;\n let next: Dir | null = null;\n if (input === 'w' && lastDir.dy !== 1) next = DIRS.up;\n else if (input === 's' && lastDir.dy !== -1) next = DIRS.down;\n else if (input === 'a' && lastDir.dx !== 1) next = DIRS.left;\n else if (input === 'd' && lastDir.dx !== -1) next = DIRS.right;\n if (next && g.dirQueue.length < 2) {\n stateRef.current = { ...g, dirQueue: [...g.dirQueue, next] };\n }\n });\n\n if (showSettings) {\n return (\n <MusicSettings\n initial={{ track, musicVolume, tinkVolume, sfxVolume }}\n accentColor={c.accent}\n tracks={catalog}\n onApply={(cfg: MusicConfig) => {\n if (cfg.musicVolume !== musicVolume && bgMusic.current) {\n bgMusic.current = setMusicVolume(bgMusic.current, cfg.musicVolume);\n }\n if (cfg.musicVolume !== musicVolume) {\n setSnakeMusicVolume(cfg.musicVolume, settingsFile);\n setMusicVolumeState(cfg.musicVolume);\n }\n if (cfg.tinkVolume !== tinkVolume) {\n setSnakeTinkVolume(cfg.tinkVolume, settingsFile);\n setTinkVolumeState(cfg.tinkVolume);\n }\n if (cfg.sfxVolume !== sfxVolume) {\n setSnakeSfxVolume(cfg.sfxVolume, settingsFile);\n setSfxVolumeState(cfg.sfxVolume);\n }\n if (cfg.track.url !== track.url) setTrack(cfg.track);\n setShowSettings(false);\n }}\n onCancel={() => setShowSettings(false)}\n />\n );\n }\n\n const { snake, food, score, gameOver, paused, started } = stateRef.current;\n\n const cells = Array.from({ length: height }, (_, y) =>\n Array.from({ length: width }, (_, x) => {\n if (x === snake[0]?.x && y === snake[0]?.y) return 'head';\n if (snake.some((s) => s.x === x && s.y === y)) return 'body';\n if (x === food.x && y === food.y) return 'food';\n return 'empty';\n }),\n );\n\n return (\n <Box flexDirection=\"column\" paddingX={1}>\n <Box gap={2}>\n <Text bold color={c.accent}>Snake</Text>\n <Text dimColor>Score: <Text bold>{score}</Text></Text>\n {highScore > 0 && <Text dimColor>Best: <Text bold>{highScore}</Text></Text>}\n {paused && <Text color=\"yellow\"> PAUSED</Text>}\n </Box>\n {music && (\n <>\n <Box gap={1}>\n <Text dimColor>♪</Text>\n <Text dimColor>{track.title}</Text>\n <Text dimColor>—</Text>\n <Text dimColor>{track.artist}</Text>\n </Box>\n <MusicVisualizer beatPhase={beatPhase} active={started && !paused && !gameOver} beatColors={c.beat} />\n </>\n )}\n <Box height={1} />\n <Box flexDirection=\"column\">\n <Text dimColor>{'┌' + '──'.repeat(width) + '┐'}</Text>\n {cells.map((row, y) => (\n <Box key={y}>\n <Text dimColor>│</Text>\n {row.map((cell, x) => {\n if (cell === 'head') return <Text key={x} color={c.head} bold>{'● '}</Text>;\n if (cell === 'body') return <Text key={x} color={c.body}>{'● '}</Text>;\n if (cell === 'food') return <Text key={x} color={c.food}>{'◆ '}</Text>;\n return <Text key={x} dimColor>{'· '}</Text>;\n })}\n <Text dimColor>│</Text>\n </Box>\n ))}\n <Text dimColor>{'└' + '──'.repeat(width) + '┘'}</Text>\n </Box>\n <Box height={1} />\n {!started && (\n <Text dimColor>\n Press <Text bold color={c.accent}>Space</Text> or{' '}\n <Text bold color={c.accent}>R</Text> to start\n </Text>\n )}\n {gameOver && (\n <Text>\n <Text color=\"red\">Game over! </Text>\n <Text dimColor>Press </Text>\n <Text bold color={c.accent}>R</Text>\n <Text dimColor> to restart</Text>\n </Text>\n )}\n {started && !gameOver && (\n <Text dimColor>\n WASD to move · Space to pause · R to restart{music ? ' · M for music' : ''}\n </Text>\n )}\n {!started && music && <Text dimColor>M to change music</Text>}\n {onExit && <Text dimColor>Press <Text bold>Q</Text> to exit</Text>}\n </Box>\n );\n};\n"]}
1
+ {"version":3,"file":"SnakeGame.js","sourceRoot":"","sources":["../../src/SnakeGame.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * SnakeGame — Playable Snake in the terminal.\n *\n * Controls: WASD to move · Space to pause · R to restart · M to change music\n *\n * Uses a ref-backed game state with a single persistent interval so\n * the game loop never captures stale closure values.\n */\n\nimport { Box, Text, useInput, type Key } from 'ink';\nimport { useReducer, useRef, useEffect, useState } from 'react';\nimport {\n getSnakeHighScore,\n setSnakeHighScore,\n getSnakeMusicVolume,\n setSnakeMusicVolume,\n getSnakeTinkVolume,\n setSnakeTinkVolume,\n getSnakeSfxVolume,\n setSnakeSfxVolume,\n} from './settings.js';\nimport {\n warmNotes, playNote, playSystemSound, SYSTEM_SOUNDS,\n startBgMusic, stopBgMusic, setMusicVolume, type BgMusicHandle,\n} from './snake-audio.js';\nimport { MusicSettings, type MusicConfig, type SelectedTrack } from './MusicSettings.js';\nimport { MIDI_CATALOG, DEFAULT_TRACK_ID, freemidiUrls, type MidiTrack } from './freemidi-catalog.js';\nimport { type SnakeColors, resolveColors, type SnakeKeybindings, resolveKeybindings } from './types.js';\n\nexport type { SnakeColors, SnakeKeybindings };\n\nconst DEFAULT_BPM = 120;\n\n// Maps keybinding token strings to Ink Key property names\nconst ARROW_KEY_MAP: Record<string, keyof Key> = {\n up: 'upArrow', down: 'downArrow', left: 'leftArrow', right: 'rightArrow',\n return: 'return', escape: 'escape', tab: 'tab',\n};\n\nfunction pressed(bindings: string[], input: string, key: Key): boolean {\n return bindings.some((b) => {\n const prop = ARROW_KEY_MAP[b];\n return prop ? (key[prop] as boolean) === true : input === b;\n });\n}\n\nconst KEY_LABELS: Record<string, string> = {\n up: '↑', down: '↓', left: '←', right: '→',\n return: '↵', escape: 'Esc', tab: 'Tab', ' ': 'Space',\n};\nfunction randomOtherTrack(catalog: MidiTrack[], currentUrl: string): MidiTrack | null {\n const others = catalog.filter((t) => freemidiUrls(t).getter !== currentUrl);\n return others.length > 0 ? (others[Math.floor(Math.random() * others.length)] ?? null) : null;\n}\n\nfunction displayKey(k: string): string {\n return KEY_LABELS[k] ?? k.toUpperCase();\n}\n\nfunction resolveDefaultTrack(catalog: MidiTrack[]): SelectedTrack {\n const entry = catalog.find((t) => t.id === DEFAULT_TRACK_ID) ?? catalog[0];\n if (!entry) return { title: 'No tracks', artist: '', url: '' };\n const urls = freemidiUrls(entry);\n return { title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage };\n}\n\n// ── Music visualizer ──────────────────────────────────────────────────\n\nfunction MusicVisualizer({\n beatPhase,\n active,\n beatColors,\n}: {\n beatPhase: number;\n active: boolean;\n beatColors: [string, string, string, string];\n}) {\n const beat = Math.floor(beatPhase / 4) % 4;\n\n return (\n <Box>\n {([0, 1, 2, 3] as const).map((b) => {\n const isCurrent = active && b === beat;\n return (\n <Text key={b} color={isCurrent ? beatColors[b] : undefined} dimColor={!isCurrent}>\n {isCurrent ? '●' : '○'}{' '}\n </Text>\n );\n })}\n </Box>\n );\n}\n\ntype Point = { x: number; y: number };\ntype Dir = { dx: number; dy: number };\n\nconst DIRS = {\n up: { dx: 0, dy: -1 },\n down: { dx: 0, dy: 1 },\n left: { dx: -1, dy: 0 },\n right: { dx: 1, dy: 0 },\n} as const;\n\nfunction randomFood(snake: Point[], w: number, h: number): Point {\n const free: Point[] = [];\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n if (!snake.some((s) => s.x === x && s.y === y)) {\n free.push({ x, y });\n }\n }\n }\n return free[Math.floor(Math.random() * free.length)] ?? { x: 0, y: 0 };\n}\n\ntype GameState = {\n snake: Point[];\n dir: Dir;\n dirQueue: Dir[];\n food: Point;\n score: number;\n gameOver: boolean;\n paused: boolean;\n started: boolean;\n};\n\nfunction makeInitial(w: number, h: number): GameState {\n const cx = Math.floor(w / 2);\n const cy = Math.floor(h / 2);\n const snake = [{ x: cx, y: cy }, { x: cx - 1, y: cy }, { x: cx - 2, y: cy }];\n return {\n snake,\n dir: DIRS.right,\n dirQueue: [],\n food: randomFood(snake, w, h),\n score: 0,\n gameOver: false,\n paused: false,\n started: false,\n };\n}\n\ninterface SnakeGameProps {\n onExit?: () => void;\n /** Enable or disable all audio (default: true) */\n music?: boolean;\n /** Override any of the game's colors */\n colors?: SnakeColors;\n /**\n * Directory for cached audio files (synthesized WAVs and per-note tinks).\n * Defaults to the OS temp directory.\n */\n cacheDir?: string;\n /**\n * Path to the JSON file used to persist settings (high score, volumes).\n * Defaults to ~/.snake-game.json\n */\n settingsFile?: string;\n /** Grid width in cells (default: 20) */\n width?: number;\n /** Grid height in cells (default: 10) */\n height?: number;\n /**\n * Track list shown in the music browser.\n * Defaults to the built-in MIDI_CATALOG from freemidi.org.\n */\n tracks?: MidiTrack[];\n /**\n * Override individual key bindings.\n * Each action accepts an array of keys. Use plain characters ('w', ' ', 'r')\n * or arrow-key tokens: 'up', 'down', 'left', 'right', 'return', 'escape'.\n * Defaults include both WASD and arrow keys for movement.\n */\n keybindings?: SnakeKeybindings;\n}\n\nexport const SnakeGame = ({\n onExit,\n music = true,\n colors,\n cacheDir,\n settingsFile,\n width = 20,\n height = 10,\n tracks,\n keybindings,\n}: SnakeGameProps = {}) => {\n const c = resolveColors(colors);\n const kb = resolveKeybindings(keybindings);\n const catalog = tracks ?? MIDI_CATALOG;\n\n const stateRef = useRef<GameState>(makeInitial(width, height));\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const highScoreRef = useRef(0);\n const [highScore, setHighScore] = useState(0);\n const bgMusic = useRef<BgMusicHandle | null>(null);\n const [bpm, setBpm] = useState(DEFAULT_BPM);\n const [track, setTrack] = useState<SelectedTrack>(() => resolveDefaultTrack(catalog));\n const [showSettings, setShowSettings] = useState(false);\n const autoPlayRef = useRef(true);\n const nextTrackRef = useRef<() => void>(() => {});\n const [beatPhase, setBeatPhase] = useState(0);\n const [musicVolume, setMusicVolumeState] = useState(() => getSnakeMusicVolume(settingsFile));\n const [tinkVolume, setTinkVolumeState] = useState(() => getSnakeTinkVolume(settingsFile));\n const [sfxVolume, setSfxVolumeState] = useState(() => getSnakeSfxVolume(settingsFile));\n\n // Keep nextTrackRef pointed at a stable \"advance to next track\" callback\n useEffect(() => {\n nextTrackRef.current = () => {\n const entry = randomOtherTrack(catalog, track.url);\n if (entry) {\n const urls = freemidiUrls(entry);\n setTrack({ title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage });\n }\n };\n }, [track, catalog]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n if (!music) return;\n warmNotes([69], cacheDir);\n stopBgMusic(bgMusic.current);\n bgMusic.current = null;\n setBpm(DEFAULT_BPM);\n autoPlayRef.current = true;\n let cancelled = false;\n void startBgMusic(track.url, track.downloadPage, musicVolume, cacheDir).then((handle) => {\n if (cancelled) { stopBgMusic(handle); return; }\n bgMusic.current = handle;\n if (handle) {\n setBpm(handle.bpm);\n handle.proc.on('exit', (_code, signal) => {\n if (signal == null && autoPlayRef.current) nextTrackRef.current();\n });\n }\n });\n return () => {\n cancelled = true;\n autoPlayRef.current = false;\n stopBgMusic(bgMusic.current);\n bgMusic.current = null;\n };\n }, [track, music]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n const saved = getSnakeHighScore(settingsFile);\n highScoreRef.current = saved;\n setHighScore(saved);\n }, []);\n\n const reset = () => {\n stateRef.current = { ...makeInitial(width, height), started: true };\n forceUpdate();\n };\n\n // Beat phase for music visualizer — runs independently of game state\n useEffect(() => {\n if (!music) return;\n const tickMs = Math.round((60_000 / bpm) / 4);\n const id = setInterval(() => setBeatPhase((p) => (p + 1) % 16), tickMs);\n return () => clearInterval(id);\n }, [bpm, music]);\n\n // Game loop — interval recreates when bpm changes (once synthesis completes)\n useEffect(() => {\n const tickMs = Math.round((60_000 / bpm) / 4); // one 16th note\n const id = setInterval(() => {\n const g = stateRef.current;\n if (!g.started || g.gameOver || g.paused) return;\n\n const [nextDir, ...restQueue] = g.dirQueue.length > 0 ? g.dirQueue : [g.dir];\n const dir = nextDir;\n const head = { x: g.snake[0].x + dir.dx, y: g.snake[0].y + dir.dy };\n\n if (\n head.x < 0 ||\n head.x >= width ||\n head.y < 0 ||\n head.y >= height ||\n g.snake.some((s) => s.x === head.x && s.y === head.y)\n ) {\n stateRef.current = { ...g, dir, dirQueue: restQueue, gameOver: true };\n if (music) playSystemSound(SYSTEM_SOUNDS.die, sfxVolume);\n forceUpdate();\n return;\n }\n\n const ate = head.x === g.food.x && head.y === g.food.y;\n const snake = ate ? [head, ...g.snake] : [head, ...g.snake.slice(0, -1)];\n const newScore = ate ? g.score + 1 : g.score;\n\n if (ate) {\n if (newScore > highScoreRef.current) {\n highScoreRef.current = newScore;\n setSnakeHighScore(newScore, settingsFile);\n setHighScore(newScore);\n }\n if (music) playSystemSound(SYSTEM_SOUNDS.eat, sfxVolume);\n } else {\n if (music) playNote(69, tinkVolume, cacheDir);\n }\n\n stateRef.current = {\n ...g,\n snake,\n dir,\n dirQueue: restQueue,\n food: ate ? randomFood(snake, width, height) : g.food,\n score: newScore,\n };\n forceUpdate();\n }, tickMs);\n\n return () => clearInterval(id);\n }, [bpm]);\n\n useInput((input, key) => {\n if (showSettings) return;\n\n const g = stateRef.current;\n\n if (pressed(kb.quit, input, key) && onExit) { onExit(); return; }\n\n if (pressed(kb.music, input, key) && music) {\n if (g.started && !g.gameOver && !g.paused) {\n stateRef.current = { ...g, paused: true };\n forceUpdate();\n }\n setShowSettings(true);\n return;\n }\n\n if (music) {\n const goToTrack = (entry: MidiTrack) => {\n const urls = freemidiUrls(entry);\n setTrack({ title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage });\n };\n if (pressed(kb.nextTrack, input, key)) {\n const entry = randomOtherTrack(catalog, track.url);\n if (entry) goToTrack(entry);\n return;\n }\n if (pressed(kb.prevTrack, input, key)) {\n const idx = catalog.findIndex((t) => freemidiUrls(t).getter === track.url);\n const entry = catalog[(idx - 1 + catalog.length) % catalog.length];\n if (entry) goToTrack(entry);\n return;\n }\n }\n\n if (!g.started || g.gameOver) {\n if (pressed(kb.restart, input, key) || pressed(kb.pause, input, key)) reset();\n return;\n }\n\n if (pressed(kb.pause, input, key)) { stateRef.current = { ...g, paused: !g.paused }; forceUpdate(); return; }\n if (pressed(kb.restart, input, key)) { reset(); return; }\n\n const lastDir = g.dirQueue[g.dirQueue.length - 1] ?? g.dir;\n let next: Dir | null = null;\n if (pressed(kb.up, input, key) && lastDir.dy !== 1) next = DIRS.up;\n else if (pressed(kb.down, input, key) && lastDir.dy !== -1) next = DIRS.down;\n else if (pressed(kb.left, input, key) && lastDir.dx !== 1) next = DIRS.left;\n else if (pressed(kb.right, input, key) && lastDir.dx !== -1) next = DIRS.right;\n if (next && g.dirQueue.length < 2) {\n stateRef.current = { ...g, dirQueue: [...g.dirQueue, next] };\n }\n });\n\n if (showSettings) {\n return (\n <MusicSettings\n initial={{ track, musicVolume, tinkVolume, sfxVolume }}\n accentColor={c.accent}\n tracks={catalog}\n onApply={(cfg: MusicConfig) => {\n if (cfg.musicVolume !== musicVolume && bgMusic.current) {\n bgMusic.current = setMusicVolume(bgMusic.current, cfg.musicVolume);\n bgMusic.current.proc.on('exit', (_code, signal) => {\n if (signal == null && autoPlayRef.current) nextTrackRef.current();\n });\n }\n if (cfg.musicVolume !== musicVolume) {\n setSnakeMusicVolume(cfg.musicVolume, settingsFile);\n setMusicVolumeState(cfg.musicVolume);\n }\n if (cfg.tinkVolume !== tinkVolume) {\n setSnakeTinkVolume(cfg.tinkVolume, settingsFile);\n setTinkVolumeState(cfg.tinkVolume);\n }\n if (cfg.sfxVolume !== sfxVolume) {\n setSnakeSfxVolume(cfg.sfxVolume, settingsFile);\n setSfxVolumeState(cfg.sfxVolume);\n }\n if (cfg.track.url !== track.url) setTrack(cfg.track);\n setShowSettings(false);\n }}\n onCancel={() => setShowSettings(false)}\n />\n );\n }\n\n const { snake, food, score, gameOver, paused, started } = stateRef.current;\n\n const cells = Array.from({ length: height }, (_, y) =>\n Array.from({ length: width }, (_, x) => {\n if (x === snake[0]?.x && y === snake[0]?.y) return 'head';\n if (snake.some((s) => s.x === x && s.y === y)) return 'body';\n if (x === food.x && y === food.y) return 'food';\n return 'empty';\n }),\n );\n\n return (\n <Box flexDirection=\"column\" paddingX={1}>\n <Box gap={2}>\n <Text bold color={c.accent}>Snake</Text>\n <Text color=\"white\">Score: <Text bold>{score}</Text></Text>\n {highScore > 0 && <Text color=\"white\">Best: <Text bold>{highScore}</Text></Text>}\n {paused && <Text color=\"yellow\"> PAUSED</Text>}\n </Box>\n {music && (\n <>\n <Box gap={1}>\n <Text dimColor>♪</Text>\n <Text dimColor>{track.title}</Text>\n <Text dimColor>—</Text>\n <Text dimColor>{track.artist}</Text>\n </Box>\n <MusicVisualizer beatPhase={beatPhase} active={started && !paused && !gameOver} beatColors={c.beat} />\n </>\n )}\n <Box height={1} />\n <Box flexDirection=\"column\">\n <Text dimColor>{'┌' + '──'.repeat(width) + '┐'}</Text>\n {cells.map((row, y) => (\n <Box key={y}>\n <Text dimColor>│</Text>\n {row.map((cell, x) => {\n if (cell === 'head') return <Text key={x} color={c.head} bold>{'● '}</Text>;\n if (cell === 'body') return <Text key={x} color={c.body}>{'● '}</Text>;\n if (cell === 'food') return <Text key={x} color={c.food}>{'◆ '}</Text>;\n return <Text key={x} dimColor>{'· '}</Text>;\n })}\n <Text dimColor>│</Text>\n </Box>\n ))}\n <Text dimColor>{'└' + '──'.repeat(width) + '┘'}</Text>\n </Box>\n <Box height={1} />\n {!started && (\n <Text dimColor>\n Press <Text bold color={c.accent}>Space</Text> or{' '}\n <Text bold color={c.accent}>R</Text> to start\n </Text>\n )}\n {gameOver && (\n <Text>\n <Text color=\"red\">Game over! </Text>\n <Text dimColor>Press </Text>\n <Text bold color={c.accent}>R</Text>\n <Text dimColor> to restart</Text>\n </Text>\n )}\n {started && !gameOver && (\n <Text dimColor>\n {displayKey(kb.up[0]!)}/{displayKey(kb.down[0]!)}/{displayKey(kb.left[0]!)}/{displayKey(kb.right[0]!)} to move\n {' · '}{displayKey(kb.pause[0]!)} to pause\n {' · '}{displayKey(kb.restart[0]!)} to restart\n {music ? ` · ${displayKey(kb.music[0]!)} menu` : ''}\n </Text>\n )}\n {!started && music && <Text dimColor>{displayKey(kb.music[0]!)} to change music</Text>}\n {onExit && <Text dimColor>Press <Text bold>{displayKey(kb.quit[0]!)}</Text> to exit</Text>}\n </Box>\n );\n};\n"]}
@@ -27,5 +27,5 @@ export declare const FALLBACK_TRACK: MidiTrack & {
27
27
  directUrl: string;
28
28
  };
29
29
  /** Default selected track. */
30
- export declare const DEFAULT_TRACK_ID = 28946;
30
+ export declare const DEFAULT_TRACK_ID = 11747;
31
31
  export declare const MIDI_CATALOG: MidiTrack[];
@@ -26,7 +26,7 @@ export const FALLBACK_TRACK = {
26
26
  directUrl: 'https://bitmidi.com/uploads/16752.mid',
27
27
  };
28
28
  /** Default selected track. */
29
- export const DEFAULT_TRACK_ID = 28946; // Abracadabra — Lady Gaga
29
+ export const DEFAULT_TRACK_ID = 11747; // Bad Romance — Lady Gaga
30
30
  export const MIDI_CATALOG = [
31
31
  // Lady Gaga — verified from https://freemidi.org/artist-1586-lady-gaga
32
32
  { id: 28946, title: 'Abracadabra', artist: 'Lady Gaga', slug: 'abracadabra-lady-gaga' },
@@ -94,6 +94,11 @@ export const MIDI_CATALOG = [
94
94
  { id: 4151, title: 'We Belong Together', artist: 'Mariah Carey', slug: 'we-belong-together-mariah-carey' },
95
95
  { id: 12750, title: 'My Heart Will Go On', artist: 'Celine Dion', slug: 'my-heart-will-go-on-celine-dion' },
96
96
  { id: 2415, title: 'The Power of Love', artist: 'Celine Dion', slug: 'power-of-love-celine-dion' },
97
+ // Taylor Swift
98
+ { id: 13607, title: 'Shake It Off', artist: 'Taylor Swift', slug: 'shake-it-off-taylor-swift' },
99
+ { id: 11750, title: 'Love Story', artist: 'Taylor Swift', slug: 'love-story-taylor-swift' },
100
+ { id: 25205, title: 'Blank Space', artist: 'Taylor Swift', slug: 'blank-space-taylor-swift' },
101
+ { id: 12257, title: 'You Belong With Me', artist: 'Taylor Swift', slug: 'you-belong-with-me-taylor-swift' },
97
102
  // 2000s pop
98
103
  { id: 2257, title: '...Baby One More Time', artist: 'Britney Spears', slug: 'baby-one-more-time-britney-spears' },
99
104
  { id: 2256, title: 'Toxic', artist: 'Britney Spears', slug: 'toxic-britney-spears' },
@@ -1 +1 @@
1
- {"version":3,"file":"freemidi-catalog.js","sourceRoot":"","sources":["../../src/freemidi-catalog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * freemidi-catalog.ts — Curated list of tracks from freemidi.org\n *\n * Attribution: All tracks sourced from https://freemidi.org\n * MIDI files are fetched on-demand (no local copies stored).\n *\n * IDs verified by cross-referencing the freemidi.org artist pages and\n * Google site:freemidi.org searches. Entries with unverifiable IDs were\n * removed rather than kept with guessed values.\n *\n * Fallback track (open source / direct URL): Beethoven Moonlight Sonata via bitmidi.com\n */\n\nexport interface MidiTrack {\n id: number;\n title: string;\n artist: string;\n /** freemidi.org download page slug, used to get session cookie */\n slug: string;\n}\n\n/** Build the two-step freemidi.org fetch URLs for a track. */\nexport function freemidiUrls(track: MidiTrack): { downloadPage: string; getter: string } {\n return {\n downloadPage: `https://freemidi.org/download3-${track.id}-${track.slug}`,\n getter: `https://freemidi.org/getter-${track.id}`,\n };\n}\n\n/** Fallback track — direct URL, no auth required. */\nexport const FALLBACK_TRACK: MidiTrack & { directUrl: string } = {\n id: 0,\n title: 'Moonlight Sonata',\n artist: 'Beethoven',\n slug: '',\n directUrl: 'https://bitmidi.com/uploads/16752.mid',\n};\n\n/** Default selected track. */\nexport const DEFAULT_TRACK_ID = 28946; // Abracadabra — Lady Gaga\n\nexport const MIDI_CATALOG: MidiTrack[] = [\n // Lady Gaga — verified from https://freemidi.org/artist-1586-lady-gaga\n { id: 28946, title: 'Abracadabra', artist: 'Lady Gaga', slug: 'abracadabra-lady-gaga' },\n { id: 11747, title: 'Bad Romance', artist: 'Lady Gaga', slug: 'bad-romance-lady-gaga' },\n { id: 11544, title: 'Poker Face', artist: 'Lady Gaga', slug: 'poker-face-lady-gaga' },\n { id: 11543, title: 'Just Dance', artist: 'Lady Gaga', slug: 'just-dance-lady-gaga' },\n { id: 12112, title: 'Telephone', artist: 'Lady Gaga', slug: 'telephone-lady-gaga' },\n { id: 11748, title: 'Paparazzi', artist: 'Lady Gaga', slug: 'paparazzi--lady-gaga' },\n { id: 12582, title: 'Alejandro', artist: 'Lady Gaga', slug: 'alejandro-lady-gaga' },\n { id: 12583, title: 'Born This Way', artist: 'Lady Gaga', slug: 'born-this-way-lady-gaga' },\n { id: 12669, title: 'Edge of Glory', artist: 'Lady Gaga', slug: 'the-edge-of-glory-lady-gaga' },\n { id: 28817, title: 'Applause', artist: 'Lady Gaga', slug: 'applause-lady-gaga' },\n\n // Queen\n { id: 5772, title: 'Bohemian Rhapsody', artist: 'Queen', slug: 'bohemian-rhapsody-queen' },\n { id: 5786, title: \"Don't Stop Me Now\", artist: 'Queen', slug: 'dont-stop-me-now-queen' },\n { id: 5860, title: 'We Will Rock You', artist: 'Queen', slug: 'we-will-rock-you-queen' },\n { id: 5841, title: 'Radio Ga Ga', artist: 'Queen', slug: 'radio-gaga-queen' },\n\n // Michael Jackson\n { id: 5169, title: 'Billie Jean', artist: 'Michael Jackson', slug: 'billie-jean-michael-jackson' },\n { id: 5187, title: 'Thriller', artist: 'Michael Jackson', slug: 'thriller-michael-jackson' },\n { id: 5168, title: 'Beat It', artist: 'Michael Jackson', slug: 'beat-it-michael-jackson' },\n { id: 5181, title: 'Man in the Mirror', artist: 'Michael Jackson', slug: 'man-in-the-mirror-michael-jackson' },\n { id: 5171, title: 'Black or White', artist: 'Michael Jackson', slug: 'black-or-white-michael-jackson' },\n\n // Madonna\n { id: 4916, title: 'Like a Prayer', artist: 'Madonna', slug: 'like-a-prayer-madonna' },\n { id: 4897, title: 'Material Girl', artist: 'Madonna', slug: 'material-girl-madonna' },\n { id: 4903, title: 'Vogue', artist: 'Madonna', slug: 'vogue-madonna' },\n { id: 4899, title: \"Papa Don't Preach\", artist: 'Madonna', slug: 'papa-dont-preach-madonna' },\n\n // Guns N' Roses\n { id: 3634, title: \"Sweet Child O' Mine\", artist: \"Guns N' Roses\", slug: 'sweet-child-of-mine-guns-n-roses' },\n { id: 3621, title: 'November Rain', artist: \"Guns N' Roses\", slug: 'november-rain-guns-n-roses' },\n { id: 21484, title: 'Welcome to the Jungle', artist: \"Guns N' Roses\", slug: 'welcome-to-the-jungle-guns-n-roses' },\n\n // Nirvana\n { id: 26749, title: 'Smells Like Teen Spirit', artist: 'Nirvana', slug: 'smells-like-teen-spirit-nirvana' },\n { id: 5409, title: 'Come As You Are', artist: 'Nirvana', slug: 'come-as-you-are-nirvana' },\n { id: 5417, title: 'Heart-Shaped Box', artist: 'Nirvana', slug: 'heart-shaped-box-nirvana' },\n\n // The Beatles\n { id: 1202, title: 'Yesterday', artist: 'The Beatles', slug: 'yesterday-beatles' },\n { id: 25870, title: 'Let It Be', artist: 'The Beatles', slug: 'let-it-be-beatles' },\n { id: 1047, title: 'Hey Jude', artist: 'The Beatles', slug: 'hey-jude-beatles' },\n { id: 1014, title: 'Come Together', artist: 'The Beatles', slug: 'come-together-beatles' },\n { id: 12092, title: 'Blackbird', artist: 'The Beatles', slug: 'blackbird-beatles' },\n\n // Classic rock\n { id: 2896, title: 'Hotel California', artist: 'Eagles', slug: 'hotel-california-eagles' },\n { id: 2911, title: 'Take It Easy', artist: 'Eagles', slug: 'take-it-easy-eagles' },\n { id: 4445, title: 'Stairway to Heaven', artist: 'Led Zeppelin', slug: 'stairway-to-heaven-led-zeppelin' },\n { id: 4430, title: 'Whole Lotta Love', artist: 'Led Zeppelin', slug: 'whole-lotta-love-led-zeppelin' },\n { id: 4724, title: 'Purple Haze', artist: 'Jimi Hendrix', slug: 'purple-haze-jimi-hendrix' },\n { id: 1638, title: 'All Along the Watchtower', artist: 'Bob Dylan', slug: 'all-along-the-watchtower-bob-dylan' },\n { id: 9620, title: 'Johnny B. Goode', artist: 'Chuck Berry', slug: 'johnny-b-goode-chuck-berry' },\n\n // Elvis Presley\n { id: 2966, title: 'Blue Suede Shoes', artist: 'Elvis Presley', slug: 'blue-suede-shoes-elvis' },\n { id: 2971, title: 'Hound Dog', artist: 'Elvis Presley', slug: 'hound-dog-elvis' },\n { id: 10228, title: 'Love Me Tender', artist: 'Elvis Presley', slug: 'love-me-tender-elvis' },\n\n // Soul / R&B\n { id: 5012, title: \"What's Going On\", artist: 'Marvin Gaye', slug: 'whats-goin-on-marvin-gaye' },\n { id: 6659, title: 'Superstition', artist: 'Stevie Wonder', slug: 'superstitions-stevie-wonder' },\n { id: 6658, title: 'Sir Duke', artist: 'Stevie Wonder', slug: 'sir-duke-stevie-wonder' },\n { id: 6653, title: \"Isn't She Lovely\", artist: 'Stevie Wonder', slug: 'isnt-she-lovely-stevie-wonder' },\n\n // Pop divas\n { id: 3989, title: 'I Will Always Love You', artist: 'Whitney Houston', slug: 'i-will-always-love-you-whitney-houston' },\n { id: 3982, title: 'Greatest Love of All', artist: 'Whitney Houston', slug: 'greatest-love-of-all-whitney-houston' },\n { id: 4975, title: 'Hero', artist: 'Mariah Carey', slug: 'hero-mariah-carey' },\n { id: 27556, title: 'All I Want for Christmas', artist: 'Mariah Carey', slug: 'all-i-want-for-christmas-is-you-mariah-carey' },\n { id: 4151, title: 'We Belong Together', artist: 'Mariah Carey', slug: 'we-belong-together-mariah-carey' },\n { id: 12750, title: 'My Heart Will Go On', artist: 'Celine Dion', slug: 'my-heart-will-go-on-celine-dion' },\n { id: 2415, title: 'The Power of Love', artist: 'Celine Dion', slug: 'power-of-love-celine-dion' },\n\n // 2000s pop\n { id: 2257, title: '...Baby One More Time', artist: 'Britney Spears', slug: 'baby-one-more-time-britney-spears' },\n { id: 2256, title: 'Toxic', artist: 'Britney Spears', slug: 'toxic-britney-spears' },\n { id: 2265, title: \"Oops!... I Did It Again\", artist: 'Britney Spears', slug: 'oops-i-did-it-again-britney-spears' },\n { id: 1244, title: 'Crazy in Love', artist: 'Beyoncé', slug: 'crazy-in-love-beyonce' },\n { id: 11736, title: 'Single Ladies', artist: 'Beyoncé', slug: 'single-ladies-beyonce' },\n { id: 16275, title: 'Halo', artist: 'Beyoncé', slug: 'halo-beyonce' },\n { id: 11406, title: 'Umbrella', artist: 'Rihanna', slug: 'umbrella-rihanna' },\n { id: 15528, title: 'Diamonds', artist: 'Rihanna', slug: 'diamonds-rihanna' },\n { id: 12548, title: 'Rolling in the Deep', artist: 'Adele', slug: 'rolling-in-the-deep-adele' },\n { id: 12550, title: 'Someone Like You', artist: 'Adele', slug: 'someone-like-you-adele' },\n { id: 25272, title: 'Hello', artist: 'Adele', slug: 'hello-adele' },\n\n // Electronic / Dance\n { id: 12139, title: 'Around the World', artist: 'Daft Punk', slug: 'around-the-world-daft-punk' },\n { id: 12138, title: 'One More Time', artist: 'Daft Punk', slug: 'one-more-time-daft-punk' },\n { id: 14492, title: 'Get Lucky', artist: 'Daft Punk', slug: 'get-lucky-feat-pharrell-williams-daft-punk' },\n { id: 2925, title: 'Blue (Da Ba Dee)', artist: 'Eiffel 65', slug: 'blue-eiffel-65' },\n { id: 7791, title: 'Sandstorm', artist: 'Darude', slug: 'sandstorm-darude' },\n { id: 3640, title: 'What Is Love', artist: 'Haddaway', slug: 'what-is-love-haddaway' },\n { id: 6576, title: 'Rhythm Is a Dancer', artist: 'Snap!', slug: 'rhythm-is-a-dancer-snap' },\n\n // The Killers\n { id: 9105, title: 'Mr. Brightside', artist: 'The Killers', slug: 'mr-brightside-killers' },\n { id: 24620, title: 'Human', artist: 'The Killers', slug: 'human-killers' },\n\n // Classical\n { id: 26718, title: 'Für Elise', artist: 'Beethoven', slug: 'fur-elise-artists-bands' },\n\n // Video games\n { id: 8373, title: 'Super Mario Bros Theme', artist: 'Video Games', slug: 'super-mario-brothers-video-games' },\n { id: 8840, title: 'Tetris Theme (Korobeiniki)', artist: 'Video Games', slug: 'theme-a-tetris' },\n { id: 8687, title: 'Mega Man 2 — Dr. Wily', artist: 'Video Games', slug: 'wiley-stage-i-mega-man-ii' },\n];\n"]}
1
+ {"version":3,"file":"freemidi-catalog.js","sourceRoot":"","sources":["../../src/freemidi-catalog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * freemidi-catalog.ts — Curated list of tracks from freemidi.org\n *\n * Attribution: All tracks sourced from https://freemidi.org\n * MIDI files are fetched on-demand (no local copies stored).\n *\n * IDs verified by cross-referencing the freemidi.org artist pages and\n * Google site:freemidi.org searches. Entries with unverifiable IDs were\n * removed rather than kept with guessed values.\n *\n * Fallback track (open source / direct URL): Beethoven Moonlight Sonata via bitmidi.com\n */\n\nexport interface MidiTrack {\n id: number;\n title: string;\n artist: string;\n /** freemidi.org download page slug, used to get session cookie */\n slug: string;\n}\n\n/** Build the two-step freemidi.org fetch URLs for a track. */\nexport function freemidiUrls(track: MidiTrack): { downloadPage: string; getter: string } {\n return {\n downloadPage: `https://freemidi.org/download3-${track.id}-${track.slug}`,\n getter: `https://freemidi.org/getter-${track.id}`,\n };\n}\n\n/** Fallback track — direct URL, no auth required. */\nexport const FALLBACK_TRACK: MidiTrack & { directUrl: string } = {\n id: 0,\n title: 'Moonlight Sonata',\n artist: 'Beethoven',\n slug: '',\n directUrl: 'https://bitmidi.com/uploads/16752.mid',\n};\n\n/** Default selected track. */\nexport const DEFAULT_TRACK_ID = 11747; // Bad Romance — Lady Gaga\n\nexport const MIDI_CATALOG: MidiTrack[] = [\n // Lady Gaga — verified from https://freemidi.org/artist-1586-lady-gaga\n { id: 28946, title: 'Abracadabra', artist: 'Lady Gaga', slug: 'abracadabra-lady-gaga' },\n { id: 11747, title: 'Bad Romance', artist: 'Lady Gaga', slug: 'bad-romance-lady-gaga' },\n { id: 11544, title: 'Poker Face', artist: 'Lady Gaga', slug: 'poker-face-lady-gaga' },\n { id: 11543, title: 'Just Dance', artist: 'Lady Gaga', slug: 'just-dance-lady-gaga' },\n { id: 12112, title: 'Telephone', artist: 'Lady Gaga', slug: 'telephone-lady-gaga' },\n { id: 11748, title: 'Paparazzi', artist: 'Lady Gaga', slug: 'paparazzi--lady-gaga' },\n { id: 12582, title: 'Alejandro', artist: 'Lady Gaga', slug: 'alejandro-lady-gaga' },\n { id: 12583, title: 'Born This Way', artist: 'Lady Gaga', slug: 'born-this-way-lady-gaga' },\n { id: 12669, title: 'Edge of Glory', artist: 'Lady Gaga', slug: 'the-edge-of-glory-lady-gaga' },\n { id: 28817, title: 'Applause', artist: 'Lady Gaga', slug: 'applause-lady-gaga' },\n\n // Queen\n { id: 5772, title: 'Bohemian Rhapsody', artist: 'Queen', slug: 'bohemian-rhapsody-queen' },\n { id: 5786, title: \"Don't Stop Me Now\", artist: 'Queen', slug: 'dont-stop-me-now-queen' },\n { id: 5860, title: 'We Will Rock You', artist: 'Queen', slug: 'we-will-rock-you-queen' },\n { id: 5841, title: 'Radio Ga Ga', artist: 'Queen', slug: 'radio-gaga-queen' },\n\n // Michael Jackson\n { id: 5169, title: 'Billie Jean', artist: 'Michael Jackson', slug: 'billie-jean-michael-jackson' },\n { id: 5187, title: 'Thriller', artist: 'Michael Jackson', slug: 'thriller-michael-jackson' },\n { id: 5168, title: 'Beat It', artist: 'Michael Jackson', slug: 'beat-it-michael-jackson' },\n { id: 5181, title: 'Man in the Mirror', artist: 'Michael Jackson', slug: 'man-in-the-mirror-michael-jackson' },\n { id: 5171, title: 'Black or White', artist: 'Michael Jackson', slug: 'black-or-white-michael-jackson' },\n\n // Madonna\n { id: 4916, title: 'Like a Prayer', artist: 'Madonna', slug: 'like-a-prayer-madonna' },\n { id: 4897, title: 'Material Girl', artist: 'Madonna', slug: 'material-girl-madonna' },\n { id: 4903, title: 'Vogue', artist: 'Madonna', slug: 'vogue-madonna' },\n { id: 4899, title: \"Papa Don't Preach\", artist: 'Madonna', slug: 'papa-dont-preach-madonna' },\n\n // Guns N' Roses\n { id: 3634, title: \"Sweet Child O' Mine\", artist: \"Guns N' Roses\", slug: 'sweet-child-of-mine-guns-n-roses' },\n { id: 3621, title: 'November Rain', artist: \"Guns N' Roses\", slug: 'november-rain-guns-n-roses' },\n { id: 21484, title: 'Welcome to the Jungle', artist: \"Guns N' Roses\", slug: 'welcome-to-the-jungle-guns-n-roses' },\n\n // Nirvana\n { id: 26749, title: 'Smells Like Teen Spirit', artist: 'Nirvana', slug: 'smells-like-teen-spirit-nirvana' },\n { id: 5409, title: 'Come As You Are', artist: 'Nirvana', slug: 'come-as-you-are-nirvana' },\n { id: 5417, title: 'Heart-Shaped Box', artist: 'Nirvana', slug: 'heart-shaped-box-nirvana' },\n\n // The Beatles\n { id: 1202, title: 'Yesterday', artist: 'The Beatles', slug: 'yesterday-beatles' },\n { id: 25870, title: 'Let It Be', artist: 'The Beatles', slug: 'let-it-be-beatles' },\n { id: 1047, title: 'Hey Jude', artist: 'The Beatles', slug: 'hey-jude-beatles' },\n { id: 1014, title: 'Come Together', artist: 'The Beatles', slug: 'come-together-beatles' },\n { id: 12092, title: 'Blackbird', artist: 'The Beatles', slug: 'blackbird-beatles' },\n\n // Classic rock\n { id: 2896, title: 'Hotel California', artist: 'Eagles', slug: 'hotel-california-eagles' },\n { id: 2911, title: 'Take It Easy', artist: 'Eagles', slug: 'take-it-easy-eagles' },\n { id: 4445, title: 'Stairway to Heaven', artist: 'Led Zeppelin', slug: 'stairway-to-heaven-led-zeppelin' },\n { id: 4430, title: 'Whole Lotta Love', artist: 'Led Zeppelin', slug: 'whole-lotta-love-led-zeppelin' },\n { id: 4724, title: 'Purple Haze', artist: 'Jimi Hendrix', slug: 'purple-haze-jimi-hendrix' },\n { id: 1638, title: 'All Along the Watchtower', artist: 'Bob Dylan', slug: 'all-along-the-watchtower-bob-dylan' },\n { id: 9620, title: 'Johnny B. Goode', artist: 'Chuck Berry', slug: 'johnny-b-goode-chuck-berry' },\n\n // Elvis Presley\n { id: 2966, title: 'Blue Suede Shoes', artist: 'Elvis Presley', slug: 'blue-suede-shoes-elvis' },\n { id: 2971, title: 'Hound Dog', artist: 'Elvis Presley', slug: 'hound-dog-elvis' },\n { id: 10228, title: 'Love Me Tender', artist: 'Elvis Presley', slug: 'love-me-tender-elvis' },\n\n // Soul / R&B\n { id: 5012, title: \"What's Going On\", artist: 'Marvin Gaye', slug: 'whats-goin-on-marvin-gaye' },\n { id: 6659, title: 'Superstition', artist: 'Stevie Wonder', slug: 'superstitions-stevie-wonder' },\n { id: 6658, title: 'Sir Duke', artist: 'Stevie Wonder', slug: 'sir-duke-stevie-wonder' },\n { id: 6653, title: \"Isn't She Lovely\", artist: 'Stevie Wonder', slug: 'isnt-she-lovely-stevie-wonder' },\n\n // Pop divas\n { id: 3989, title: 'I Will Always Love You', artist: 'Whitney Houston', slug: 'i-will-always-love-you-whitney-houston' },\n { id: 3982, title: 'Greatest Love of All', artist: 'Whitney Houston', slug: 'greatest-love-of-all-whitney-houston' },\n { id: 4975, title: 'Hero', artist: 'Mariah Carey', slug: 'hero-mariah-carey' },\n { id: 27556, title: 'All I Want for Christmas', artist: 'Mariah Carey', slug: 'all-i-want-for-christmas-is-you-mariah-carey' },\n { id: 4151, title: 'We Belong Together', artist: 'Mariah Carey', slug: 'we-belong-together-mariah-carey' },\n { id: 12750, title: 'My Heart Will Go On', artist: 'Celine Dion', slug: 'my-heart-will-go-on-celine-dion' },\n { id: 2415, title: 'The Power of Love', artist: 'Celine Dion', slug: 'power-of-love-celine-dion' },\n\n // Taylor Swift\n { id: 13607, title: 'Shake It Off', artist: 'Taylor Swift', slug: 'shake-it-off-taylor-swift' },\n { id: 11750, title: 'Love Story', artist: 'Taylor Swift', slug: 'love-story-taylor-swift' },\n { id: 25205, title: 'Blank Space', artist: 'Taylor Swift', slug: 'blank-space-taylor-swift' },\n { id: 12257, title: 'You Belong With Me', artist: 'Taylor Swift', slug: 'you-belong-with-me-taylor-swift' },\n\n // 2000s pop\n { id: 2257, title: '...Baby One More Time', artist: 'Britney Spears', slug: 'baby-one-more-time-britney-spears' },\n { id: 2256, title: 'Toxic', artist: 'Britney Spears', slug: 'toxic-britney-spears' },\n { id: 2265, title: \"Oops!... I Did It Again\", artist: 'Britney Spears', slug: 'oops-i-did-it-again-britney-spears' },\n { id: 1244, title: 'Crazy in Love', artist: 'Beyoncé', slug: 'crazy-in-love-beyonce' },\n { id: 11736, title: 'Single Ladies', artist: 'Beyoncé', slug: 'single-ladies-beyonce' },\n { id: 16275, title: 'Halo', artist: 'Beyoncé', slug: 'halo-beyonce' },\n { id: 11406, title: 'Umbrella', artist: 'Rihanna', slug: 'umbrella-rihanna' },\n { id: 15528, title: 'Diamonds', artist: 'Rihanna', slug: 'diamonds-rihanna' },\n { id: 12548, title: 'Rolling in the Deep', artist: 'Adele', slug: 'rolling-in-the-deep-adele' },\n { id: 12550, title: 'Someone Like You', artist: 'Adele', slug: 'someone-like-you-adele' },\n { id: 25272, title: 'Hello', artist: 'Adele', slug: 'hello-adele' },\n\n // Electronic / Dance\n { id: 12139, title: 'Around the World', artist: 'Daft Punk', slug: 'around-the-world-daft-punk' },\n { id: 12138, title: 'One More Time', artist: 'Daft Punk', slug: 'one-more-time-daft-punk' },\n { id: 14492, title: 'Get Lucky', artist: 'Daft Punk', slug: 'get-lucky-feat-pharrell-williams-daft-punk' },\n { id: 2925, title: 'Blue (Da Ba Dee)', artist: 'Eiffel 65', slug: 'blue-eiffel-65' },\n { id: 7791, title: 'Sandstorm', artist: 'Darude', slug: 'sandstorm-darude' },\n { id: 3640, title: 'What Is Love', artist: 'Haddaway', slug: 'what-is-love-haddaway' },\n { id: 6576, title: 'Rhythm Is a Dancer', artist: 'Snap!', slug: 'rhythm-is-a-dancer-snap' },\n\n // The Killers\n { id: 9105, title: 'Mr. Brightside', artist: 'The Killers', slug: 'mr-brightside-killers' },\n { id: 24620, title: 'Human', artist: 'The Killers', slug: 'human-killers' },\n\n // Classical\n { id: 26718, title: 'Für Elise', artist: 'Beethoven', slug: 'fur-elise-artists-bands' },\n\n // Video games\n { id: 8373, title: 'Super Mario Bros Theme', artist: 'Video Games', slug: 'super-mario-brothers-video-games' },\n { id: 8840, title: 'Tetris Theme (Korobeiniki)', artist: 'Video Games', slug: 'theme-a-tetris' },\n { id: 8687, title: 'Mega Man 2 — Dr. Wily', artist: 'Video Games', slug: 'wiley-stage-i-mega-man-ii' },\n];\n"]}
@@ -10,8 +10,8 @@
10
10
  * await runSnakeGame({ music: false, colors: { accent: '#00ff00' } });
11
11
  */
12
12
  export { SnakeGame } from './SnakeGame.js';
13
- export type { SnakeColors } from './types.js';
14
- export { DEFAULT_COLORS } from './types.js';
13
+ export type { SnakeColors, SnakeKeybindings } from './types.js';
14
+ export { DEFAULT_COLORS, DEFAULT_KEYBINDINGS } from './types.js';
15
15
  export type { SelectedTrack, MusicConfig } from './MusicSettings.js';
16
16
  export type { MidiTrack } from './freemidi-catalog.js';
17
17
  export { MIDI_CATALOG, DEFAULT_TRACK_ID, FALLBACK_TRACK, freemidiUrls } from './freemidi-catalog.js';
package/dist/src/index.js CHANGED
@@ -11,7 +11,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
11
11
  * await runSnakeGame({ music: false, colors: { accent: '#00ff00' } });
12
12
  */
13
13
  export { SnakeGame } from './SnakeGame.js';
14
- export { DEFAULT_COLORS } from './types.js';
14
+ export { DEFAULT_COLORS, DEFAULT_KEYBINDINGS } from './types.js';
15
15
  export { MIDI_CATALOG, DEFAULT_TRACK_ID, FALLBACK_TRACK, freemidiUrls } from './freemidi-catalog.js';
16
16
  import { render } from 'ink';
17
17
  import { SnakeGame } from './SnakeGame.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErG,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAc3C;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,UAA+B,EAAE;IAC5D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,GAAG,GAAG,MAAM,CAChB,KAAC,SAAS,IACR,KAAK,EAAE,OAAO,CAAC,KAAK,EACpB,MAAM,EAAE,OAAO,CAAC,MAAM,EACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAC1B,YAAY,EAAE,OAAO,CAAC,YAAY,EAClC,KAAK,EAAE,OAAO,CAAC,KAAK,EACpB,MAAM,EAAE,OAAO,CAAC,MAAM,EACtB,MAAM,EAAE,OAAO,CAAC,MAAM,EACtB,MAAM,EAAE,GAAG,EAAE;gBACX,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,OAAO,EAAE,CAAC;YACZ,CAAC,GACD,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * snake-game — public API\n *\n * Embed the game in an existing Ink app:\n * import { SnakeGame } from 'snake-game';\n * <SnakeGame onExit={() => { ... }} colors={{ head: '#ff0000' }} music={false} />\n *\n * Or launch it imperatively from any CLI and await completion:\n * import { runSnakeGame } from 'snake-game';\n * await runSnakeGame({ music: false, colors: { accent: '#00ff00' } });\n */\n\nexport { SnakeGame } from './SnakeGame.js';\nexport type { SnakeColors } from './types.js';\nexport { DEFAULT_COLORS } from './types.js';\nexport type { SelectedTrack, MusicConfig } from './MusicSettings.js';\nexport type { MidiTrack } from './freemidi-catalog.js';\nexport { MIDI_CATALOG, DEFAULT_TRACK_ID, FALLBACK_TRACK, freemidiUrls } from './freemidi-catalog.js';\n\nimport { render } from 'ink';\nimport { SnakeGame } from './SnakeGame.js';\nimport type { SnakeColors } from './types.js';\nimport type { MidiTrack } from './freemidi-catalog.js';\n\ninterface RunSnakeGameOptions {\n music?: boolean;\n colors?: SnakeColors;\n cacheDir?: string;\n settingsFile?: string;\n width?: number;\n height?: number;\n tracks?: MidiTrack[];\n}\n\n/**\n * Launch Snake in the current terminal and resolve when the user exits.\n * Safe to call from any CLI — manages its own Ink render lifecycle.\n */\nexport function runSnakeGame(options: RunSnakeGameOptions = {}): Promise<void> {\n return new Promise((resolve) => {\n const app = render(\n <SnakeGame\n music={options.music}\n colors={options.colors}\n cacheDir={options.cacheDir}\n settingsFile={options.settingsFile}\n width={options.width}\n height={options.height}\n tracks={options.tracks}\n onExit={() => {\n app.unmount();\n resolve();\n }}\n />,\n );\n });\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGjE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErG,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAc3C;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,UAA+B,EAAE;IAC5D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,GAAG,GAAG,MAAM,CAChB,KAAC,SAAS,IACR,KAAK,EAAE,OAAO,CAAC,KAAK,EACpB,MAAM,EAAE,OAAO,CAAC,MAAM,EACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAC1B,YAAY,EAAE,OAAO,CAAC,YAAY,EAClC,KAAK,EAAE,OAAO,CAAC,KAAK,EACpB,MAAM,EAAE,OAAO,CAAC,MAAM,EACtB,MAAM,EAAE,OAAO,CAAC,MAAM,EACtB,MAAM,EAAE,GAAG,EAAE;gBACX,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,OAAO,EAAE,CAAC;YACZ,CAAC,GACD,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * snake-game — public API\n *\n * Embed the game in an existing Ink app:\n * import { SnakeGame } from 'snake-game';\n * <SnakeGame onExit={() => { ... }} colors={{ head: '#ff0000' }} music={false} />\n *\n * Or launch it imperatively from any CLI and await completion:\n * import { runSnakeGame } from 'snake-game';\n * await runSnakeGame({ music: false, colors: { accent: '#00ff00' } });\n */\n\nexport { SnakeGame } from './SnakeGame.js';\nexport type { SnakeColors, SnakeKeybindings } from './types.js';\nexport { DEFAULT_COLORS, DEFAULT_KEYBINDINGS } from './types.js';\nexport type { SelectedTrack, MusicConfig } from './MusicSettings.js';\nexport type { MidiTrack } from './freemidi-catalog.js';\nexport { MIDI_CATALOG, DEFAULT_TRACK_ID, FALLBACK_TRACK, freemidiUrls } from './freemidi-catalog.js';\n\nimport { render } from 'ink';\nimport { SnakeGame } from './SnakeGame.js';\nimport type { SnakeColors } from './types.js';\nimport type { MidiTrack } from './freemidi-catalog.js';\n\ninterface RunSnakeGameOptions {\n music?: boolean;\n colors?: SnakeColors;\n cacheDir?: string;\n settingsFile?: string;\n width?: number;\n height?: number;\n tracks?: MidiTrack[];\n}\n\n/**\n * Launch Snake in the current terminal and resolve when the user exits.\n * Safe to call from any CLI — manages its own Ink render lifecycle.\n */\nexport function runSnakeGame(options: RunSnakeGameOptions = {}): Promise<void> {\n return new Promise((resolve) => {\n const app = render(\n <SnakeGame\n music={options.music}\n colors={options.colors}\n cacheDir={options.cacheDir}\n settingsFile={options.settingsFile}\n width={options.width}\n height={options.height}\n tracks={options.tracks}\n onExit={() => {\n app.unmount();\n resolve();\n }}\n />,\n );\n });\n}\n"]}
@@ -1,3 +1,27 @@
1
+ export interface SnakeKeybindings {
2
+ /** Keys to move up (default: ['w', 'up']) */
3
+ up?: string[];
4
+ /** Keys to move down (default: ['s', 'down']) */
5
+ down?: string[];
6
+ /** Keys to move left (default: ['a', 'left']) */
7
+ left?: string[];
8
+ /** Keys to move right (default: ['d', 'right']) */
9
+ right?: string[];
10
+ /** Keys to pause (default: [' ']) */
11
+ pause?: string[];
12
+ /** Keys to restart (default: ['r']) */
13
+ restart?: string[];
14
+ /** Keys to open music settings (default: ['m']) */
15
+ music?: string[];
16
+ /** Keys to quit (default: ['q']) */
17
+ quit?: string[];
18
+ /** Keys to skip to next track (default: [']']) */
19
+ nextTrack?: string[];
20
+ /** Keys to skip to previous track (default: ['[']) */
21
+ prevTrack?: string[];
22
+ }
23
+ export declare const DEFAULT_KEYBINDINGS: Required<SnakeKeybindings>;
24
+ export declare function resolveKeybindings(kb?: SnakeKeybindings): Required<SnakeKeybindings>;
1
25
  export interface SnakeColors {
2
26
  /** UI accent color — title, score labels, controls (default: '#1e61f0') */
3
27
  accent?: string;
package/dist/src/types.js CHANGED
@@ -1,3 +1,31 @@
1
+ export const DEFAULT_KEYBINDINGS = {
2
+ up: ['w', 'up'],
3
+ down: ['s', 'down'],
4
+ left: ['a', 'left'],
5
+ right: ['d', 'right'],
6
+ pause: [' '],
7
+ restart: ['r'],
8
+ music: ['m'],
9
+ quit: ['q'],
10
+ nextTrack: [']'],
11
+ prevTrack: ['['],
12
+ };
13
+ export function resolveKeybindings(kb) {
14
+ if (!kb)
15
+ return DEFAULT_KEYBINDINGS;
16
+ return {
17
+ up: kb.up ?? DEFAULT_KEYBINDINGS.up,
18
+ down: kb.down ?? DEFAULT_KEYBINDINGS.down,
19
+ left: kb.left ?? DEFAULT_KEYBINDINGS.left,
20
+ right: kb.right ?? DEFAULT_KEYBINDINGS.right,
21
+ pause: kb.pause ?? DEFAULT_KEYBINDINGS.pause,
22
+ restart: kb.restart ?? DEFAULT_KEYBINDINGS.restart,
23
+ music: kb.music ?? DEFAULT_KEYBINDINGS.music,
24
+ quit: kb.quit ?? DEFAULT_KEYBINDINGS.quit,
25
+ nextTrack: kb.nextTrack ?? DEFAULT_KEYBINDINGS.nextTrack,
26
+ prevTrack: kb.prevTrack ?? DEFAULT_KEYBINDINGS.prevTrack,
27
+ };
28
+ }
1
29
  export const DEFAULT_COLORS = {
2
30
  accent: '#1e61f0',
3
31
  head: '#f7a8b8',
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,MAAM,EAAE,SAAS;IACjB,IAAI,EAAI,SAAS;IACjB,IAAI,EAAI,SAAS;IACjB,IAAI,EAAI,SAAS;IACjB,IAAI,EAAI,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAqC;CAChF,CAAC;AAEX,MAAM,UAAU,aAAa,CAAC,MAAoB;IAChD,OAAO;QACL,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,cAAc,CAAC,MAAM;QAC/C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;QAC7C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;QAC7C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;QAC7C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;KAC9C,CAAC;AACJ,CAAC","sourcesContent":["export interface SnakeColors {\n /** UI accent color — title, score labels, controls (default: '#1e61f0') */\n accent?: string;\n /** Snake head color (default: '#f7a8b8') */\n head?: string;\n /** Snake body color (default: '#ffffff') */\n body?: string;\n /** Food color (default: '#55cdfc') */\n food?: string;\n /** Beat visualizer colors for beats 1–4 (default: trans pride palette) */\n beat?: [string, string, string, string];\n}\n\nexport const DEFAULT_COLORS = {\n accent: '#1e61f0',\n head: '#f7a8b8',\n body: '#ffffff',\n food: '#55cdfc',\n beat: ['#55cdfc', '#ffffff', '#f7a8b8', '#ffffff'] as [string, string, string, string],\n} as const;\n\nexport function resolveColors(colors?: SnakeColors): Required<SnakeColors> {\n return {\n accent: colors?.accent ?? DEFAULT_COLORS.accent,\n head: colors?.head ?? DEFAULT_COLORS.head,\n body: colors?.body ?? DEFAULT_COLORS.body,\n food: colors?.food ?? DEFAULT_COLORS.food,\n beat: colors?.beat ?? DEFAULT_COLORS.beat,\n };\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAuBA,MAAM,CAAC,MAAM,mBAAmB,GAA+B;IAC7D,EAAE,EAAS,CAAC,GAAG,EAAE,IAAI,CAAC;IACtB,IAAI,EAAO,CAAC,GAAG,EAAE,MAAM,CAAC;IACxB,IAAI,EAAO,CAAC,GAAG,EAAE,MAAM,CAAC;IACxB,KAAK,EAAM,CAAC,GAAG,EAAE,OAAO,CAAC;IACzB,KAAK,EAAM,CAAC,GAAG,CAAC;IAChB,OAAO,EAAI,CAAC,GAAG,CAAC;IAChB,KAAK,EAAM,CAAC,GAAG,CAAC;IAChB,IAAI,EAAO,CAAC,GAAG,CAAC;IAChB,SAAS,EAAE,CAAC,GAAG,CAAC;IAChB,SAAS,EAAE,CAAC,GAAG,CAAC;CACjB,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,EAAqB;IACtD,IAAI,CAAC,EAAE;QAAE,OAAO,mBAAmB,CAAC;IACpC,OAAO;QACL,EAAE,EAAO,EAAE,CAAC,EAAE,IAAS,mBAAmB,CAAC,EAAE;QAC7C,IAAI,EAAK,EAAE,CAAC,IAAI,IAAO,mBAAmB,CAAC,IAAI;QAC/C,IAAI,EAAK,EAAE,CAAC,IAAI,IAAO,mBAAmB,CAAC,IAAI;QAC/C,KAAK,EAAI,EAAE,CAAC,KAAK,IAAM,mBAAmB,CAAC,KAAK;QAChD,KAAK,EAAI,EAAE,CAAC,KAAK,IAAM,mBAAmB,CAAC,KAAK;QAChD,OAAO,EAAE,EAAE,CAAC,OAAO,IAAI,mBAAmB,CAAC,OAAO;QAClD,KAAK,EAAM,EAAE,CAAC,KAAK,IAAQ,mBAAmB,CAAC,KAAK;QACpD,IAAI,EAAO,EAAE,CAAC,IAAI,IAAS,mBAAmB,CAAC,IAAI;QACnD,SAAS,EAAE,EAAE,CAAC,SAAS,IAAI,mBAAmB,CAAC,SAAS;QACxD,SAAS,EAAE,EAAE,CAAC,SAAS,IAAI,mBAAmB,CAAC,SAAS;KACzD,CAAC;AACJ,CAAC;AAeD,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,MAAM,EAAE,SAAS;IACjB,IAAI,EAAI,SAAS;IACjB,IAAI,EAAI,SAAS;IACjB,IAAI,EAAI,SAAS;IACjB,IAAI,EAAI,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAqC;CAChF,CAAC;AAEX,MAAM,UAAU,aAAa,CAAC,MAAoB;IAChD,OAAO;QACL,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,cAAc,CAAC,MAAM;QAC/C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;QAC7C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;QAC7C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;QAC7C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;KAC9C,CAAC;AACJ,CAAC","sourcesContent":["export interface SnakeKeybindings {\n /** Keys to move up (default: ['w', 'up']) */\n up?: string[];\n /** Keys to move down (default: ['s', 'down']) */\n down?: string[];\n /** Keys to move left (default: ['a', 'left']) */\n left?: string[];\n /** Keys to move right (default: ['d', 'right']) */\n right?: string[];\n /** Keys to pause (default: [' ']) */\n pause?: string[];\n /** Keys to restart (default: ['r']) */\n restart?: string[];\n /** Keys to open music settings (default: ['m']) */\n music?: string[];\n /** Keys to quit (default: ['q']) */\n quit?: string[];\n /** Keys to skip to next track (default: [']']) */\n nextTrack?: string[];\n /** Keys to skip to previous track (default: ['[']) */\n prevTrack?: string[];\n}\n\nexport const DEFAULT_KEYBINDINGS: Required<SnakeKeybindings> = {\n up: ['w', 'up'],\n down: ['s', 'down'],\n left: ['a', 'left'],\n right: ['d', 'right'],\n pause: [' '],\n restart: ['r'],\n music: ['m'],\n quit: ['q'],\n nextTrack: [']'],\n prevTrack: ['['],\n};\n\nexport function resolveKeybindings(kb?: SnakeKeybindings): Required<SnakeKeybindings> {\n if (!kb) return DEFAULT_KEYBINDINGS;\n return {\n up: kb.up ?? DEFAULT_KEYBINDINGS.up,\n down: kb.down ?? DEFAULT_KEYBINDINGS.down,\n left: kb.left ?? DEFAULT_KEYBINDINGS.left,\n right: kb.right ?? DEFAULT_KEYBINDINGS.right,\n pause: kb.pause ?? DEFAULT_KEYBINDINGS.pause,\n restart: kb.restart ?? DEFAULT_KEYBINDINGS.restart,\n music: kb.music ?? DEFAULT_KEYBINDINGS.music,\n quit: kb.quit ?? DEFAULT_KEYBINDINGS.quit,\n nextTrack: kb.nextTrack ?? DEFAULT_KEYBINDINGS.nextTrack,\n prevTrack: kb.prevTrack ?? DEFAULT_KEYBINDINGS.prevTrack,\n };\n}\n\nexport interface SnakeColors {\n /** UI accent color — title, score labels, controls (default: '#1e61f0') */\n accent?: string;\n /** Snake head color (default: '#f7a8b8') */\n head?: string;\n /** Snake body color (default: '#ffffff') */\n body?: string;\n /** Food color (default: '#55cdfc') */\n food?: string;\n /** Beat visualizer colors for beats 1–4 (default: trans pride palette) */\n beat?: [string, string, string, string];\n}\n\nexport const DEFAULT_COLORS = {\n accent: '#1e61f0',\n head: '#f7a8b8',\n body: '#ffffff',\n food: '#55cdfc',\n beat: ['#55cdfc', '#ffffff', '#f7a8b8', '#ffffff'] as [string, string, string, string],\n} as const;\n\nexport function resolveColors(colors?: SnakeColors): Required<SnakeColors> {\n return {\n accent: colors?.accent ?? DEFAULT_COLORS.accent,\n head: colors?.head ?? DEFAULT_COLORS.head,\n body: colors?.body ?? DEFAULT_COLORS.body,\n food: colors?.food ?? DEFAULT_COLORS.food,\n beat: colors?.beat ?? DEFAULT_COLORS.beat,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pavus/snake-game",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Playable Snake in the terminal with MIDI music",
5
5
  "type": "module",
6
6
  "bin": {