diff --git a/README.md b/README.md index 0c7b69d..c1d2ec3 100644 --- a/README.md +++ b/README.md @@ -1 +1,308 @@ -# Nebula-Core \ No newline at end of file +# Nebula Core + +Nebula Core is a controller-first, SteamOS-friendly foundation for building living-room and handheld applications. +It focuses on predictable navigation, input abstraction, and UI patterns that work without a mouse or keyboard. + +This repository is a monorepo of small, independent packages. Each package solves one problem well and stays UI-agnostic where possible. + +## Goals + +- Controller and gamepad first +- Steam Deck and SteamOS as a primary target +- Large, readable UI defaults +- Minimal dependencies and no framework lock-in +- ES Modules, Node 18+ + +## Packages + +- @nebula/core-utils — shared helpers and small utilities +- @nebula/core-input — unified, action-based input abstraction +- @nebula/core-navigation — focus management and spatial navigation primitives +- @nebula/core-theme — tokens for color, spacing, typography, and motion +- @nebula/core-glyphs — controller glyph mappings and lookup helpers +- @nebula/core-ui — optional, minimal UI primitives for controller-first apps + +## Installation + +Install only the packages you need: + +```bash +npm install @nebula/core-input @nebula/core-navigation @nebula/core-ui @nebula/core-theme @nebula/core-glyphs @nebula/core-utils +``` + +## Quick start + +```js +import { createActionMapper } from "@nebula/core-input"; +import { pickBestCandidate } from "@nebula/core-navigation"; +import { getFocusableAttributes, focusRing, hitTarget } from "@nebula/core-ui"; +import { createTheme } from "@nebula/core-theme"; +import { getGlyph } from "@nebula/core-glyphs"; + +const theme = createTheme({ colors: { accent: "#44d3ff" } }); +const glyph = getGlyph("steam-deck", "confirm"); + +const mapper = createActionMapper({ + bindings: { + confirm: [{ source: "gamepad", control: "a" }], + back: [{ source: "gamepad", control: "b" }] + } +}); + +mapper.onAction((update) => { + if (update.action === "confirm" && update.active) { + console.log("Confirm pressed", glyph, theme.colors.accent); + } +}); + +const current = { id: "settings", x: 100, y: 100, width: 180, height: 80 }; +const candidates = [ + { id: "play", x: 100, y: 10, width: 180, height: 80 }, + { id: "help", x: 320, y: 100, width: 180, height: 80 } +]; + +const next = pickBestCandidate(current, candidates, "up"); + +const focusableProps = getFocusableAttributes({ role: "button", focusKey: "play" }); +const styles = { + minHeight: hitTarget.minHeight, + minWidth: hitTarget.minWidth, + outline: `${focusRing.outlineWidth}px solid ${focusRing.outlineColor}`, + outlineOffset: focusRing.outlineOffset +}; +``` + +## Architecture overview + +Nebula Core is designed as small, composable packages: + +1. **Input**: Normalize device events into actions. +2. **Navigation**: Choose the next focus target with spatial heuristics. +3. **UI**: Provide hit-target and focus-ring defaults for controller use. +4. **Theme**: Provide tokens tuned for couch and handheld readability. +5. **Glyphs**: Map logical actions to controller glyphs and assets. +6. **Utils**: Small reusable helpers used by other packages. + +## Package documentation + +### @nebula/core-utils + +Small, dependency-free helpers. No DOM or platform assumptions. + +#### API + +##### clamp(value, min, max) +Clamp a number between a minimum and maximum. + +##### lerp(from, to, t) +Linear interpolation between two numbers. + +##### roundTo(value, decimals) +Round a number to a specific decimal precision. + +##### createEmitter() +Create a tiny event emitter with `on`, `emit`, and `clear`. + +#### Example + +```js +import { clamp, createEmitter } from "@nebula/core-utils"; + +const value = clamp(12, 0, 10); +const events = createEmitter(); +const off = events.on((payload) => console.log(payload)); +events.emit({ action: "confirm" }); +off(); +``` + +### @nebula/core-input + +Action-based input abstraction for controller-first apps. Convert raw device events into app-level actions. + +#### Key concepts + +- **InputEvent**: Normalized input from any device (`gamepad`, `keyboard`, `mouse`, `touch`). +- **InputBinding**: Maps a physical control to an action name. +- **ActionUpdate**: Emitted update with `action`, `value`, and `active` state. + +#### API + +##### createActionMapper(options) +Creates an action mapper instance. + +**Options** +- `bindings`: `Record` +- `deadzone?`: `number` (default: `0.2`) + +**Returns** +- `mapEvent(event)`: map a single `InputEvent` to action updates. +- `getActionState(action)`: get current `value` and `active`. +- `onAction(listener)`: subscribe to action updates. +- `reset()`: clear all action states. + +#### Example + +```js +import { createActionMapper } from "@nebula/core-input"; + +const mapper = createActionMapper({ + bindings: { + confirm: [{ source: "gamepad", control: "a" }], + back: [{ source: "gamepad", control: "b" }] + } +}); + +mapper.onAction((update) => { + if (update.action === "confirm" && update.active) { + console.log("Confirm action"); + } +}); + +mapper.mapEvent({ + source: "gamepad", + control: "a", + type: "pressed", + value: 1 +}); +``` + +#### InputEvent shape + +```ts +type InputEvent = { + source: "gamepad" | "keyboard" | "mouse" | "touch" | "unknown"; + control: string; + type: "pressed" | "released" | "moved" | "axis"; + value: number; // 0..1 for digital, -1..1 for analog + timestamp?: number; +}; +``` + +### @nebula/core-navigation + +Focus management and spatial navigation primitives for controller-first UIs. + +#### API + +##### getRectCenter(rect) +Return the center point of a rectangle. + +##### getDirectionalCandidates(current, candidates, direction) +Filter candidates in the requested direction (`up`, `down`, `left`, `right`). + +##### pickBestCandidate(current, candidates, direction) +Pick the nearest candidate using a weighted distance score. + +#### Example + +```js +import { pickBestCandidate } from "@nebula/core-navigation"; + +const current = { id: "settings", x: 100, y: 100, width: 180, height: 80 }; +const candidates = [ + { id: "play", x: 100, y: 10, width: 180, height: 80 }, + { id: "help", x: 320, y: 100, width: 180, height: 80 } +]; + +const next = pickBestCandidate(current, candidates, "up"); +``` + +### @nebula/core-theme + +Theme tokens tuned for readable, couch-friendly UI. Exports plain objects and helpers only. + +#### API + +##### baseTheme +Default theme tokens: `colors`, `spacing`, `radius`, `typography`, `motion`. + +##### createTheme(overrides) +Merge overrides with the base theme. Returns a full theme object. + +#### Example + +```js +import { baseTheme, createTheme } from "@nebula/core-theme"; + +const theme = createTheme({ colors: { accent: "#44d3ff" } }); +console.log(baseTheme.typography.display, theme.colors.accent); +``` + +### @nebula/core-glyphs + +Controller glyph mappings and lookup helpers. Data-only, no rendering logic. + +#### API + +##### glyphMap +Mapping of logical actions to glyph labels per controller. + +##### glyphAssetMap +Mapping of logical actions to asset paths per controller. + +##### getGlyph(controller, action) +Return the glyph label for a controller and action. + +##### getGlyphAssetPath(controller, action) +Return the asset path for a controller and action. + +#### Example + +```js +import { getGlyph, getGlyphAssetPath } from "@nebula/core-glyphs"; + +const glyph = getGlyph("steam-deck", "confirm"); +const asset = getGlyphAssetPath("steam-deck", "confirm"); +``` + +### @nebula/core-ui + +Minimal UI helpers for controller-first applications. No framework lock-in. + +#### API + +##### hitTarget +Recommended hit target sizes (pixels at 100% scale). + +##### focusRing +Focus ring style tokens for UI libraries or CSS-in-JS. + +##### getFocusableAttributes(options) +Build a minimal set of focusable attributes for controller navigation. + +#### Example + +```js +import { focusRing, hitTarget, getFocusableAttributes } from "@nebula/core-ui"; + +const props = getFocusableAttributes({ role: "button", focusKey: "play" }); +const styles = { + minHeight: hitTarget.minHeight, + minWidth: hitTarget.minWidth, + outline: `${focusRing.outlineWidth}px solid ${focusRing.outlineColor}`, + outlineOffset: focusRing.outlineOffset +}; +``` + +## SteamOS / Steam Deck notes + +- Favor large hit targets (48px+ at 100% scale) and strong focus rings. +- Keep text contrast high and use `title`/`display` sizes for primary UI. +- Map actions to glyphs rather than hard-coded button labels. +- Provide a thin input adapter that converts raw device events into `InputEvent` objects. + +## Development + +- Node 18+ +- JavaScript only (ES Modules) +- JSDoc for all public APIs + +Run tests from the repo root: + +``` +npm test +``` + +## License + +MIT \ No newline at end of file diff --git a/documentation/core-glyphs.md b/documentation/core-glyphs.md new file mode 100644 index 0000000..eb37f5e --- /dev/null +++ b/documentation/core-glyphs.md @@ -0,0 +1,22 @@ +# @nebula/core-glyphs + +Controller glyph mappings and lookup helpers. Data-only, no rendering logic. + +## What it does + +- Maps logical actions to glyph labels per controller type. +- Provides asset path lookups for common glyphs. +- Keeps glyph decisions centralized and consistent. + +## Included data and helpers + +- `glyphMap` — Action-to-label mappings per controller type. +- `glyphAssetMap` — Action-to-asset-path mappings per controller type. +- `getGlyph(controller, action)` — Resolve a label. +- `getGlyphAssetPath(controller, action)` — Resolve an asset path. + +## Typical use cases + +- Displaying correct prompts for Steam Deck, Xbox, PlayStation, or Switch. +- Using consistent glyph labels in UI and localization. +- Swapping controller visuals based on detected device. diff --git a/documentation/core-input.md b/documentation/core-input.md new file mode 100644 index 0000000..6e8580d --- /dev/null +++ b/documentation/core-input.md @@ -0,0 +1,21 @@ +# @nebula/core-input + +Action-based input abstraction for controller-first apps. Converts raw device events into app-level actions. + +## What it does + +- Normalizes device input into a consistent `InputEvent` shape. +- Maps physical controls to logical actions (e.g. confirm, back). +- Emits action updates with `value` and `active` state. + +## Key concepts + +- **InputEvent**: Normalized event with `source`, `control`, `type`, and `value`. +- **InputBinding**: Maps a physical control to an action name. +- **ActionUpdate**: The emitted result when an input matches a binding. + +## Typical use cases + +- Gamepad-first UIs that should work across Deck, Xbox, and PlayStation layouts. +- Unified handling of keyboard and controller inputs without coupling to hardware. +- Action-driven UI logic (confirm/back/menu) instead of button-driven logic. diff --git a/documentation/core-navigation.md b/documentation/core-navigation.md new file mode 100644 index 0000000..a8778a6 --- /dev/null +++ b/documentation/core-navigation.md @@ -0,0 +1,20 @@ +# @nebula/core-navigation + +Focus management and spatial navigation primitives for controller-first UIs. + +## What it does + +- Calculates spatial relationships between focusable rectangles. +- Filters candidates by direction (up/down/left/right). +- Picks the best next focus target using a weighted distance heuristic. + +## Key concepts + +- **Rect**: A focusable element described by `x`, `y`, `width`, `height`, and `id`. +- **Direction**: The requested movement direction (up/down/left/right). + +## Typical use cases + +- D-pad or stick navigation across grids and lists. +- Consistent focus movement in TV or handheld UIs. +- Simple focus logic without a framework dependency. diff --git a/documentation/core-theme.md b/documentation/core-theme.md new file mode 100644 index 0000000..774464a --- /dev/null +++ b/documentation/core-theme.md @@ -0,0 +1,23 @@ +# @nebula/core-theme + +Theme tokens tuned for readable, couch-friendly UI. Exports plain objects and helpers only. + +## What it does + +- Provides a default token set for colors, spacing, typography, radius, and motion. +- Lets you merge overrides with `createTheme()` for custom styling. +- Keeps everything as plain objects for easy use in any UI layer. + +## Token groups + +- **colors**: Background, surface, text, accent, focus, danger. +- **spacing**: XS through XXL spacing scale. +- **radius**: Corner radius presets. +- **typography**: Caption, body, title, display sizes. +- **motion**: Fast/base/slow durations. + +## Typical use cases + +- Consistent visual system across packages and apps. +- Theme customization without framework coupling. +- Readable defaults for TV and handheld screens. diff --git a/documentation/core-ui.md b/documentation/core-ui.md new file mode 100644 index 0000000..f72bde8 --- /dev/null +++ b/documentation/core-ui.md @@ -0,0 +1,21 @@ +# @nebula/core-ui + +Minimal UI helpers for controller-first applications. No framework lock-in. + +## What it does + +- Defines recommended hit target sizes for controller navigation. +- Provides focus ring tokens for visible focus styling. +- Builds minimal focusable attributes for DOM-based UIs. + +## Included helpers + +- `hitTarget` — Minimum dimensions for interactive elements. +- `focusRing` — Outline width/color/offset for focus indication. +- `getFocusableAttributes(options)` — Returns `role`, `tabIndex`, and `data-` attributes. + +## Typical use cases + +- Ensuring touch and controller targets are large enough. +- Consistent focus styling in custom UI frameworks. +- Simple DOM attribute helpers for focus management. diff --git a/documentation/core-utils.md b/documentation/core-utils.md new file mode 100644 index 0000000..0f8a04e --- /dev/null +++ b/documentation/core-utils.md @@ -0,0 +1,22 @@ +# @nebula/core-utils + +Small, dependency-free utilities shared across Nebula Core packages. No DOM or platform assumptions. + +## What it does + +- Provides tiny, reusable math helpers. +- Exposes a minimal event emitter for small, decoupled messaging. +- Stays platform-agnostic so it works in browser, Electron, or Node. + +## Included utilities + +- `clamp(value, min, max)` — Clamp a number between bounds. +- `lerp(from, to, t)` — Linear interpolation between two numbers. +- `roundTo(value, decimals)` — Round to a fixed decimal precision. +- `createEmitter()` — Lightweight emitter with `on`, `emit`, and `clear`. + +## Typical use cases + +- Input or navigation modules needing small math helpers. +- App-level messaging without pulling in larger event libraries. +- Shared helpers across packages in the monorepo. diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..d67f691 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "checkJs": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "target": "ES2022", + "allowJs": true, + "baseUrl": ".", + "paths": { + "@nebula/*": ["packages/*/src/index.js"] + }, + "resolveJsonModule": true, + "strict": true, + "noEmit": true + }, + "exclude": [ + "node_modules", + "**/node_modules" + ] +} diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json new file mode 100644 index 0000000..a887c2d --- /dev/null +++ b/node_modules/.package-lock.json @@ -0,0 +1,85 @@ +{ + "name": "nebula-core", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/@nebulaproject/core": { + "resolved": "packages/core", + "link": true + }, + "node_modules/@nebulaproject/core-glyphs": { + "resolved": "packages/core-glyphs", + "link": true + }, + "node_modules/@nebulaproject/core-input": { + "resolved": "packages/core-input", + "link": true + }, + "node_modules/@nebulaproject/core-navigation": { + "resolved": "packages/core-navigation", + "link": true + }, + "node_modules/@nebulaproject/core-theme": { + "resolved": "packages/core-theme", + "link": true + }, + "node_modules/@nebulaproject/core-ui": { + "resolved": "packages/core-ui", + "link": true + }, + "node_modules/@nebulaproject/core-utils": { + "resolved": "packages/core-utils", + "link": true + }, + "packages/core": { + "name": "@nebulaproject/core", + "version": "0.1.3", + "license": "MIT" + }, + "packages/core-glyphs": { + "name": "@nebulaproject/core-glyphs", + "version": "0.1.0", + "engines": { + "node": ">=18" + } + }, + "packages/core-input": { + "name": "@nebulaproject/core-input", + "version": "0.1.0", + "dependencies": { + "@nebulaproject/core-utils": "0.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "packages/core-navigation": { + "name": "@nebulaproject/core-navigation", + "version": "0.1.0", + "engines": { + "node": ">=18" + } + }, + "packages/core-theme": { + "name": "@nebulaproject/core-theme", + "version": "0.1.0", + "engines": { + "node": ">=18" + } + }, + "packages/core-ui": { + "name": "@nebulaproject/core-ui", + "version": "0.1.0", + "engines": { + "node": ">=18" + } + }, + "packages/core-utils": { + "name": "@nebulaproject/core-utils", + "version": "0.1.0", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/node_modules/@nebulaproject/core-glyphs/README.md b/node_modules/@nebulaproject/core-glyphs/README.md new file mode 100644 index 0000000..1f1a18f --- /dev/null +++ b/node_modules/@nebulaproject/core-glyphs/README.md @@ -0,0 +1,21 @@ +# @nebula/core-glyphs + +Controller glyph mappings and lookup helpers. Data-only, no rendering logic. + +## Why it exists +Controller prompts should be consistent across Steam Deck, Xbox, and PlayStation. This package keeps glyph mapping in one place. + +## Usage + +```js +import { getGlyph, getGlyphAssetPath } from "@nebula/core-glyphs"; + +const glyph = getGlyph("steam-deck", "confirm"); +console.log(glyph); // "A" + +const asset = getGlyphAssetPath("steam-deck", "confirm"); +console.log(asset); // "assets/Steam Deck/SteamDeck_A.png" +``` + +## SteamOS / Steam Deck notes +Provide Deck-specific prompts by selecting `steam-deck` when the device is detected. Use the asset path helper or bind your own icon assets keyed by the glyph labels. diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Circle.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Circle.png new file mode 100644 index 0000000..05a89a9 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Circle.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Cross.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Cross.png new file mode 100644 index 0000000..395a898 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Cross.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Diagram.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Diagram.png new file mode 100644 index 0000000..d452d34 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Diagram.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Diagram_Simple.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Diagram_Simple.png new file mode 100644 index 0000000..99d85c9 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Diagram_Simple.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad.png new file mode 100644 index 0000000..49e6405 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad_Down.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad_Down.png new file mode 100644 index 0000000..a8f893a Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad_Down.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad_Left.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad_Left.png new file mode 100644 index 0000000..2bdc048 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad_Left.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad_Right.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad_Right.png new file mode 100644 index 0000000..b7cd568 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad_Right.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad_Up.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad_Up.png new file mode 100644 index 0000000..99180d2 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Dpad_Up.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_L1.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_L1.png new file mode 100644 index 0000000..07e505a Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_L1.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_L2.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_L2.png new file mode 100644 index 0000000..05f3dd2 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_L2.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Left_Stick.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Left_Stick.png new file mode 100644 index 0000000..0245ff8 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Left_Stick.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Left_Stick_Click.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Left_Stick_Click.png new file mode 100644 index 0000000..66e5271 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Left_Stick_Click.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Microphone.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Microphone.png new file mode 100644 index 0000000..bb0f331 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Microphone.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Options.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Options.png new file mode 100644 index 0000000..3e56fe2 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Options.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Options_Alt.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Options_Alt.png new file mode 100644 index 0000000..ca28364 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Options_Alt.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_R1.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_R1.png new file mode 100644 index 0000000..2cff97a Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_R1.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_R2.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_R2.png new file mode 100644 index 0000000..a13f17f Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_R2.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Right_Stick.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Right_Stick.png new file mode 100644 index 0000000..85c1556 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Right_Stick.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Right_Stick_Click.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Right_Stick_Click.png new file mode 100644 index 0000000..eecd4e8 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Right_Stick_Click.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Share.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Share.png new file mode 100644 index 0000000..d96e698 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Share.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Share_Alt.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Share_Alt.png new file mode 100644 index 0000000..10941aa Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Share_Alt.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Square.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Square.png new file mode 100644 index 0000000..20f6065 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Square.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Touch_Pad.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Touch_Pad.png new file mode 100644 index 0000000..1a77d0c Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Touch_Pad.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Triangle.png b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Triangle.png new file mode 100644 index 0000000..4950d17 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/PS5_Triangle.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/PS5/Thumbs.db b/node_modules/@nebulaproject/core-glyphs/assets/PS5/Thumbs.db new file mode 100644 index 0000000..e54da85 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/PS5/Thumbs.db differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_A.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_A.png new file mode 100644 index 0000000..36e4867 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_A.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_B.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_B.png new file mode 100644 index 0000000..aa94303 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_B.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dots.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dots.png new file mode 100644 index 0000000..fb04073 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dots.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad.png new file mode 100644 index 0000000..e79effa Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Down.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Down.png new file mode 100644 index 0000000..f6976e4 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Down.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Left.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Left.png new file mode 100644 index 0000000..80b5647 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Left.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Right.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Right.png new file mode 100644 index 0000000..37953ea Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Right.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Up.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Up.png new file mode 100644 index 0000000..6fa4a6b Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Up.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Gyro.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Gyro.png new file mode 100644 index 0000000..4db3d2e Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Gyro.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Inventory.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Inventory.png new file mode 100644 index 0000000..c6e297b Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Inventory.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_L1.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_L1.png new file mode 100644 index 0000000..25e2562 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_L1.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_L2.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_L2.png new file mode 100644 index 0000000..399ffb4 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_L2.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_L4.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_L4.png new file mode 100644 index 0000000..3a2b4be Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_L4.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_L5.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_L5.png new file mode 100644 index 0000000..507fc01 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_L5.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Left_Stick.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Left_Stick.png new file mode 100644 index 0000000..e006d0a Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Left_Stick.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Left_Stick_Click.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Left_Stick_Click.png new file mode 100644 index 0000000..d7e2029 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Left_Stick_Click.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Left_Track.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Left_Track.png new file mode 100644 index 0000000..d1596fa Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Left_Track.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Menu.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Menu.png new file mode 100644 index 0000000..39c5d17 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Menu.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Minus.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Minus.png new file mode 100644 index 0000000..1347872 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Minus.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Plus.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Plus.png new file mode 100644 index 0000000..394d5bb Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Plus.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Power.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Power.png new file mode 100644 index 0000000..13bfec5 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Power.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_R1.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_R1.png new file mode 100644 index 0000000..4d7ce77 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_R1.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_R2.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_R2.png new file mode 100644 index 0000000..bb4787a Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_R2.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_R4.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_R4.png new file mode 100644 index 0000000..07f6110 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_R4.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_R5.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_R5.png new file mode 100644 index 0000000..d5659cb Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_R5.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Right_Stick.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Right_Stick.png new file mode 100644 index 0000000..a2fc9db Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Right_Stick.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Right_Stick_Click.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Right_Stick_Click.png new file mode 100644 index 0000000..2236e57 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Right_Stick_Click.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Right_Track.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Right_Track.png new file mode 100644 index 0000000..0628aba Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Right_Track.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Square.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Square.png new file mode 100644 index 0000000..d87ae38 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Square.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Steam.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Steam.png new file mode 100644 index 0000000..af63f7f Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Steam.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_X.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_X.png new file mode 100644 index 0000000..606739b Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_X.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Y.png b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Y.png new file mode 100644 index 0000000..4a0423f Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/SteamDeck_Y.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/Thumbs.db b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/Thumbs.db new file mode 100644 index 0000000..034e75a Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Steam Deck/Thumbs.db differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_A.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_A.png new file mode 100644 index 0000000..df756ef Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_A.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_B.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_B.png new file mode 100644 index 0000000..ea7e743 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_B.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Controller_Left.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Controller_Left.png new file mode 100644 index 0000000..f90a244 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Controller_Left.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Controller_Right.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Controller_Right.png new file mode 100644 index 0000000..873da7e Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Controller_Right.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Controllers.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Controllers.png new file mode 100644 index 0000000..b6ee54d Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Controllers.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Controllers_Separate.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Controllers_Separate.png new file mode 100644 index 0000000..0c019da Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Controllers_Separate.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Down.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Down.png new file mode 100644 index 0000000..7b7b2b2 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Down.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad.png new file mode 100644 index 0000000..12f01eb Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad_Down.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad_Down.png new file mode 100644 index 0000000..37f6d5b Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad_Down.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad_Left.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad_Left.png new file mode 100644 index 0000000..8efd7a4 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad_Left.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad_Right.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad_Right.png new file mode 100644 index 0000000..8b5411d Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad_Right.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad_Up.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad_Up.png new file mode 100644 index 0000000..700a8ba Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Dpad_Up.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Home.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Home.png new file mode 100644 index 0000000..9b6733c Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Home.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_LB.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_LB.png new file mode 100644 index 0000000..ddfa3b9 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_LB.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_LT.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_LT.png new file mode 100644 index 0000000..6942e1f Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_LT.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Left.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Left.png new file mode 100644 index 0000000..fd58439 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Left.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Left_Stick.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Left_Stick.png new file mode 100644 index 0000000..d861ca5 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Left_Stick.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Left_Stick_Click.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Left_Stick_Click.png new file mode 100644 index 0000000..66e5271 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Left_Stick_Click.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Minus.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Minus.png new file mode 100644 index 0000000..d32608e Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Minus.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Plus.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Plus.png new file mode 100644 index 0000000..f1b0dc4 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Plus.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_RB.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_RB.png new file mode 100644 index 0000000..01f137a Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_RB.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_RT.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_RT.png new file mode 100644 index 0000000..6aef3cb Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_RT.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Right.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Right.png new file mode 100644 index 0000000..f524c6c Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Right.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Right_Stick.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Right_Stick.png new file mode 100644 index 0000000..f2c605b Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Right_Stick.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Right_Stick_Click.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Right_Stick_Click.png new file mode 100644 index 0000000..eecd4e8 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Right_Stick_Click.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Square.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Square.png new file mode 100644 index 0000000..d0fd432 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Square.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Up.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Up.png new file mode 100644 index 0000000..352f890 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Up.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_X.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_X.png new file mode 100644 index 0000000..bdf86ba Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_X.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Y.png b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Y.png new file mode 100644 index 0000000..46ac216 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Switch_Y.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Switch/Thumbs.db b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Thumbs.db new file mode 100644 index 0000000..84336d5 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Switch/Thumbs.db differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/Thumbs.db b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/Thumbs.db new file mode 100644 index 0000000..fee514b Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/Thumbs.db differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_A.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_A.png new file mode 100644 index 0000000..e22bb29 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_A.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_B.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_B.png new file mode 100644 index 0000000..9312c26 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_B.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Diagram.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Diagram.png new file mode 100644 index 0000000..ff29b45 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Diagram.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Diagram_Simple.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Diagram_Simple.png new file mode 100644 index 0000000..bf23d73 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Diagram_Simple.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad.png new file mode 100644 index 0000000..d66bd81 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Down.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Down.png new file mode 100644 index 0000000..93478ee Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Down.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Left.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Left.png new file mode 100644 index 0000000..e1e3dfd Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Left.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Right.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Right.png new file mode 100644 index 0000000..2cabaef Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Right.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Up.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Up.png new file mode 100644 index 0000000..a466bad Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Up.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_LB.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_LB.png new file mode 100644 index 0000000..f6c414b Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_LB.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_LT.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_LT.png new file mode 100644 index 0000000..526816c Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_LT.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Left_Stick.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Left_Stick.png new file mode 100644 index 0000000..de49dc6 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Left_Stick.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Left_Stick_Click.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Left_Stick_Click.png new file mode 100644 index 0000000..ad0428f Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Left_Stick_Click.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Menu.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Menu.png new file mode 100644 index 0000000..190780e Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Menu.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_RB.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_RB.png new file mode 100644 index 0000000..5dcfc6d Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_RB.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_RT.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_RT.png new file mode 100644 index 0000000..8004286 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_RT.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Right_Stick.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Right_Stick.png new file mode 100644 index 0000000..866be1c Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Right_Stick.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Right_Stick_Click.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Right_Stick_Click.png new file mode 100644 index 0000000..de08508 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Right_Stick_Click.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Share.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Share.png new file mode 100644 index 0000000..66d9f95 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Share.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_View.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_View.png new file mode 100644 index 0000000..066086a Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_View.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_X.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_X.png new file mode 100644 index 0000000..e944b3e Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_X.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Y.png b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Y.png new file mode 100644 index 0000000..cf4a997 Binary files /dev/null and b/node_modules/@nebulaproject/core-glyphs/assets/Xbox Series/XboxSeriesX_Y.png differ diff --git a/node_modules/@nebulaproject/core-glyphs/package.json b/node_modules/@nebulaproject/core-glyphs/package.json new file mode 100644 index 0000000..e03b57f --- /dev/null +++ b/node_modules/@nebulaproject/core-glyphs/package.json @@ -0,0 +1,19 @@ +{ + "name": "@nebulaproject/core-glyphs", + "private": true, + "version": "0.1.0", + "description": "Controller glyph mappings and lookup helpers.", + "type": "module", + "exports": "./src/index.js", + "main": "./src/index.js", + "files": [ + "src", + "assets" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + } +} diff --git a/node_modules/@nebulaproject/core-glyphs/src/index.js b/node_modules/@nebulaproject/core-glyphs/src/index.js new file mode 100644 index 0000000..d2a2d82 --- /dev/null +++ b/node_modules/@nebulaproject/core-glyphs/src/index.js @@ -0,0 +1,118 @@ +/** + * @typedef {"steam-deck" | "xbox" | "playstation" | "switch" | "generic"} ControllerType + */ + +/** + * Mapping of logical actions to glyph labels. + * Glyphs are plain strings or asset keys – render them however you like. + * @type {Record>} + */ +export const glyphMap = { + "steam-deck": { + confirm: "A", + back: "B", + menu: "Menu", + view: "View", + leftBumper: "L1", + rightBumper: "R1" + }, + xbox: { + confirm: "A", + back: "B", + menu: "Menu", + view: "View", + leftBumper: "LB", + rightBumper: "RB" + }, + playstation: { + confirm: "Cross", + back: "Circle", + menu: "Options", + view: "Share", + leftBumper: "L1", + rightBumper: "R1" + }, + switch: { + confirm: "A", + back: "B", + menu: "Plus", + view: "Minus", + leftBumper: "LB", + rightBumper: "RB" + }, + generic: { + confirm: "1", + back: "2", + menu: "Menu", + view: "View", + leftBumper: "L", + rightBumper: "R" + } +}; + +/** + * Asset filenames for common actions per controller. + * @type {Record>} + */ +export const glyphAssetMap = { + "steam-deck": { + confirm: "assets/Steam Deck/SteamDeck_A.png", + back: "assets/Steam Deck/SteamDeck_B.png", + menu: "assets/Steam Deck/SteamDeck_Menu.png", + view: "assets/Steam Deck/SteamDeck_Dots.png", + leftBumper: "assets/Steam Deck/SteamDeck_L1.png", + rightBumper: "assets/Steam Deck/SteamDeck_R1.png" + }, + xbox: { + confirm: "assets/Xbox Series/XboxSeriesX_A.png", + back: "assets/Xbox Series/XboxSeriesX_B.png", + menu: "assets/Xbox Series/XboxSeriesX_Menu.png", + view: "assets/Xbox Series/XboxSeriesX_View.png", + leftBumper: "assets/Xbox Series/XboxSeriesX_LB.png", + rightBumper: "assets/Xbox Series/XboxSeriesX_RB.png" + }, + playstation: { + confirm: "assets/PS5/PS5_Cross.png", + back: "assets/PS5/PS5_Circle.png", + menu: "assets/PS5/PS5_Options.png", + view: "assets/PS5/PS5_Share.png", + leftBumper: "assets/PS5/PS5_L1.png", + rightBumper: "assets/PS5/PS5_R1.png" + }, + switch: { + confirm: "assets/Switch/Switch_A.png", + back: "assets/Switch/Switch_B.png", + menu: "assets/Switch/Switch_Plus.png", + view: "assets/Switch/Switch_Minus.png", + leftBumper: "assets/Switch/Switch_LB.png", + rightBumper: "assets/Switch/Switch_RB.png" + }, + generic: { + confirm: "assets/Xbox Series/XboxSeriesX_A.png", + back: "assets/Xbox Series/XboxSeriesX_B.png", + menu: "assets/Xbox Series/XboxSeriesX_Menu.png", + view: "assets/Xbox Series/XboxSeriesX_View.png", + leftBumper: "assets/Xbox Series/XboxSeriesX_LB.png", + rightBumper: "assets/Xbox Series/XboxSeriesX_RB.png" + } +}; + +/** + * Get a glyph label for a controller type and action key. + * @param {ControllerType} controller + * @param {string} action + * @returns {string | null} + */ +export function getGlyph(controller, action) { + return glyphMap[controller]?.[action] ?? null; +} + +/** + * Get a relative asset path for a controller type and action key. + * @param {ControllerType} controller + * @param {string} action + * @returns {string | null} + */ +export function getGlyphAssetPath(controller, action) { + return glyphAssetMap[controller]?.[action] ?? null; +} diff --git a/node_modules/@nebulaproject/core-input/README.md b/node_modules/@nebulaproject/core-input/README.md new file mode 100644 index 0000000..a5a6aac --- /dev/null +++ b/node_modules/@nebulaproject/core-input/README.md @@ -0,0 +1,41 @@ +# @nebula/core-input + +Action-based input abstraction for controller-first apps. Convert raw device events into app-level actions. + +## Why it exists +Controller-focused apps should reason in terms of actions (confirm, back, pause) rather than physical inputs. This package lets you plug in your own device adapters and map them to actions consistently. + +## Usage + +```js +import { createActionMapper } from "@nebula/core-input"; + +const mapper = createActionMapper({ + bindings: { + confirm: [ + { source: "gamepad", control: "a" }, + { source: "keyboard", control: "Enter" } + ], + back: [ + { source: "gamepad", control: "b" }, + { source: "keyboard", control: "Escape" } + ] + } +}); + +mapper.onAction((update) => { + if (update.action === "confirm" && update.active) { + console.log("Confirm action"); + } +}); + +mapper.mapEvent({ + source: "gamepad", + control: "a", + type: "pressed", + value: 1 +}); +``` + +## SteamOS / Steam Deck notes +Pair this with a thin adapter that translates Steam Input or Gamepad API events into `InputEvent` objects. Keep bindings action-first so Deck, Xbox, and PlayStation controllers share the same behavior. diff --git a/node_modules/@nebulaproject/core-input/package.json b/node_modules/@nebulaproject/core-input/package.json new file mode 100644 index 0000000..fb160d3 --- /dev/null +++ b/node_modules/@nebulaproject/core-input/package.json @@ -0,0 +1,21 @@ +{ + "name": "@nebulaproject/core-input", + "private": true, + "version": "0.1.0", + "description": "Action-based input abstraction for controllers, keyboard, and mouse.", + "type": "module", + "exports": "./src/index.js", + "main": "./src/index.js", + "files": [ + "src" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + }, + "dependencies": { + "@nebulaproject/core-utils": "0.1.0" + } +} diff --git a/node_modules/@nebulaproject/core-input/src/index.js b/node_modules/@nebulaproject/core-input/src/index.js new file mode 100644 index 0000000..76286c7 --- /dev/null +++ b/node_modules/@nebulaproject/core-input/src/index.js @@ -0,0 +1,121 @@ +import { createEmitter } from "@nebula/core-utils"; + +/** + * @typedef {"gamepad" | "keyboard" | "mouse" | "touch" | "unknown"} InputSource + */ + +/** + * @typedef {"pressed" | "released" | "moved" | "axis"} InputEventType + */ + +/** + * Normalized input event shape. + * @typedef {object} InputEvent + * @property {InputSource} source + * @property {string} control - Physical control identifier (e.g. "a", "dpad-up", "left-stick-x"). + * @property {InputEventType} type + * @property {number} value - Normalized 0..1 for digital, -1..1 for analog. + * @property {number} [timestamp] + */ + +/** + * Binding definition for mapping physical controls to actions. + * @typedef {object} InputBinding + * @property {InputSource} source + * @property {string} control + * @property {InputEventType | "any"} [type] + * @property {number} [threshold] - Digital activation threshold for analog controls. + */ + +/** + * @typedef {object} ActionUpdate + * @property {string} action + * @property {number} value + * @property {boolean} active + * @property {InputEvent} event + */ + +/** + * @typedef {object} ActionState + * @property {number} value + * @property {boolean} active + */ + +/** + * @typedef {object} ActionMapperOptions + * @property {Record} bindings + * @property {number} [deadzone] - Analog deadzone applied to -1..1 axes. + */ + +/** + * Create an action-based mapper that converts raw input events into action state updates. + * @param {ActionMapperOptions} options + * @returns {{ + * mapEvent: (event: InputEvent) => ActionUpdate[], + * getActionState: (action: string) => ActionState, + * onAction: (listener: (update: ActionUpdate) => void) => () => void, + * reset: () => void + * }} + */ +export function createActionMapper(options) { + const deadzone = options.deadzone ?? 0.2; + /** @type {Map} */ + const state = new Map(); + const updates = createEmitter(); + + /** @param {string} action */ + function ensureState(action) { + if (!state.has(action)) { + state.set(action, { value: 0, active: false }); + } + return /** @type {ActionState} */ (state.get(action)); + } + + /** @param {number} value */ + function applyDeadzone(value) { + if (Math.abs(value) < deadzone) return 0; + return value; + } + + return { + mapEvent(event) { + /** @type {ActionUpdate[]} */ + const result = []; + + for (const [action, bindings] of Object.entries(options.bindings)) { + for (const binding of bindings) { + if (binding.source !== event.source) continue; + if (binding.control !== event.control) continue; + if (binding.type && binding.type !== "any" && binding.type !== event.type) continue; + + const threshold = binding.threshold ?? 0.5; + const value = event.type === "axis" || event.type === "moved" + ? applyDeadzone(event.value) + : event.type === "released" + ? 0 + : 1; + + const active = Math.abs(value) >= threshold; + const current = ensureState(action); + current.value = value; + current.active = active; + + const update = { action, value, active, event }; + result.push(update); + updates.emit(update); + } + } + + return result; + }, + getActionState(action) { + return ensureState(action); + }, + onAction(listener) { + return updates.on(listener); + }, + reset() { + state.clear(); + } + }; +} diff --git a/node_modules/@nebulaproject/core-input/test/input.test.js b/node_modules/@nebulaproject/core-input/test/input.test.js new file mode 100644 index 0000000..8aca8f9 --- /dev/null +++ b/node_modules/@nebulaproject/core-input/test/input.test.js @@ -0,0 +1,23 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createActionMapper } from "../src/index.js"; + +test("createActionMapper maps events to actions", () => { + const mapper = createActionMapper({ + bindings: { + confirm: [{ source: "gamepad", control: "a" }], + back: [{ source: "keyboard", control: "Escape" }] + } + }); + + const updates = mapper.mapEvent({ + source: "gamepad", + control: "a", + type: "pressed", + value: 1 + }); + + assert.equal(updates.length, 1); + assert.equal(updates[0].action, "confirm"); + assert.equal(mapper.getActionState("confirm").active, true); +}); diff --git a/node_modules/@nebulaproject/core-navigation/README.md b/node_modules/@nebulaproject/core-navigation/README.md new file mode 100644 index 0000000..68a9d80 --- /dev/null +++ b/node_modules/@nebulaproject/core-navigation/README.md @@ -0,0 +1,23 @@ +# @nebula/core-navigation + +Focus management and spatial navigation primitives for controller-first UIs. + +## Why it exists +Directional navigation needs predictable, consistent logic for TVs and handhelds. This package provides small, testable functions that you can plug into any UI layer. + +## Usage + +```js +import { pickBestCandidate } from "@nebula/core-navigation"; + +const current = { id: "settings", x: 100, y: 100, width: 180, height: 80 }; +const candidates = [ + { id: "play", x: 100, y: 10, width: 180, height: 80 }, + { id: "help", x: 320, y: 100, width: 180, height: 80 } +]; + +const next = pickBestCandidate(current, candidates, "up"); +``` + +## SteamOS / Steam Deck notes +Use these primitives to drive focus on elements that are at least 8–10mm tall on a TV. Combine with large hit targets and visible focus rings. diff --git a/node_modules/@nebulaproject/core-navigation/package.json b/node_modules/@nebulaproject/core-navigation/package.json new file mode 100644 index 0000000..7589834 --- /dev/null +++ b/node_modules/@nebulaproject/core-navigation/package.json @@ -0,0 +1,18 @@ +{ + "name": "@nebulaproject/core-navigation", + "private": true, + "version": "0.1.0", + "description": "Focus management and spatial navigation primitives.", + "type": "module", + "exports": "./src/index.js", + "main": "./src/index.js", + "files": [ + "src" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + } +} diff --git a/node_modules/@nebulaproject/core-navigation/src/index.js b/node_modules/@nebulaproject/core-navigation/src/index.js new file mode 100644 index 0000000..cbc492a --- /dev/null +++ b/node_modules/@nebulaproject/core-navigation/src/index.js @@ -0,0 +1,78 @@ +/** + * @typedef {"up" | "down" | "left" | "right"} Direction + */ + +/** + * @typedef {object} Rect + * @property {string} id + * @property {number} x + * @property {number} y + * @property {number} width + * @property {number} height + */ + +/** + * @param {Rect} rect + * @returns {{ x: number, y: number }} + */ +export function getRectCenter(rect) { + return { + x: rect.x + rect.width / 2, + y: rect.y + rect.height / 2 + }; +} + +/** + * Filter candidates to those that are in the requested direction. + * @param {Rect} current + * @param {Rect[]} candidates + * @param {Direction} direction + * @returns {Rect[]} + */ +export function getDirectionalCandidates(current, candidates, direction) { + const currentCenter = getRectCenter(current); + return candidates.filter((candidate) => { + const center = getRectCenter(candidate); + switch (direction) { + case "up": + return center.y < currentCenter.y; + case "down": + return center.y > currentCenter.y; + case "left": + return center.x < currentCenter.x; + case "right": + return center.x > currentCenter.x; + default: + return false; + } + }); +} + +/** + * Pick the nearest candidate in a direction using a weighted distance. + * @param {Rect} current + * @param {Rect[]} candidates + * @param {Direction} direction + * @returns {Rect | null} + */ +export function pickBestCandidate(current, candidates, direction) { + const currentCenter = getRectCenter(current); + let best = null; + let bestScore = Number.POSITIVE_INFINITY; + + for (const candidate of getDirectionalCandidates(current, candidates, direction)) { + const center = getRectCenter(candidate); + const dx = center.x - currentCenter.x; + const dy = center.y - currentCenter.y; + const primary = direction === "left" || direction === "right" ? Math.abs(dx) : Math.abs(dy); + const secondary = direction === "left" || direction === "right" ? Math.abs(dy) : Math.abs(dx); + const score = primary * 1.25 + secondary; + + if (score < bestScore) { + bestScore = score; + best = candidate; + } + } + + return best; +} diff --git a/node_modules/@nebulaproject/core-navigation/test/navigation.test.js b/node_modules/@nebulaproject/core-navigation/test/navigation.test.js new file mode 100644 index 0000000..3c41be9 --- /dev/null +++ b/node_modules/@nebulaproject/core-navigation/test/navigation.test.js @@ -0,0 +1,14 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { pickBestCandidate } from "../src/index.js"; + +test("pickBestCandidate finds nearest in direction", () => { + const current = { id: "center", x: 100, y: 100, width: 100, height: 100 }; + const candidates = [ + { id: "up", x: 100, y: 0, width: 100, height: 100 }, + { id: "right", x: 220, y: 100, width: 100, height: 100 } + ]; + + const next = pickBestCandidate(current, candidates, "up"); + assert.equal(next?.id, "up"); +}); diff --git a/node_modules/@nebulaproject/core-theme/README.md b/node_modules/@nebulaproject/core-theme/README.md new file mode 100644 index 0000000..8c27706 --- /dev/null +++ b/node_modules/@nebulaproject/core-theme/README.md @@ -0,0 +1,23 @@ +# @nebula/core-theme + +Theme tokens tuned for readable, couch-friendly UI. Exports plain objects and helpers only. + +## Why it exists +TV and handheld screens need higher contrast, larger type, and clearer spacing. This package provides opinionated defaults that work well from a distance. + +## Usage + +```js +import { baseTheme, createTheme } from "@nebula/core-theme"; + +const theme = createTheme({ + colors: { + accent: "#44d3ff" + } +}); + +console.log(baseTheme.typography.display, theme.colors.accent); +``` + +## SteamOS / Steam Deck notes +Use `display` or `title` sizes for primary UI. Keep contrast high for use in bright rooms or handheld mode. diff --git a/node_modules/@nebulaproject/core-theme/package.json b/node_modules/@nebulaproject/core-theme/package.json new file mode 100644 index 0000000..e9c62a7 --- /dev/null +++ b/node_modules/@nebulaproject/core-theme/package.json @@ -0,0 +1,18 @@ +{ + "name": "@nebulaproject/core-theme", + "private": true, + "version": "0.1.0", + "description": "Theme tokens for readable, couch-friendly UI.", + "type": "module", + "exports": "./src/index.js", + "main": "./src/index.js", + "files": [ + "src" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + } +} diff --git a/node_modules/@nebulaproject/core-theme/src/index.js b/node_modules/@nebulaproject/core-theme/src/index.js new file mode 100644 index 0000000..6514a50 --- /dev/null +++ b/node_modules/@nebulaproject/core-theme/src/index.js @@ -0,0 +1,61 @@ +/** + * Nebula Core default theme tokens. + * @type {{ + * colors: Record, + * spacing: Record, + * radius: Record, + * typography: Record, + * motion: Record + * }} + */ +export const baseTheme = { + colors: { + background: "#0b0d12", + surface: "#161a22", + accent: "#5b7cff", + text: "#f5f7ff", + textMuted: "#b7c0d8", + focus: "#8ddcff", + danger: "#ff6363" + }, + spacing: { + xs: 4, + sm: 8, + md: 16, + lg: 24, + xl: 32, + xxl: 48 + }, + radius: { + sm: 6, + md: 10, + lg: 16, + pill: 999 + }, + typography: { + caption: 12, + body: 16, + title: 22, + display: 32 + }, + motion: { + fast: 120, + base: 200, + slow: 320 + } +}; + +/** + * Merge theme overrides with the Nebula base tokens. + * @param {Partial} overrides + * @returns {typeof baseTheme} + */ +export function createTheme(overrides = {}) { + return { + colors: { ...baseTheme.colors, ...overrides.colors }, + spacing: { ...baseTheme.spacing, ...overrides.spacing }, + radius: { ...baseTheme.radius, ...overrides.radius }, + typography: { ...baseTheme.typography, ...overrides.typography }, + motion: { ...baseTheme.motion, ...overrides.motion } + }; +} diff --git a/node_modules/@nebulaproject/core-ui/README.md b/node_modules/@nebulaproject/core-ui/README.md new file mode 100644 index 0000000..f31f8e7 --- /dev/null +++ b/node_modules/@nebulaproject/core-ui/README.md @@ -0,0 +1,25 @@ +# @nebula/core-ui + +Minimal UI helpers for controller-first applications. No framework lock-in. + +## Why it exists +Controller-first UI benefits from consistent hit targets and clear focus styling. This package provides small, composable primitives you can apply in any UI layer. + +## Usage + +```js +import { focusRing, hitTarget, getFocusableAttributes } from "@nebula/core-ui"; + +const focusableProps = getFocusableAttributes({ role: "button", focusKey: "play" }); + +// Example with a UI layer that accepts inline styles +const styles = { + minHeight: hitTarget.minHeight, + minWidth: hitTarget.minWidth, + outline: `${focusRing.outlineWidth}px solid ${focusRing.outlineColor}`, + outlineOffset: focusRing.outlineOffset +}; +``` + +## SteamOS / Steam Deck notes +Use large hit targets and strong focus rings for readability on TV and handheld screens. Combine with a focus system (such as @nebula/core-navigation). diff --git a/node_modules/@nebulaproject/core-ui/package.json b/node_modules/@nebulaproject/core-ui/package.json new file mode 100644 index 0000000..7195dad --- /dev/null +++ b/node_modules/@nebulaproject/core-ui/package.json @@ -0,0 +1,18 @@ +{ + "name": "@nebulaproject/core-ui", + "private": true, + "version": "0.1.0", + "description": "Minimal, controller-first UI helpers and primitives.", + "type": "module", + "exports": "./src/index.js", + "main": "./src/index.js", + "files": [ + "src" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + } +} diff --git a/node_modules/@nebulaproject/core-ui/src/index.js b/node_modules/@nebulaproject/core-ui/src/index.js new file mode 100644 index 0000000..9f3efa1 --- /dev/null +++ b/node_modules/@nebulaproject/core-ui/src/index.js @@ -0,0 +1,43 @@ +/** + * Recommended hit target sizes for controller-first UI. + * Values are in pixels at 100% scale. + * @type {{ minSize: number, minHeight: number, minWidth: number }} + */ +export const hitTarget = { + minSize: 48, + minHeight: 48, + minWidth: 48 +}; + +/** + * Focus ring style tokens for UI libraries or CSS-in-JS. + * @type {{ outlineWidth: number, outlineOffset: number, outlineColor: string }} + */ +export const focusRing = { + outlineWidth: 3, + outlineOffset: 3, + outlineColor: "#8ddcff" +}; + +/** + * Build a minimal set of focusable attributes for controller navigation. + * Works with DOM, React, or any rendering layer that accepts plain props. + * @param {{ role?: string, tabIndex?: number, focusKey?: string }} [options] + * @returns {Record} + */ +export function getFocusableAttributes(options = {}) { + const role = options.role ?? "button"; + const tabIndex = options.tabIndex ?? 0; + /** @type {Record} */ + const attrs = { + role, + tabIndex, + "data-nebula-focus": "true" + }; + + if (options.focusKey) { + attrs["data-nebula-focus-key"] = options.focusKey; + } + + return attrs; +} diff --git a/node_modules/@nebulaproject/core-utils/README.md b/node_modules/@nebulaproject/core-utils/README.md new file mode 100644 index 0000000..c47abd0 --- /dev/null +++ b/node_modules/@nebulaproject/core-utils/README.md @@ -0,0 +1,128 @@ +# @nebula/core-utils + +Small, dependency-free utilities shared across Nebula Core packages. No DOM or platform assumptions. + +## Why it exists +Controller-first apps often duplicate tiny helpers (math, events, clamping). This package centralizes those primitives without imposing any UI or input model. + +## Installation + +```bash +npm install @nebula/core-utils +``` + +## Usage + +```js +import { clamp, createEmitter, lerp, roundTo } from "@nebula/core-utils"; + +const value = clamp(12, 0, 10); // 10 +const blended = lerp(0, 100, 0.25); // 25 +const rounded = roundTo(3.14159, 2); // 3.14 + +const inputEvents = createEmitter(); +const unsubscribe = inputEvents.on((payload) => { + console.log("input", payload); +}); + +inputEvents.emit({ action: "confirm" }); +unsubscribe(); +``` + +## API + +### clamp(value, min, max) + +Clamp a number between a minimum and maximum. + +**Parameters** +- `value` (`number`): The value to clamp. +- `min` (`number`): Lower bound. +- `max` (`number`): Upper bound. + +**Returns** +- `number`: The clamped value. + +**Examples** +```js +clamp(5, 0, 10); // 5 +clamp(-2, 0, 10); // 0 +clamp(99, 0, 10); // 10 +``` + +### lerp(from, to, t) + +Linear interpolation between two numbers. + +**Parameters** +- `from` (`number`): Start value. +- `to` (`number`): End value. +- `t` (`number`): Interpolation factor in the range 0..1. + +**Returns** +- `number`: Interpolated value. + +**Examples** +```js +lerp(0, 10, 0); // 0 +lerp(0, 10, 1); // 10 +lerp(0, 10, 0.5); // 5 +``` + +### roundTo(value, decimals) + +Round a number to a specific decimal precision. + +**Parameters** +- `value` (`number`): The value to round. +- `decimals` (`number`): Number of decimal places. + +**Returns** +- `number`: Rounded value. + +**Examples** +```js +roundTo(1.005, 2); // 1.01 +roundTo(123.4567, 1); // 123.5 +``` + +### createEmitter() + +Create a tiny event emitter for a single payload type. The emitter provides three methods: `on`, `emit`, and `clear`. + +**Returns** +An object with the shape: + +```ts +{ + on: (listener: (payload: T) => void) => () => void; + emit: (payload: T) => void; + clear: () => void; +} +``` + +**Methods** +- `on(listener)`: Registers a listener. Returns an `unsubscribe()` function. +- `emit(payload)`: Calls all listeners with the payload. +- `clear()`: Removes all listeners. + +**Examples** +```js +const events = createEmitter(); +const off = events.on((payload) => { + console.log(payload); +}); + +events.emit({ type: "focus", id: "button-1" }); +off(); +events.clear(); +``` + +## TypeScript / IntelliSense +This package ships as ES Modules with JSDoc annotations, so you get editor IntelliSense out of the box in TypeScript-aware editors. + +## SteamOS / Steam Deck notes +This package is platform-agnostic and safe to use in any runtime (browser, Electron, Node). + +## License +MIT diff --git a/node_modules/@nebulaproject/core-utils/package.json b/node_modules/@nebulaproject/core-utils/package.json new file mode 100644 index 0000000..d816b0e --- /dev/null +++ b/node_modules/@nebulaproject/core-utils/package.json @@ -0,0 +1,18 @@ +{ + "name": "@nebulaproject/core-utils", + "private": true, + "version": "0.1.0", + "description": "Shared small utilities for controller-first apps.", + "type": "module", + "exports": "./src/index.js", + "main": "./src/index.js", + "files": [ + "src" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + } +} diff --git a/node_modules/@nebulaproject/core-utils/src/index.js b/node_modules/@nebulaproject/core-utils/src/index.js new file mode 100644 index 0000000..432eb70 --- /dev/null +++ b/node_modules/@nebulaproject/core-utils/src/index.js @@ -0,0 +1,57 @@ +/** + * Clamp a number between a minimum and maximum. + * @param {number} value + * @param {number} min + * @param {number} max + * @returns {number} + */ +export function clamp(value, min, max) { + return Math.min(max, Math.max(min, value)); +} + +/** + * Linear interpolation between two numbers. + * @param {number} from + * @param {number} to + * @param {number} t - 0..1 + * @returns {number} + */ +export function lerp(from, to, t) { + return from + (to - from) * t; +} + +/** + * Round a number to a specific decimal precision. + * @param {number} value + * @param {number} decimals + * @returns {number} + */ +export function roundTo(value, decimals) { + const factor = 10 ** decimals; + return Math.round(value * factor) / factor; +} + +/** + * Create a tiny event emitter. + * @template T + * @returns {{ on: (listener: (payload: T) => void) => () => void, emit: (payload: T) => void, clear: () => void }} + */ +export function createEmitter() { + /** @type {Set<(payload: T) => void>} */ + const listeners = new Set(); + + return { + on(listener) { + listeners.add(listener); + return () => listeners.delete(listener); + }, + emit(payload) { + for (const listener of listeners) { + listener(payload); + } + }, + clear() { + listeners.clear(); + } + }; +} diff --git a/node_modules/@nebulaproject/core-utils/test/utils.test.js b/node_modules/@nebulaproject/core-utils/test/utils.test.js new file mode 100644 index 0000000..3d030da --- /dev/null +++ b/node_modules/@nebulaproject/core-utils/test/utils.test.js @@ -0,0 +1,22 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { clamp, createEmitter } from "../src/index.js"; + +test("clamp keeps value within bounds", () => { + assert.equal(clamp(5, 0, 10), 5); + assert.equal(clamp(-1, 0, 10), 0); + assert.equal(clamp(99, 0, 10), 10); +}); + +test("createEmitter registers and emits", () => { + const emitter = createEmitter(); + /** @type {Array} */ + const events = []; + + const off = emitter.on((payload) => events.push(payload)); + emitter.emit("confirm"); + off(); + emitter.emit("cancel"); + + assert.deepEqual(events, ["confirm"]); +}); diff --git a/node_modules/@nebulaproject/core/dist/glyphs.js b/node_modules/@nebulaproject/core/dist/glyphs.js new file mode 100644 index 0000000..a66f572 --- /dev/null +++ b/node_modules/@nebulaproject/core/dist/glyphs.js @@ -0,0 +1 @@ +export * from "@nebula/core-glyphs"; diff --git a/node_modules/@nebulaproject/core/dist/index.js b/node_modules/@nebulaproject/core/dist/index.js new file mode 100644 index 0000000..d2cee6e --- /dev/null +++ b/node_modules/@nebulaproject/core/dist/index.js @@ -0,0 +1,6 @@ +export * as input from "./input.js"; +export * as navigation from "./navigation.js"; +export * as theme from "./theme.js"; +export * as glyphs from "./glyphs.js"; +export * as ui from "./ui.js"; +export * as utils from "./utils.js"; diff --git a/node_modules/@nebulaproject/core/dist/input.js b/node_modules/@nebulaproject/core/dist/input.js new file mode 100644 index 0000000..c6ead57 --- /dev/null +++ b/node_modules/@nebulaproject/core/dist/input.js @@ -0,0 +1 @@ +export * from "@nebula/core-input"; diff --git a/node_modules/@nebulaproject/core/dist/navigation.js b/node_modules/@nebulaproject/core/dist/navigation.js new file mode 100644 index 0000000..b2a392a --- /dev/null +++ b/node_modules/@nebulaproject/core/dist/navigation.js @@ -0,0 +1 @@ +export * from "@nebula/core-navigation"; diff --git a/node_modules/@nebulaproject/core/dist/theme.js b/node_modules/@nebulaproject/core/dist/theme.js new file mode 100644 index 0000000..b5b26df --- /dev/null +++ b/node_modules/@nebulaproject/core/dist/theme.js @@ -0,0 +1 @@ +export * from "@nebula/core-theme"; diff --git a/node_modules/@nebulaproject/core/dist/ui.js b/node_modules/@nebulaproject/core/dist/ui.js new file mode 100644 index 0000000..44aaf63 --- /dev/null +++ b/node_modules/@nebulaproject/core/dist/ui.js @@ -0,0 +1 @@ +export * from "@nebula/core-ui"; diff --git a/node_modules/@nebulaproject/core/dist/utils.js b/node_modules/@nebulaproject/core/dist/utils.js new file mode 100644 index 0000000..22307c9 --- /dev/null +++ b/node_modules/@nebulaproject/core/dist/utils.js @@ -0,0 +1 @@ +export * from "@nebula/core-utils"; diff --git a/node_modules/@nebulaproject/core/package.json b/node_modules/@nebulaproject/core/package.json new file mode 100644 index 0000000..f22a0a4 --- /dev/null +++ b/node_modules/@nebulaproject/core/package.json @@ -0,0 +1,27 @@ +{ + "name": "@nebulaproject/core", + "version": "0.1.3", + "description": "Controller-first foundation for SteamOS-friendly Nebula apps", + "license": "MIT", + "type": "module", + "exports": { + ".": "./dist/index.js", + "./input": "./dist/input.js", + "./navigation": "./dist/navigation.js", + "./theme": "./dist/theme.js", + "./glyphs": "./dist/glyphs.js", + "./ui": "./dist/ui.js", + "./utils": "./dist/utils.js" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "node scripts/build.js" + } +} diff --git a/node_modules/@nebulaproject/core/scripts/build.js b/node_modules/@nebulaproject/core/scripts/build.js new file mode 100644 index 0000000..6cfc0fe --- /dev/null +++ b/node_modules/@nebulaproject/core/scripts/build.js @@ -0,0 +1,19 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const srcDir = path.resolve(__dirname, "..", "src"); +const distDir = path.resolve(__dirname, "..", "dist"); + +fs.rmSync(distDir, { recursive: true, force: true }); +fs.mkdirSync(distDir, { recursive: true }); + +for (const file of fs.readdirSync(srcDir)) { + if (!file.endsWith(".js")) continue; + fs.copyFileSync(path.join(srcDir, file), path.join(distDir, file)); +} + +console.log("Built @nebula/core -> dist/"); diff --git a/node_modules/@nebulaproject/core/src/glyphs.js b/node_modules/@nebulaproject/core/src/glyphs.js new file mode 100644 index 0000000..a66f572 --- /dev/null +++ b/node_modules/@nebulaproject/core/src/glyphs.js @@ -0,0 +1 @@ +export * from "@nebula/core-glyphs"; diff --git a/node_modules/@nebulaproject/core/src/index.js b/node_modules/@nebulaproject/core/src/index.js new file mode 100644 index 0000000..d2cee6e --- /dev/null +++ b/node_modules/@nebulaproject/core/src/index.js @@ -0,0 +1,6 @@ +export * as input from "./input.js"; +export * as navigation from "./navigation.js"; +export * as theme from "./theme.js"; +export * as glyphs from "./glyphs.js"; +export * as ui from "./ui.js"; +export * as utils from "./utils.js"; diff --git a/node_modules/@nebulaproject/core/src/input.js b/node_modules/@nebulaproject/core/src/input.js new file mode 100644 index 0000000..c6ead57 --- /dev/null +++ b/node_modules/@nebulaproject/core/src/input.js @@ -0,0 +1 @@ +export * from "@nebula/core-input"; diff --git a/node_modules/@nebulaproject/core/src/navigation.js b/node_modules/@nebulaproject/core/src/navigation.js new file mode 100644 index 0000000..b2a392a --- /dev/null +++ b/node_modules/@nebulaproject/core/src/navigation.js @@ -0,0 +1 @@ +export * from "@nebula/core-navigation"; diff --git a/node_modules/@nebulaproject/core/src/theme.js b/node_modules/@nebulaproject/core/src/theme.js new file mode 100644 index 0000000..b5b26df --- /dev/null +++ b/node_modules/@nebulaproject/core/src/theme.js @@ -0,0 +1 @@ +export * from "@nebula/core-theme"; diff --git a/node_modules/@nebulaproject/core/src/ui.js b/node_modules/@nebulaproject/core/src/ui.js new file mode 100644 index 0000000..44aaf63 --- /dev/null +++ b/node_modules/@nebulaproject/core/src/ui.js @@ -0,0 +1 @@ +export * from "@nebula/core-ui"; diff --git a/node_modules/@nebulaproject/core/src/utils.js b/node_modules/@nebulaproject/core/src/utils.js new file mode 100644 index 0000000..22307c9 --- /dev/null +++ b/node_modules/@nebulaproject/core/src/utils.js @@ -0,0 +1 @@ +export * from "@nebula/core-utils"; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..369a144 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,94 @@ +{ + "name": "nebula-core", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nebula-core", + "workspaces": [ + "packages/*" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@nebulaproject/core": { + "resolved": "packages/core", + "link": true + }, + "node_modules/@nebulaproject/core-glyphs": { + "resolved": "packages/core-glyphs", + "link": true + }, + "node_modules/@nebulaproject/core-input": { + "resolved": "packages/core-input", + "link": true + }, + "node_modules/@nebulaproject/core-navigation": { + "resolved": "packages/core-navigation", + "link": true + }, + "node_modules/@nebulaproject/core-theme": { + "resolved": "packages/core-theme", + "link": true + }, + "node_modules/@nebulaproject/core-ui": { + "resolved": "packages/core-ui", + "link": true + }, + "node_modules/@nebulaproject/core-utils": { + "resolved": "packages/core-utils", + "link": true + }, + "packages/core": { + "name": "@nebulaproject/core", + "version": "0.1.3", + "license": "MIT" + }, + "packages/core-glyphs": { + "name": "@nebulaproject/core-glyphs", + "version": "0.1.0", + "engines": { + "node": ">=18" + } + }, + "packages/core-input": { + "name": "@nebulaproject/core-input", + "version": "0.1.0", + "dependencies": { + "@nebulaproject/core-utils": "0.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "packages/core-navigation": { + "name": "@nebulaproject/core-navigation", + "version": "0.1.0", + "engines": { + "node": ">=18" + } + }, + "packages/core-theme": { + "name": "@nebulaproject/core-theme", + "version": "0.1.0", + "engines": { + "node": ">=18" + } + }, + "packages/core-ui": { + "name": "@nebulaproject/core-ui", + "version": "0.1.0", + "engines": { + "node": ">=18" + } + }, + "packages/core-utils": { + "name": "@nebulaproject/core-utils", + "version": "0.1.0", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ab0e490 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "nebula-core", + "private": true, + "description": "Controller-first, SteamOS-friendly foundation packages.", + "type": "module", + "workspaces": [ + "packages/*" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + } +} diff --git a/packages/core-glyphs/README.md b/packages/core-glyphs/README.md new file mode 100644 index 0000000..1f1a18f --- /dev/null +++ b/packages/core-glyphs/README.md @@ -0,0 +1,21 @@ +# @nebula/core-glyphs + +Controller glyph mappings and lookup helpers. Data-only, no rendering logic. + +## Why it exists +Controller prompts should be consistent across Steam Deck, Xbox, and PlayStation. This package keeps glyph mapping in one place. + +## Usage + +```js +import { getGlyph, getGlyphAssetPath } from "@nebula/core-glyphs"; + +const glyph = getGlyph("steam-deck", "confirm"); +console.log(glyph); // "A" + +const asset = getGlyphAssetPath("steam-deck", "confirm"); +console.log(asset); // "assets/Steam Deck/SteamDeck_A.png" +``` + +## SteamOS / Steam Deck notes +Provide Deck-specific prompts by selecting `steam-deck` when the device is detected. Use the asset path helper or bind your own icon assets keyed by the glyph labels. diff --git a/packages/core-glyphs/assets/PS5/PS5_Circle.png b/packages/core-glyphs/assets/PS5/PS5_Circle.png new file mode 100644 index 0000000..05a89a9 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Circle.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Cross.png b/packages/core-glyphs/assets/PS5/PS5_Cross.png new file mode 100644 index 0000000..395a898 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Cross.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Diagram.png b/packages/core-glyphs/assets/PS5/PS5_Diagram.png new file mode 100644 index 0000000..d452d34 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Diagram.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Diagram_Simple.png b/packages/core-glyphs/assets/PS5/PS5_Diagram_Simple.png new file mode 100644 index 0000000..99d85c9 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Diagram_Simple.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Dpad.png b/packages/core-glyphs/assets/PS5/PS5_Dpad.png new file mode 100644 index 0000000..49e6405 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Dpad.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Dpad_Down.png b/packages/core-glyphs/assets/PS5/PS5_Dpad_Down.png new file mode 100644 index 0000000..a8f893a Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Dpad_Down.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Dpad_Left.png b/packages/core-glyphs/assets/PS5/PS5_Dpad_Left.png new file mode 100644 index 0000000..2bdc048 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Dpad_Left.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Dpad_Right.png b/packages/core-glyphs/assets/PS5/PS5_Dpad_Right.png new file mode 100644 index 0000000..b7cd568 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Dpad_Right.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Dpad_Up.png b/packages/core-glyphs/assets/PS5/PS5_Dpad_Up.png new file mode 100644 index 0000000..99180d2 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Dpad_Up.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_L1.png b/packages/core-glyphs/assets/PS5/PS5_L1.png new file mode 100644 index 0000000..07e505a Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_L1.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_L2.png b/packages/core-glyphs/assets/PS5/PS5_L2.png new file mode 100644 index 0000000..05f3dd2 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_L2.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Left_Stick.png b/packages/core-glyphs/assets/PS5/PS5_Left_Stick.png new file mode 100644 index 0000000..0245ff8 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Left_Stick.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Left_Stick_Click.png b/packages/core-glyphs/assets/PS5/PS5_Left_Stick_Click.png new file mode 100644 index 0000000..66e5271 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Left_Stick_Click.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Microphone.png b/packages/core-glyphs/assets/PS5/PS5_Microphone.png new file mode 100644 index 0000000..bb0f331 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Microphone.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Options.png b/packages/core-glyphs/assets/PS5/PS5_Options.png new file mode 100644 index 0000000..3e56fe2 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Options.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Options_Alt.png b/packages/core-glyphs/assets/PS5/PS5_Options_Alt.png new file mode 100644 index 0000000..ca28364 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Options_Alt.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_R1.png b/packages/core-glyphs/assets/PS5/PS5_R1.png new file mode 100644 index 0000000..2cff97a Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_R1.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_R2.png b/packages/core-glyphs/assets/PS5/PS5_R2.png new file mode 100644 index 0000000..a13f17f Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_R2.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Right_Stick.png b/packages/core-glyphs/assets/PS5/PS5_Right_Stick.png new file mode 100644 index 0000000..85c1556 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Right_Stick.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Right_Stick_Click.png b/packages/core-glyphs/assets/PS5/PS5_Right_Stick_Click.png new file mode 100644 index 0000000..eecd4e8 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Right_Stick_Click.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Share.png b/packages/core-glyphs/assets/PS5/PS5_Share.png new file mode 100644 index 0000000..d96e698 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Share.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Share_Alt.png b/packages/core-glyphs/assets/PS5/PS5_Share_Alt.png new file mode 100644 index 0000000..10941aa Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Share_Alt.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Square.png b/packages/core-glyphs/assets/PS5/PS5_Square.png new file mode 100644 index 0000000..20f6065 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Square.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Touch_Pad.png b/packages/core-glyphs/assets/PS5/PS5_Touch_Pad.png new file mode 100644 index 0000000..1a77d0c Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Touch_Pad.png differ diff --git a/packages/core-glyphs/assets/PS5/PS5_Triangle.png b/packages/core-glyphs/assets/PS5/PS5_Triangle.png new file mode 100644 index 0000000..4950d17 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/PS5_Triangle.png differ diff --git a/packages/core-glyphs/assets/PS5/Thumbs.db b/packages/core-glyphs/assets/PS5/Thumbs.db new file mode 100644 index 0000000..e54da85 Binary files /dev/null and b/packages/core-glyphs/assets/PS5/Thumbs.db differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_A.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_A.png new file mode 100644 index 0000000..36e4867 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_A.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_B.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_B.png new file mode 100644 index 0000000..aa94303 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_B.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dots.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dots.png new file mode 100644 index 0000000..fb04073 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dots.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad.png new file mode 100644 index 0000000..e79effa Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Down.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Down.png new file mode 100644 index 0000000..f6976e4 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Down.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Left.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Left.png new file mode 100644 index 0000000..80b5647 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Left.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Right.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Right.png new file mode 100644 index 0000000..37953ea Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Right.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Up.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Up.png new file mode 100644 index 0000000..6fa4a6b Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Dpad_Up.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Gyro.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Gyro.png new file mode 100644 index 0000000..4db3d2e Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Gyro.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Inventory.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Inventory.png new file mode 100644 index 0000000..c6e297b Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Inventory.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_L1.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_L1.png new file mode 100644 index 0000000..25e2562 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_L1.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_L2.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_L2.png new file mode 100644 index 0000000..399ffb4 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_L2.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_L4.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_L4.png new file mode 100644 index 0000000..3a2b4be Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_L4.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_L5.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_L5.png new file mode 100644 index 0000000..507fc01 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_L5.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Left_Stick.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Left_Stick.png new file mode 100644 index 0000000..e006d0a Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Left_Stick.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Left_Stick_Click.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Left_Stick_Click.png new file mode 100644 index 0000000..d7e2029 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Left_Stick_Click.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Left_Track.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Left_Track.png new file mode 100644 index 0000000..d1596fa Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Left_Track.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Menu.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Menu.png new file mode 100644 index 0000000..39c5d17 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Menu.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Minus.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Minus.png new file mode 100644 index 0000000..1347872 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Minus.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Plus.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Plus.png new file mode 100644 index 0000000..394d5bb Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Plus.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Power.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Power.png new file mode 100644 index 0000000..13bfec5 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Power.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_R1.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_R1.png new file mode 100644 index 0000000..4d7ce77 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_R1.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_R2.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_R2.png new file mode 100644 index 0000000..bb4787a Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_R2.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_R4.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_R4.png new file mode 100644 index 0000000..07f6110 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_R4.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_R5.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_R5.png new file mode 100644 index 0000000..d5659cb Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_R5.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Right_Stick.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Right_Stick.png new file mode 100644 index 0000000..a2fc9db Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Right_Stick.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Right_Stick_Click.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Right_Stick_Click.png new file mode 100644 index 0000000..2236e57 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Right_Stick_Click.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Right_Track.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Right_Track.png new file mode 100644 index 0000000..0628aba Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Right_Track.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Square.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Square.png new file mode 100644 index 0000000..d87ae38 Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Square.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Steam.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Steam.png new file mode 100644 index 0000000..af63f7f Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Steam.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_X.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_X.png new file mode 100644 index 0000000..606739b Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_X.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/SteamDeck_Y.png b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Y.png new file mode 100644 index 0000000..4a0423f Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/SteamDeck_Y.png differ diff --git a/packages/core-glyphs/assets/Steam Deck/Thumbs.db b/packages/core-glyphs/assets/Steam Deck/Thumbs.db new file mode 100644 index 0000000..034e75a Binary files /dev/null and b/packages/core-glyphs/assets/Steam Deck/Thumbs.db differ diff --git a/packages/core-glyphs/assets/Switch/Switch_A.png b/packages/core-glyphs/assets/Switch/Switch_A.png new file mode 100644 index 0000000..df756ef Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_A.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_B.png b/packages/core-glyphs/assets/Switch/Switch_B.png new file mode 100644 index 0000000..ea7e743 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_B.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Controller_Left.png b/packages/core-glyphs/assets/Switch/Switch_Controller_Left.png new file mode 100644 index 0000000..f90a244 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Controller_Left.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Controller_Right.png b/packages/core-glyphs/assets/Switch/Switch_Controller_Right.png new file mode 100644 index 0000000..873da7e Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Controller_Right.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Controllers.png b/packages/core-glyphs/assets/Switch/Switch_Controllers.png new file mode 100644 index 0000000..b6ee54d Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Controllers.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Controllers_Separate.png b/packages/core-glyphs/assets/Switch/Switch_Controllers_Separate.png new file mode 100644 index 0000000..0c019da Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Controllers_Separate.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Down.png b/packages/core-glyphs/assets/Switch/Switch_Down.png new file mode 100644 index 0000000..7b7b2b2 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Down.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Dpad.png b/packages/core-glyphs/assets/Switch/Switch_Dpad.png new file mode 100644 index 0000000..12f01eb Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Dpad.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Dpad_Down.png b/packages/core-glyphs/assets/Switch/Switch_Dpad_Down.png new file mode 100644 index 0000000..37f6d5b Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Dpad_Down.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Dpad_Left.png b/packages/core-glyphs/assets/Switch/Switch_Dpad_Left.png new file mode 100644 index 0000000..8efd7a4 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Dpad_Left.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Dpad_Right.png b/packages/core-glyphs/assets/Switch/Switch_Dpad_Right.png new file mode 100644 index 0000000..8b5411d Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Dpad_Right.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Dpad_Up.png b/packages/core-glyphs/assets/Switch/Switch_Dpad_Up.png new file mode 100644 index 0000000..700a8ba Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Dpad_Up.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Home.png b/packages/core-glyphs/assets/Switch/Switch_Home.png new file mode 100644 index 0000000..9b6733c Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Home.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_LB.png b/packages/core-glyphs/assets/Switch/Switch_LB.png new file mode 100644 index 0000000..ddfa3b9 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_LB.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_LT.png b/packages/core-glyphs/assets/Switch/Switch_LT.png new file mode 100644 index 0000000..6942e1f Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_LT.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Left.png b/packages/core-glyphs/assets/Switch/Switch_Left.png new file mode 100644 index 0000000..fd58439 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Left.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Left_Stick.png b/packages/core-glyphs/assets/Switch/Switch_Left_Stick.png new file mode 100644 index 0000000..d861ca5 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Left_Stick.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Left_Stick_Click.png b/packages/core-glyphs/assets/Switch/Switch_Left_Stick_Click.png new file mode 100644 index 0000000..66e5271 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Left_Stick_Click.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Minus.png b/packages/core-glyphs/assets/Switch/Switch_Minus.png new file mode 100644 index 0000000..d32608e Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Minus.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Plus.png b/packages/core-glyphs/assets/Switch/Switch_Plus.png new file mode 100644 index 0000000..f1b0dc4 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Plus.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_RB.png b/packages/core-glyphs/assets/Switch/Switch_RB.png new file mode 100644 index 0000000..01f137a Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_RB.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_RT.png b/packages/core-glyphs/assets/Switch/Switch_RT.png new file mode 100644 index 0000000..6aef3cb Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_RT.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Right.png b/packages/core-glyphs/assets/Switch/Switch_Right.png new file mode 100644 index 0000000..f524c6c Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Right.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Right_Stick.png b/packages/core-glyphs/assets/Switch/Switch_Right_Stick.png new file mode 100644 index 0000000..f2c605b Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Right_Stick.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Right_Stick_Click.png b/packages/core-glyphs/assets/Switch/Switch_Right_Stick_Click.png new file mode 100644 index 0000000..eecd4e8 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Right_Stick_Click.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Square.png b/packages/core-glyphs/assets/Switch/Switch_Square.png new file mode 100644 index 0000000..d0fd432 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Square.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Up.png b/packages/core-glyphs/assets/Switch/Switch_Up.png new file mode 100644 index 0000000..352f890 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Up.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_X.png b/packages/core-glyphs/assets/Switch/Switch_X.png new file mode 100644 index 0000000..bdf86ba Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_X.png differ diff --git a/packages/core-glyphs/assets/Switch/Switch_Y.png b/packages/core-glyphs/assets/Switch/Switch_Y.png new file mode 100644 index 0000000..46ac216 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Switch_Y.png differ diff --git a/packages/core-glyphs/assets/Switch/Thumbs.db b/packages/core-glyphs/assets/Switch/Thumbs.db new file mode 100644 index 0000000..84336d5 Binary files /dev/null and b/packages/core-glyphs/assets/Switch/Thumbs.db differ diff --git a/packages/core-glyphs/assets/Xbox Series/Thumbs.db b/packages/core-glyphs/assets/Xbox Series/Thumbs.db new file mode 100644 index 0000000..fee514b Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/Thumbs.db differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_A.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_A.png new file mode 100644 index 0000000..e22bb29 Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_A.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_B.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_B.png new file mode 100644 index 0000000..9312c26 Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_B.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Diagram.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Diagram.png new file mode 100644 index 0000000..ff29b45 Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Diagram.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Diagram_Simple.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Diagram_Simple.png new file mode 100644 index 0000000..bf23d73 Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Diagram_Simple.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad.png new file mode 100644 index 0000000..d66bd81 Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Down.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Down.png new file mode 100644 index 0000000..93478ee Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Down.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Left.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Left.png new file mode 100644 index 0000000..e1e3dfd Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Left.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Right.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Right.png new file mode 100644 index 0000000..2cabaef Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Right.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Up.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Up.png new file mode 100644 index 0000000..a466bad Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Dpad_Up.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_LB.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_LB.png new file mode 100644 index 0000000..f6c414b Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_LB.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_LT.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_LT.png new file mode 100644 index 0000000..526816c Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_LT.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Left_Stick.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Left_Stick.png new file mode 100644 index 0000000..de49dc6 Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Left_Stick.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Left_Stick_Click.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Left_Stick_Click.png new file mode 100644 index 0000000..ad0428f Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Left_Stick_Click.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Menu.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Menu.png new file mode 100644 index 0000000..190780e Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Menu.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_RB.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_RB.png new file mode 100644 index 0000000..5dcfc6d Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_RB.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_RT.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_RT.png new file mode 100644 index 0000000..8004286 Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_RT.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Right_Stick.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Right_Stick.png new file mode 100644 index 0000000..866be1c Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Right_Stick.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Right_Stick_Click.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Right_Stick_Click.png new file mode 100644 index 0000000..de08508 Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Right_Stick_Click.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Share.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Share.png new file mode 100644 index 0000000..66d9f95 Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Share.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_View.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_View.png new file mode 100644 index 0000000..066086a Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_View.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_X.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_X.png new file mode 100644 index 0000000..e944b3e Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_X.png differ diff --git a/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Y.png b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Y.png new file mode 100644 index 0000000..cf4a997 Binary files /dev/null and b/packages/core-glyphs/assets/Xbox Series/XboxSeriesX_Y.png differ diff --git a/packages/core-glyphs/package.json b/packages/core-glyphs/package.json new file mode 100644 index 0000000..e03b57f --- /dev/null +++ b/packages/core-glyphs/package.json @@ -0,0 +1,19 @@ +{ + "name": "@nebulaproject/core-glyphs", + "private": true, + "version": "0.1.0", + "description": "Controller glyph mappings and lookup helpers.", + "type": "module", + "exports": "./src/index.js", + "main": "./src/index.js", + "files": [ + "src", + "assets" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + } +} diff --git a/packages/core-glyphs/src/index.js b/packages/core-glyphs/src/index.js new file mode 100644 index 0000000..d2a2d82 --- /dev/null +++ b/packages/core-glyphs/src/index.js @@ -0,0 +1,118 @@ +/** + * @typedef {"steam-deck" | "xbox" | "playstation" | "switch" | "generic"} ControllerType + */ + +/** + * Mapping of logical actions to glyph labels. + * Glyphs are plain strings or asset keys – render them however you like. + * @type {Record>} + */ +export const glyphMap = { + "steam-deck": { + confirm: "A", + back: "B", + menu: "Menu", + view: "View", + leftBumper: "L1", + rightBumper: "R1" + }, + xbox: { + confirm: "A", + back: "B", + menu: "Menu", + view: "View", + leftBumper: "LB", + rightBumper: "RB" + }, + playstation: { + confirm: "Cross", + back: "Circle", + menu: "Options", + view: "Share", + leftBumper: "L1", + rightBumper: "R1" + }, + switch: { + confirm: "A", + back: "B", + menu: "Plus", + view: "Minus", + leftBumper: "LB", + rightBumper: "RB" + }, + generic: { + confirm: "1", + back: "2", + menu: "Menu", + view: "View", + leftBumper: "L", + rightBumper: "R" + } +}; + +/** + * Asset filenames for common actions per controller. + * @type {Record>} + */ +export const glyphAssetMap = { + "steam-deck": { + confirm: "assets/Steam Deck/SteamDeck_A.png", + back: "assets/Steam Deck/SteamDeck_B.png", + menu: "assets/Steam Deck/SteamDeck_Menu.png", + view: "assets/Steam Deck/SteamDeck_Dots.png", + leftBumper: "assets/Steam Deck/SteamDeck_L1.png", + rightBumper: "assets/Steam Deck/SteamDeck_R1.png" + }, + xbox: { + confirm: "assets/Xbox Series/XboxSeriesX_A.png", + back: "assets/Xbox Series/XboxSeriesX_B.png", + menu: "assets/Xbox Series/XboxSeriesX_Menu.png", + view: "assets/Xbox Series/XboxSeriesX_View.png", + leftBumper: "assets/Xbox Series/XboxSeriesX_LB.png", + rightBumper: "assets/Xbox Series/XboxSeriesX_RB.png" + }, + playstation: { + confirm: "assets/PS5/PS5_Cross.png", + back: "assets/PS5/PS5_Circle.png", + menu: "assets/PS5/PS5_Options.png", + view: "assets/PS5/PS5_Share.png", + leftBumper: "assets/PS5/PS5_L1.png", + rightBumper: "assets/PS5/PS5_R1.png" + }, + switch: { + confirm: "assets/Switch/Switch_A.png", + back: "assets/Switch/Switch_B.png", + menu: "assets/Switch/Switch_Plus.png", + view: "assets/Switch/Switch_Minus.png", + leftBumper: "assets/Switch/Switch_LB.png", + rightBumper: "assets/Switch/Switch_RB.png" + }, + generic: { + confirm: "assets/Xbox Series/XboxSeriesX_A.png", + back: "assets/Xbox Series/XboxSeriesX_B.png", + menu: "assets/Xbox Series/XboxSeriesX_Menu.png", + view: "assets/Xbox Series/XboxSeriesX_View.png", + leftBumper: "assets/Xbox Series/XboxSeriesX_LB.png", + rightBumper: "assets/Xbox Series/XboxSeriesX_RB.png" + } +}; + +/** + * Get a glyph label for a controller type and action key. + * @param {ControllerType} controller + * @param {string} action + * @returns {string | null} + */ +export function getGlyph(controller, action) { + return glyphMap[controller]?.[action] ?? null; +} + +/** + * Get a relative asset path for a controller type and action key. + * @param {ControllerType} controller + * @param {string} action + * @returns {string | null} + */ +export function getGlyphAssetPath(controller, action) { + return glyphAssetMap[controller]?.[action] ?? null; +} diff --git a/packages/core-input/README.md b/packages/core-input/README.md new file mode 100644 index 0000000..a5a6aac --- /dev/null +++ b/packages/core-input/README.md @@ -0,0 +1,41 @@ +# @nebula/core-input + +Action-based input abstraction for controller-first apps. Convert raw device events into app-level actions. + +## Why it exists +Controller-focused apps should reason in terms of actions (confirm, back, pause) rather than physical inputs. This package lets you plug in your own device adapters and map them to actions consistently. + +## Usage + +```js +import { createActionMapper } from "@nebula/core-input"; + +const mapper = createActionMapper({ + bindings: { + confirm: [ + { source: "gamepad", control: "a" }, + { source: "keyboard", control: "Enter" } + ], + back: [ + { source: "gamepad", control: "b" }, + { source: "keyboard", control: "Escape" } + ] + } +}); + +mapper.onAction((update) => { + if (update.action === "confirm" && update.active) { + console.log("Confirm action"); + } +}); + +mapper.mapEvent({ + source: "gamepad", + control: "a", + type: "pressed", + value: 1 +}); +``` + +## SteamOS / Steam Deck notes +Pair this with a thin adapter that translates Steam Input or Gamepad API events into `InputEvent` objects. Keep bindings action-first so Deck, Xbox, and PlayStation controllers share the same behavior. diff --git a/packages/core-input/package.json b/packages/core-input/package.json new file mode 100644 index 0000000..fb160d3 --- /dev/null +++ b/packages/core-input/package.json @@ -0,0 +1,21 @@ +{ + "name": "@nebulaproject/core-input", + "private": true, + "version": "0.1.0", + "description": "Action-based input abstraction for controllers, keyboard, and mouse.", + "type": "module", + "exports": "./src/index.js", + "main": "./src/index.js", + "files": [ + "src" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + }, + "dependencies": { + "@nebulaproject/core-utils": "0.1.0" + } +} diff --git a/packages/core-input/src/index.js b/packages/core-input/src/index.js new file mode 100644 index 0000000..76286c7 --- /dev/null +++ b/packages/core-input/src/index.js @@ -0,0 +1,121 @@ +import { createEmitter } from "@nebula/core-utils"; + +/** + * @typedef {"gamepad" | "keyboard" | "mouse" | "touch" | "unknown"} InputSource + */ + +/** + * @typedef {"pressed" | "released" | "moved" | "axis"} InputEventType + */ + +/** + * Normalized input event shape. + * @typedef {object} InputEvent + * @property {InputSource} source + * @property {string} control - Physical control identifier (e.g. "a", "dpad-up", "left-stick-x"). + * @property {InputEventType} type + * @property {number} value - Normalized 0..1 for digital, -1..1 for analog. + * @property {number} [timestamp] + */ + +/** + * Binding definition for mapping physical controls to actions. + * @typedef {object} InputBinding + * @property {InputSource} source + * @property {string} control + * @property {InputEventType | "any"} [type] + * @property {number} [threshold] - Digital activation threshold for analog controls. + */ + +/** + * @typedef {object} ActionUpdate + * @property {string} action + * @property {number} value + * @property {boolean} active + * @property {InputEvent} event + */ + +/** + * @typedef {object} ActionState + * @property {number} value + * @property {boolean} active + */ + +/** + * @typedef {object} ActionMapperOptions + * @property {Record} bindings + * @property {number} [deadzone] - Analog deadzone applied to -1..1 axes. + */ + +/** + * Create an action-based mapper that converts raw input events into action state updates. + * @param {ActionMapperOptions} options + * @returns {{ + * mapEvent: (event: InputEvent) => ActionUpdate[], + * getActionState: (action: string) => ActionState, + * onAction: (listener: (update: ActionUpdate) => void) => () => void, + * reset: () => void + * }} + */ +export function createActionMapper(options) { + const deadzone = options.deadzone ?? 0.2; + /** @type {Map} */ + const state = new Map(); + const updates = createEmitter(); + + /** @param {string} action */ + function ensureState(action) { + if (!state.has(action)) { + state.set(action, { value: 0, active: false }); + } + return /** @type {ActionState} */ (state.get(action)); + } + + /** @param {number} value */ + function applyDeadzone(value) { + if (Math.abs(value) < deadzone) return 0; + return value; + } + + return { + mapEvent(event) { + /** @type {ActionUpdate[]} */ + const result = []; + + for (const [action, bindings] of Object.entries(options.bindings)) { + for (const binding of bindings) { + if (binding.source !== event.source) continue; + if (binding.control !== event.control) continue; + if (binding.type && binding.type !== "any" && binding.type !== event.type) continue; + + const threshold = binding.threshold ?? 0.5; + const value = event.type === "axis" || event.type === "moved" + ? applyDeadzone(event.value) + : event.type === "released" + ? 0 + : 1; + + const active = Math.abs(value) >= threshold; + const current = ensureState(action); + current.value = value; + current.active = active; + + const update = { action, value, active, event }; + result.push(update); + updates.emit(update); + } + } + + return result; + }, + getActionState(action) { + return ensureState(action); + }, + onAction(listener) { + return updates.on(listener); + }, + reset() { + state.clear(); + } + }; +} diff --git a/packages/core-input/test/input.test.js b/packages/core-input/test/input.test.js new file mode 100644 index 0000000..8aca8f9 --- /dev/null +++ b/packages/core-input/test/input.test.js @@ -0,0 +1,23 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createActionMapper } from "../src/index.js"; + +test("createActionMapper maps events to actions", () => { + const mapper = createActionMapper({ + bindings: { + confirm: [{ source: "gamepad", control: "a" }], + back: [{ source: "keyboard", control: "Escape" }] + } + }); + + const updates = mapper.mapEvent({ + source: "gamepad", + control: "a", + type: "pressed", + value: 1 + }); + + assert.equal(updates.length, 1); + assert.equal(updates[0].action, "confirm"); + assert.equal(mapper.getActionState("confirm").active, true); +}); diff --git a/packages/core-navigation/README.md b/packages/core-navigation/README.md new file mode 100644 index 0000000..68a9d80 --- /dev/null +++ b/packages/core-navigation/README.md @@ -0,0 +1,23 @@ +# @nebula/core-navigation + +Focus management and spatial navigation primitives for controller-first UIs. + +## Why it exists +Directional navigation needs predictable, consistent logic for TVs and handhelds. This package provides small, testable functions that you can plug into any UI layer. + +## Usage + +```js +import { pickBestCandidate } from "@nebula/core-navigation"; + +const current = { id: "settings", x: 100, y: 100, width: 180, height: 80 }; +const candidates = [ + { id: "play", x: 100, y: 10, width: 180, height: 80 }, + { id: "help", x: 320, y: 100, width: 180, height: 80 } +]; + +const next = pickBestCandidate(current, candidates, "up"); +``` + +## SteamOS / Steam Deck notes +Use these primitives to drive focus on elements that are at least 8–10mm tall on a TV. Combine with large hit targets and visible focus rings. diff --git a/packages/core-navigation/package.json b/packages/core-navigation/package.json new file mode 100644 index 0000000..7589834 --- /dev/null +++ b/packages/core-navigation/package.json @@ -0,0 +1,18 @@ +{ + "name": "@nebulaproject/core-navigation", + "private": true, + "version": "0.1.0", + "description": "Focus management and spatial navigation primitives.", + "type": "module", + "exports": "./src/index.js", + "main": "./src/index.js", + "files": [ + "src" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + } +} diff --git a/packages/core-navigation/src/index.js b/packages/core-navigation/src/index.js new file mode 100644 index 0000000..cbc492a --- /dev/null +++ b/packages/core-navigation/src/index.js @@ -0,0 +1,78 @@ +/** + * @typedef {"up" | "down" | "left" | "right"} Direction + */ + +/** + * @typedef {object} Rect + * @property {string} id + * @property {number} x + * @property {number} y + * @property {number} width + * @property {number} height + */ + +/** + * @param {Rect} rect + * @returns {{ x: number, y: number }} + */ +export function getRectCenter(rect) { + return { + x: rect.x + rect.width / 2, + y: rect.y + rect.height / 2 + }; +} + +/** + * Filter candidates to those that are in the requested direction. + * @param {Rect} current + * @param {Rect[]} candidates + * @param {Direction} direction + * @returns {Rect[]} + */ +export function getDirectionalCandidates(current, candidates, direction) { + const currentCenter = getRectCenter(current); + return candidates.filter((candidate) => { + const center = getRectCenter(candidate); + switch (direction) { + case "up": + return center.y < currentCenter.y; + case "down": + return center.y > currentCenter.y; + case "left": + return center.x < currentCenter.x; + case "right": + return center.x > currentCenter.x; + default: + return false; + } + }); +} + +/** + * Pick the nearest candidate in a direction using a weighted distance. + * @param {Rect} current + * @param {Rect[]} candidates + * @param {Direction} direction + * @returns {Rect | null} + */ +export function pickBestCandidate(current, candidates, direction) { + const currentCenter = getRectCenter(current); + let best = null; + let bestScore = Number.POSITIVE_INFINITY; + + for (const candidate of getDirectionalCandidates(current, candidates, direction)) { + const center = getRectCenter(candidate); + const dx = center.x - currentCenter.x; + const dy = center.y - currentCenter.y; + const primary = direction === "left" || direction === "right" ? Math.abs(dx) : Math.abs(dy); + const secondary = direction === "left" || direction === "right" ? Math.abs(dy) : Math.abs(dx); + const score = primary * 1.25 + secondary; + + if (score < bestScore) { + bestScore = score; + best = candidate; + } + } + + return best; +} diff --git a/packages/core-navigation/test/navigation.test.js b/packages/core-navigation/test/navigation.test.js new file mode 100644 index 0000000..3c41be9 --- /dev/null +++ b/packages/core-navigation/test/navigation.test.js @@ -0,0 +1,14 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { pickBestCandidate } from "../src/index.js"; + +test("pickBestCandidate finds nearest in direction", () => { + const current = { id: "center", x: 100, y: 100, width: 100, height: 100 }; + const candidates = [ + { id: "up", x: 100, y: 0, width: 100, height: 100 }, + { id: "right", x: 220, y: 100, width: 100, height: 100 } + ]; + + const next = pickBestCandidate(current, candidates, "up"); + assert.equal(next?.id, "up"); +}); diff --git a/packages/core-theme/README.md b/packages/core-theme/README.md new file mode 100644 index 0000000..8c27706 --- /dev/null +++ b/packages/core-theme/README.md @@ -0,0 +1,23 @@ +# @nebula/core-theme + +Theme tokens tuned for readable, couch-friendly UI. Exports plain objects and helpers only. + +## Why it exists +TV and handheld screens need higher contrast, larger type, and clearer spacing. This package provides opinionated defaults that work well from a distance. + +## Usage + +```js +import { baseTheme, createTheme } from "@nebula/core-theme"; + +const theme = createTheme({ + colors: { + accent: "#44d3ff" + } +}); + +console.log(baseTheme.typography.display, theme.colors.accent); +``` + +## SteamOS / Steam Deck notes +Use `display` or `title` sizes for primary UI. Keep contrast high for use in bright rooms or handheld mode. diff --git a/packages/core-theme/package.json b/packages/core-theme/package.json new file mode 100644 index 0000000..e9c62a7 --- /dev/null +++ b/packages/core-theme/package.json @@ -0,0 +1,18 @@ +{ + "name": "@nebulaproject/core-theme", + "private": true, + "version": "0.1.0", + "description": "Theme tokens for readable, couch-friendly UI.", + "type": "module", + "exports": "./src/index.js", + "main": "./src/index.js", + "files": [ + "src" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + } +} diff --git a/packages/core-theme/src/index.js b/packages/core-theme/src/index.js new file mode 100644 index 0000000..6514a50 --- /dev/null +++ b/packages/core-theme/src/index.js @@ -0,0 +1,61 @@ +/** + * Nebula Core default theme tokens. + * @type {{ + * colors: Record, + * spacing: Record, + * radius: Record, + * typography: Record, + * motion: Record + * }} + */ +export const baseTheme = { + colors: { + background: "#0b0d12", + surface: "#161a22", + accent: "#5b7cff", + text: "#f5f7ff", + textMuted: "#b7c0d8", + focus: "#8ddcff", + danger: "#ff6363" + }, + spacing: { + xs: 4, + sm: 8, + md: 16, + lg: 24, + xl: 32, + xxl: 48 + }, + radius: { + sm: 6, + md: 10, + lg: 16, + pill: 999 + }, + typography: { + caption: 12, + body: 16, + title: 22, + display: 32 + }, + motion: { + fast: 120, + base: 200, + slow: 320 + } +}; + +/** + * Merge theme overrides with the Nebula base tokens. + * @param {Partial} overrides + * @returns {typeof baseTheme} + */ +export function createTheme(overrides = {}) { + return { + colors: { ...baseTheme.colors, ...overrides.colors }, + spacing: { ...baseTheme.spacing, ...overrides.spacing }, + radius: { ...baseTheme.radius, ...overrides.radius }, + typography: { ...baseTheme.typography, ...overrides.typography }, + motion: { ...baseTheme.motion, ...overrides.motion } + }; +} diff --git a/packages/core-ui/README.md b/packages/core-ui/README.md new file mode 100644 index 0000000..f31f8e7 --- /dev/null +++ b/packages/core-ui/README.md @@ -0,0 +1,25 @@ +# @nebula/core-ui + +Minimal UI helpers for controller-first applications. No framework lock-in. + +## Why it exists +Controller-first UI benefits from consistent hit targets and clear focus styling. This package provides small, composable primitives you can apply in any UI layer. + +## Usage + +```js +import { focusRing, hitTarget, getFocusableAttributes } from "@nebula/core-ui"; + +const focusableProps = getFocusableAttributes({ role: "button", focusKey: "play" }); + +// Example with a UI layer that accepts inline styles +const styles = { + minHeight: hitTarget.minHeight, + minWidth: hitTarget.minWidth, + outline: `${focusRing.outlineWidth}px solid ${focusRing.outlineColor}`, + outlineOffset: focusRing.outlineOffset +}; +``` + +## SteamOS / Steam Deck notes +Use large hit targets and strong focus rings for readability on TV and handheld screens. Combine with a focus system (such as @nebula/core-navigation). diff --git a/packages/core-ui/package.json b/packages/core-ui/package.json new file mode 100644 index 0000000..7195dad --- /dev/null +++ b/packages/core-ui/package.json @@ -0,0 +1,18 @@ +{ + "name": "@nebulaproject/core-ui", + "private": true, + "version": "0.1.0", + "description": "Minimal, controller-first UI helpers and primitives.", + "type": "module", + "exports": "./src/index.js", + "main": "./src/index.js", + "files": [ + "src" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + } +} diff --git a/packages/core-ui/src/index.js b/packages/core-ui/src/index.js new file mode 100644 index 0000000..9f3efa1 --- /dev/null +++ b/packages/core-ui/src/index.js @@ -0,0 +1,43 @@ +/** + * Recommended hit target sizes for controller-first UI. + * Values are in pixels at 100% scale. + * @type {{ minSize: number, minHeight: number, minWidth: number }} + */ +export const hitTarget = { + minSize: 48, + minHeight: 48, + minWidth: 48 +}; + +/** + * Focus ring style tokens for UI libraries or CSS-in-JS. + * @type {{ outlineWidth: number, outlineOffset: number, outlineColor: string }} + */ +export const focusRing = { + outlineWidth: 3, + outlineOffset: 3, + outlineColor: "#8ddcff" +}; + +/** + * Build a minimal set of focusable attributes for controller navigation. + * Works with DOM, React, or any rendering layer that accepts plain props. + * @param {{ role?: string, tabIndex?: number, focusKey?: string }} [options] + * @returns {Record} + */ +export function getFocusableAttributes(options = {}) { + const role = options.role ?? "button"; + const tabIndex = options.tabIndex ?? 0; + /** @type {Record} */ + const attrs = { + role, + tabIndex, + "data-nebula-focus": "true" + }; + + if (options.focusKey) { + attrs["data-nebula-focus-key"] = options.focusKey; + } + + return attrs; +} diff --git a/packages/core-utils/README.md b/packages/core-utils/README.md new file mode 100644 index 0000000..c47abd0 --- /dev/null +++ b/packages/core-utils/README.md @@ -0,0 +1,128 @@ +# @nebula/core-utils + +Small, dependency-free utilities shared across Nebula Core packages. No DOM or platform assumptions. + +## Why it exists +Controller-first apps often duplicate tiny helpers (math, events, clamping). This package centralizes those primitives without imposing any UI or input model. + +## Installation + +```bash +npm install @nebula/core-utils +``` + +## Usage + +```js +import { clamp, createEmitter, lerp, roundTo } from "@nebula/core-utils"; + +const value = clamp(12, 0, 10); // 10 +const blended = lerp(0, 100, 0.25); // 25 +const rounded = roundTo(3.14159, 2); // 3.14 + +const inputEvents = createEmitter(); +const unsubscribe = inputEvents.on((payload) => { + console.log("input", payload); +}); + +inputEvents.emit({ action: "confirm" }); +unsubscribe(); +``` + +## API + +### clamp(value, min, max) + +Clamp a number between a minimum and maximum. + +**Parameters** +- `value` (`number`): The value to clamp. +- `min` (`number`): Lower bound. +- `max` (`number`): Upper bound. + +**Returns** +- `number`: The clamped value. + +**Examples** +```js +clamp(5, 0, 10); // 5 +clamp(-2, 0, 10); // 0 +clamp(99, 0, 10); // 10 +``` + +### lerp(from, to, t) + +Linear interpolation between two numbers. + +**Parameters** +- `from` (`number`): Start value. +- `to` (`number`): End value. +- `t` (`number`): Interpolation factor in the range 0..1. + +**Returns** +- `number`: Interpolated value. + +**Examples** +```js +lerp(0, 10, 0); // 0 +lerp(0, 10, 1); // 10 +lerp(0, 10, 0.5); // 5 +``` + +### roundTo(value, decimals) + +Round a number to a specific decimal precision. + +**Parameters** +- `value` (`number`): The value to round. +- `decimals` (`number`): Number of decimal places. + +**Returns** +- `number`: Rounded value. + +**Examples** +```js +roundTo(1.005, 2); // 1.01 +roundTo(123.4567, 1); // 123.5 +``` + +### createEmitter() + +Create a tiny event emitter for a single payload type. The emitter provides three methods: `on`, `emit`, and `clear`. + +**Returns** +An object with the shape: + +```ts +{ + on: (listener: (payload: T) => void) => () => void; + emit: (payload: T) => void; + clear: () => void; +} +``` + +**Methods** +- `on(listener)`: Registers a listener. Returns an `unsubscribe()` function. +- `emit(payload)`: Calls all listeners with the payload. +- `clear()`: Removes all listeners. + +**Examples** +```js +const events = createEmitter(); +const off = events.on((payload) => { + console.log(payload); +}); + +events.emit({ type: "focus", id: "button-1" }); +off(); +events.clear(); +``` + +## TypeScript / IntelliSense +This package ships as ES Modules with JSDoc annotations, so you get editor IntelliSense out of the box in TypeScript-aware editors. + +## SteamOS / Steam Deck notes +This package is platform-agnostic and safe to use in any runtime (browser, Electron, Node). + +## License +MIT diff --git a/packages/core-utils/package.json b/packages/core-utils/package.json new file mode 100644 index 0000000..d816b0e --- /dev/null +++ b/packages/core-utils/package.json @@ -0,0 +1,18 @@ +{ + "name": "@nebulaproject/core-utils", + "private": true, + "version": "0.1.0", + "description": "Shared small utilities for controller-first apps.", + "type": "module", + "exports": "./src/index.js", + "main": "./src/index.js", + "files": [ + "src" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "node --test" + } +} diff --git a/packages/core-utils/src/index.js b/packages/core-utils/src/index.js new file mode 100644 index 0000000..432eb70 --- /dev/null +++ b/packages/core-utils/src/index.js @@ -0,0 +1,57 @@ +/** + * Clamp a number between a minimum and maximum. + * @param {number} value + * @param {number} min + * @param {number} max + * @returns {number} + */ +export function clamp(value, min, max) { + return Math.min(max, Math.max(min, value)); +} + +/** + * Linear interpolation between two numbers. + * @param {number} from + * @param {number} to + * @param {number} t - 0..1 + * @returns {number} + */ +export function lerp(from, to, t) { + return from + (to - from) * t; +} + +/** + * Round a number to a specific decimal precision. + * @param {number} value + * @param {number} decimals + * @returns {number} + */ +export function roundTo(value, decimals) { + const factor = 10 ** decimals; + return Math.round(value * factor) / factor; +} + +/** + * Create a tiny event emitter. + * @template T + * @returns {{ on: (listener: (payload: T) => void) => () => void, emit: (payload: T) => void, clear: () => void }} + */ +export function createEmitter() { + /** @type {Set<(payload: T) => void>} */ + const listeners = new Set(); + + return { + on(listener) { + listeners.add(listener); + return () => listeners.delete(listener); + }, + emit(payload) { + for (const listener of listeners) { + listener(payload); + } + }, + clear() { + listeners.clear(); + } + }; +} diff --git a/packages/core-utils/test/utils.test.js b/packages/core-utils/test/utils.test.js new file mode 100644 index 0000000..3d030da --- /dev/null +++ b/packages/core-utils/test/utils.test.js @@ -0,0 +1,22 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { clamp, createEmitter } from "../src/index.js"; + +test("clamp keeps value within bounds", () => { + assert.equal(clamp(5, 0, 10), 5); + assert.equal(clamp(-1, 0, 10), 0); + assert.equal(clamp(99, 0, 10), 10); +}); + +test("createEmitter registers and emits", () => { + const emitter = createEmitter(); + /** @type {Array} */ + const events = []; + + const off = emitter.on((payload) => events.push(payload)); + emitter.emit("confirm"); + off(); + emitter.emit("cancel"); + + assert.deepEqual(events, ["confirm"]); +}); diff --git a/packages/core/dist/glyphs.js b/packages/core/dist/glyphs.js new file mode 100644 index 0000000..a66f572 --- /dev/null +++ b/packages/core/dist/glyphs.js @@ -0,0 +1 @@ +export * from "@nebula/core-glyphs"; diff --git a/packages/core/dist/index.js b/packages/core/dist/index.js new file mode 100644 index 0000000..d2cee6e --- /dev/null +++ b/packages/core/dist/index.js @@ -0,0 +1,6 @@ +export * as input from "./input.js"; +export * as navigation from "./navigation.js"; +export * as theme from "./theme.js"; +export * as glyphs from "./glyphs.js"; +export * as ui from "./ui.js"; +export * as utils from "./utils.js"; diff --git a/packages/core/dist/input.js b/packages/core/dist/input.js new file mode 100644 index 0000000..c6ead57 --- /dev/null +++ b/packages/core/dist/input.js @@ -0,0 +1 @@ +export * from "@nebula/core-input"; diff --git a/packages/core/dist/navigation.js b/packages/core/dist/navigation.js new file mode 100644 index 0000000..b2a392a --- /dev/null +++ b/packages/core/dist/navigation.js @@ -0,0 +1 @@ +export * from "@nebula/core-navigation"; diff --git a/packages/core/dist/theme.js b/packages/core/dist/theme.js new file mode 100644 index 0000000..b5b26df --- /dev/null +++ b/packages/core/dist/theme.js @@ -0,0 +1 @@ +export * from "@nebula/core-theme"; diff --git a/packages/core/dist/ui.js b/packages/core/dist/ui.js new file mode 100644 index 0000000..44aaf63 --- /dev/null +++ b/packages/core/dist/ui.js @@ -0,0 +1 @@ +export * from "@nebula/core-ui"; diff --git a/packages/core/dist/utils.js b/packages/core/dist/utils.js new file mode 100644 index 0000000..22307c9 --- /dev/null +++ b/packages/core/dist/utils.js @@ -0,0 +1 @@ +export * from "@nebula/core-utils"; diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..f22a0a4 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,27 @@ +{ + "name": "@nebulaproject/core", + "version": "0.1.3", + "description": "Controller-first foundation for SteamOS-friendly Nebula apps", + "license": "MIT", + "type": "module", + "exports": { + ".": "./dist/index.js", + "./input": "./dist/input.js", + "./navigation": "./dist/navigation.js", + "./theme": "./dist/theme.js", + "./glyphs": "./dist/glyphs.js", + "./ui": "./dist/ui.js", + "./utils": "./dist/utils.js" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "node scripts/build.js" + } +} diff --git a/packages/core/scripts/build.js b/packages/core/scripts/build.js new file mode 100644 index 0000000..6cfc0fe --- /dev/null +++ b/packages/core/scripts/build.js @@ -0,0 +1,19 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const srcDir = path.resolve(__dirname, "..", "src"); +const distDir = path.resolve(__dirname, "..", "dist"); + +fs.rmSync(distDir, { recursive: true, force: true }); +fs.mkdirSync(distDir, { recursive: true }); + +for (const file of fs.readdirSync(srcDir)) { + if (!file.endsWith(".js")) continue; + fs.copyFileSync(path.join(srcDir, file), path.join(distDir, file)); +} + +console.log("Built @nebula/core -> dist/"); diff --git a/packages/core/src/glyphs.js b/packages/core/src/glyphs.js new file mode 100644 index 0000000..a66f572 --- /dev/null +++ b/packages/core/src/glyphs.js @@ -0,0 +1 @@ +export * from "@nebula/core-glyphs"; diff --git a/packages/core/src/index.js b/packages/core/src/index.js new file mode 100644 index 0000000..d2cee6e --- /dev/null +++ b/packages/core/src/index.js @@ -0,0 +1,6 @@ +export * as input from "./input.js"; +export * as navigation from "./navigation.js"; +export * as theme from "./theme.js"; +export * as glyphs from "./glyphs.js"; +export * as ui from "./ui.js"; +export * as utils from "./utils.js"; diff --git a/packages/core/src/input.js b/packages/core/src/input.js new file mode 100644 index 0000000..c6ead57 --- /dev/null +++ b/packages/core/src/input.js @@ -0,0 +1 @@ +export * from "@nebula/core-input"; diff --git a/packages/core/src/navigation.js b/packages/core/src/navigation.js new file mode 100644 index 0000000..b2a392a --- /dev/null +++ b/packages/core/src/navigation.js @@ -0,0 +1 @@ +export * from "@nebula/core-navigation"; diff --git a/packages/core/src/theme.js b/packages/core/src/theme.js new file mode 100644 index 0000000..b5b26df --- /dev/null +++ b/packages/core/src/theme.js @@ -0,0 +1 @@ +export * from "@nebula/core-theme"; diff --git a/packages/core/src/ui.js b/packages/core/src/ui.js new file mode 100644 index 0000000..44aaf63 --- /dev/null +++ b/packages/core/src/ui.js @@ -0,0 +1 @@ +export * from "@nebula/core-ui"; diff --git a/packages/core/src/utils.js b/packages/core/src/utils.js new file mode 100644 index 0000000..22307c9 --- /dev/null +++ b/packages/core/src/utils.js @@ -0,0 +1 @@ +export * from "@nebula/core-utils";