Add initial Nebula Core packages and docs
Import initial monorepo structure for Nebula Core: add packages (@nebula/core, core-glyphs, core-input, core-navigation, core-theme, core-ui, core-utils) with source, dist, tests and assets. Expand README with overview, quick start and API snippets, and add package-level documentation files. Add jsconfig.json for path mapping, package.json and lockfiles to bootstrap the repo. This commit sets up the project layout, docs, and local package links for further development.
@@ -1 +1,308 @@
|
||||
# Nebula-Core
|
||||
# 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<string, InputBinding[]>`
|
||||
- `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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 356 B |
|
After Width: | Height: | Size: 764 B |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.6 KiB |