diff --git a/README.md b/README.md index 8c8da3a..77ec8f9 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ layers now, with the rules and panels to come next: Six evenly spaced slots: **Research, Build, Ship, ·, ·, Menu**. The slots fire `onAction` and are otherwise inert; two slots are reserved. Dressed with the menu's CRT language — scanlines over the strip and the GlitchText - RGB pull-apart on icons, labels, and the panel outline during bursts. All + RGB pull-apart on labels and the panel outline during bursts. All layout, palette, and motion (boot flicker, rail comet, glitch bursts, RGB split, scanlines) live in the config. diff --git a/data/actionbar.json b/data/actionbar.json index 746738c..5cfa467 100644 --- a/data/actionbar.json +++ b/data/actionbar.json @@ -1,14 +1,13 @@ { "_comment": "COMMAND DECK — the cyberpunk bar across the bottom of the screen (rendered by js/ui/ActionBar.js). Six evenly spaced slots: `buttons` is the ORDERED list — give a slot an `id` (the action it will fire) + `label` + `accent`, or leave both `null` for a reserved slot. Buttons currently fire `onAction` (see GameScene); the behavior behind them comes next. Everything visual is procedural (no image assets) and tunable here: layout in `height`/`margin`/`padding`/`button`, palette in `colors`, CRT scanlines in `scanline`, motion in `animation` (boot flicker, the rail comet, the glitch bursts, the RGB pull-apart).", "enabled": true, - "height": 92, + "height": 56, "margin": { "left": 20, "right": 20, "bottom": 12 }, "padding": 16, "button": { "widthFactor": 0.72, "maxWidth": 190, "notch": 10, - "iconSize": 26, "fontSize": 14, "letterSpacing": 2.5 }, @@ -39,7 +38,7 @@ "animation": { "bootStagger": 70, "rgb": { - "_comment": "RGB pull-apart on icons/labels/panel (the menu title's GlitchText technique): idleOffset px of constant chromatic fringe, burstOffset extra px at full burst, idleAlpha/burstAlpha ghost strength.", + "_comment": "RGB pull-apart on labels/panel (the menu title's GlitchText technique): idleOffset px of constant chromatic fringe, burstOffset extra px at full burst, idleAlpha/burstAlpha ghost strength.", "idleOffset": 1.8, "burstOffset": 6, "idleAlpha": 0.4, diff --git a/js/ui/ActionBar.js b/js/ui/ActionBar.js index f0f6d25..a77c4bb 100644 --- a/js/ui/ActionBar.js +++ b/js/ui/ActionBar.js @@ -6,17 +6,16 @@ import { canvasTexture } from '../utils/Textures.js'; import { CyberShape } from './CyberShape.js'; const FONT_FALLBACK = "'Segoe UI', 'Helvetica Neue', Arial, sans-serif"; -const DEG = Math.PI / 180; /** * COMMAND DECK — the cyberpunk action bar across the bottom of the screen: * a cut-corner console panel with an energy rail, six evenly spaced slot - * buttons (drawn icons, RGB-split labels, per-slot accent colors), reserved - * "standby" slots, a rail comet that streaks past on a loop, periodic glitch + * buttons (label-only, per-slot accent rail on top, RGB-split labels), + * reserved "standby" slots, a rail comet that streaks past on a loop, periodic glitch * bursts (slice bars + label jitter), and a boot flicker-in. * Dressed with the menu's CRT language: scanlines over the whole bar (the * same 1px-dark-row tile as CyberOverlay) and the GlitchText RGB pull-apart - * on everything — icons, labels, and the panel outline itself on bursts. + * on labels and the panel outline itself on bursts. * * Everything is procedural — no image assets — and fully config-driven * (data/actionbar.json): slots, labels, accents, palette, and every @@ -69,9 +68,7 @@ export class ActionBar extends Phaser.GameObjects.Container { slotBorder: toColor(ab.colors?.slotBorder, 0x22405f), reserved: toColor(ab.colors?.reserved, 0x3d4c74), inkCss: toCss(ab.colors?.text ?? '#eaf6ff'), - iconY: -12, - labelY: 16, - iconSize: bc.iconSize ?? 26, + labelY: 0, // label-only buttons: the text is centred vertically }; // The menu's CRT scanline tuning (data/theme.json) is the default, so // the bar matches the menu unless actionbar.json overrides it. @@ -212,8 +209,6 @@ export class ActionBar extends Phaser.GameObjects.Container { const letterSpacing = bc.letterSpacing ?? 2.5; const cyan = toCss('#00e5ff'); const magenta = toCss('#ff2d6f'); - const iconSize = this.style.iconSize; - const iconY = this.style.iconY; this.slots = buttons.map((b, i) => { const live = typeof b.id === 'string' && b.id.length > 0; @@ -244,31 +239,7 @@ export class ActionBar extends Phaser.GameObjects.Container { _scaleTw: null, }; s.panel = scene.add.graphics().setScrollFactor(0); - // RGB-split icon ghosts (the GlitchText technique on the menu title): - // additive magenta/cyan copies of the drawn icon, moved per frame. - s.iconGhostM = scene - .add.graphics() - .setPosition(0, iconY) - .setScrollFactor(0) - .setBlendMode(Phaser.BlendModes.ADD) - .setAlpha(0); - s.iconGhostC = scene - .add.graphics() - .setPosition(0, iconY) - .setScrollFactor(0) - .setBlendMode(Phaser.BlendModes.ADD) - .setAlpha(0); - s.icon = scene.add.graphics().setPosition(0, iconY).setScrollFactor(0); - slot.add([s.panel, s.iconGhostM, s.iconGhostC, s.icon]); - - // Drawn icon (accent for live slots, dim for reserved sockets) — - // live slots also get their ghost copies in the split colors. - const iconKind = live ? b.id : 'reserved'; - drawIcon(s.icon, iconKind, iconSize, live ? s.accent : this.style.reserved); - if (live) { - drawIcon(s.iconGhostM, iconKind, iconSize, 0xff2d6f); - drawIcon(s.iconGhostC, iconKind, iconSize, 0x00e5ff); - } + slot.add(s.panel); if (live) { const labelText = String(b.label ?? '').toUpperCase(); @@ -312,8 +283,8 @@ export class ActionBar extends Phaser.GameObjects.Container { s.panel.on('pointerout', () => this.setHover(s, false)); s.panel.on('pointerdown', () => this.press(s)); } else { - // Reserved socket: a standby dot that breathes in update(). - s.resDot = scene.add.circle(0, iconY, 1.6, this.style.reserved, 0.35).setScrollFactor(0); + // Reserved socket: a standby dot (centred) that breathes in update(). + s.resDot = scene.add.circle(0, 0, 1.6, this.style.reserved, 0.35).setScrollFactor(0); slot.add(s.resDot); } @@ -509,7 +480,7 @@ export class ActionBar extends Phaser.GameObjects.Container { /** * Fire a glitch burst right now: slice bars across the deck, and the RGB - * split (level → 1, decays in update) on icons, labels, and the panel. + * split (level → 1, decays in update) on labels and the panel. */ triggerBurst(duration = 300, intensity = 1) { const now = this.lastTime ?? this.scene.time.now; @@ -620,7 +591,7 @@ export class ActionBar extends Phaser.GameObjects.Container { const flick = 0.6 + 0.4 * Math.sin(t * 3.3); const baseA = Math.min(1, rgb.idleAlpha * flick + L * rgb.burstAlpha); - // Slots: the menu's RGB pull-apart on icon + label — magenta left, + // Slots: the menu's RGB pull-apart on the label — magenta left, // cyan right (GlitchText's exact offsets), always a faint fringe at // idle, pulled apart + jittered during bursts; per-slot phase so the // six buttons don't shimmer in lockstep. @@ -636,8 +607,6 @@ export class ActionBar extends Phaser.GameObjects.Container { const a = s.hoverOn ? Math.min(1, baseA + 0.25) : baseA; s.ghostMagenta.setAlpha(a).setPosition(-off + jx, this.style.labelY + jy * 0.5); s.ghostCyan.setAlpha(a * 0.95).setPosition(off * 0.85, this.style.labelY - off * 0.3 + jy); - s.iconGhostM.setAlpha(a * 0.8).setPosition(-off + jx, this.style.iconY + jy * 0.5); - s.iconGhostC.setAlpha(a * 0.75).setPosition(off * 0.85, this.style.iconY - off * 0.3 + jy); } // The bar itself: the panel outline pulled apart in magenta/cyan while @@ -835,85 +804,3 @@ function drawBarBody(ctx, W, H, P, c) { corner(x - 3, y + h + 3, 1, -1); corner(x + w + 3, y + h + 3, -1, -1); } - -/** - * The drawn slot icons — small, geometric, in the console's language. - * Each icon is drawn centered on the icon Graphics' origin. - */ -function drawIcon(g, id, size, color) { - const r = size * 0.44; - const glow = (lw) => g.lineStyle(lw, color, 0.25); - - switch (id) { - case 'research': { - // Orbit: a ring with a lit trail, a satellite riding it, a core. - glow(4); - g.strokeCircle(0, 0, r); - g.lineStyle(1.5, color, 0.95); - g.strokeCircle(0, 0, r); - g.lineStyle(2, color, 0.5); // trail behind the satellite - g.beginPath(); - g.arc(0, 0, r, -110 * DEG, -25 * DEG); - g.stroke(); - const sa = -35 * DEG; - g.fillStyle(color, 1); - g.fillCircle(Math.cos(sa) * r, Math.sin(sa) * r, 2.6); - g.fillStyle(color, 0.9); - g.fillCircle(0, 0, 1.7); - break; - } - - case 'build': { - // Isometric cube — construction. - const v = [0, -30, 30, 90, 150, 210].map((deg) => ({ - x: Math.cos(deg * DEG) * r, - y: Math.sin(deg * DEG) * r, - })); - glow(4); - g.strokePoints(v, true); - g.lineStyle(1.5, color, 0.95); - g.strokePoints(v, true); - g.lineStyle(1.5, color, 0.8); // inner edges of the cube - g.lineBetween(0, 0, v[0].x, v[0].y); - g.lineBetween(0, 0, v[2].x, v[2].y); - g.lineBetween(0, 0, v[4].x, v[4].y); - break; - } - - case 'ship': { - // Chevron arrow, nose up. - const pts = [ - { x: 0, y: -r }, - { x: r * 0.78, y: r * 0.62 }, - { x: 0, y: r * 0.18 }, - { x: -r * 0.78, y: r * 0.62 }, - ]; - glow(4); - g.strokePoints(pts, true); - g.lineStyle(1.5, color, 0.95); - g.strokePoints(pts, true); - break; - } - - case 'menu': { - // Signal bars — slightly ragged widths, left-aligned. - g.fillStyle(color, 0.9); - g.fillRect(-r, -r * 0.75, r * 2, 2.4); - g.fillRect(-r, -1.2, r * 1.5, 2.4); - g.fillRect(-r, r * 0.75 - 2.4, r * 1.85, 2.4); - break; - } - - default: { - // Reserved socket: a dim diamond — a slot waiting for a command. - const d = [ - { x: 0, y: -7 }, - { x: 7, y: 0 }, - { x: 0, y: 7 }, - { x: -7, y: 0 }, - ]; - g.lineStyle(1.2, color, 0.45); - g.strokePoints(d, true); - } - } -}