Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 6 additions & 23 deletions .changeset/default-skin-facelift.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,11 @@
'@be-music/player-web': minor
---

Redesign the default (skinless) gameplay chrome as a "night cabinet": near-black violet ground with cyan / magenta
neon accents and gold money values.
Restyle the default (skinless) select, gameplay, and result chrome as a cut-in comic: black / crimson / cream / gold plates that slam in with overshoot, diagonal slashes, and beat-synced impact frames.

The built-in chrome was a set of flat panels: single-colour bars, flat note rectangles, a star-sparkle bomb, and an
amber judgement strip. The redesign keeps the same geometry contract (LR2 default 7-keys lane positions, 640x480
design canvas) and repaints everything on top of it:
The 640x480 design canvas and LR2-default 7-keys playfield contract are unchanged. What changed is the motion language — notes, HUD stamps, and result rank letters throw in instead of fading in:

- Notes are three-tone plastic keys (highlight / body / shade with an outline) in white / neon cyan / hot pink; long
notes render as a translucent core with bright side rails and real head/tail caps.
- The judgement line is neon magenta and breathes with the beat; an LR2-style keyboard of glassy key caps sits under
it, blazing on press with an under-glow strip and a gradient key beam.
- The groove gauge is LR2's 50-cell segmented bar — orange below the clear line, red-hot above it, flickering tip,
threshold notch — labelled with the active ruleset's own gauge id, and survival gauges run the all-red scheme.
- The header HUD carries a mode pill, LED-style BPM / HI-SPEED readouts, a beat-pulsed accent trim, and a ruleset
chip (LR2 / BEATORAJA / IIDX) so the compat mode is visible at a glance.
- The score panel shows a gold score headline and a DJ-level meter under EX RATE with the IIDX ninth-boundary ticks,
so the distance to the next rank letter is legible mid-play.
- A live judge tally column (PG/GR/GD/BD/PR plus FAST / SLOW) sits beside the BGA monitor, which idles as a hex
reticle with a crosshair instead of a black hole.
- The playfield gets dark-chrome side rails with a magenta song-progress pipe, and bombs are a two-stage burst:
white-hot core, flame ring, rotating starburst spokes, ember dots, and a light pillar in the lane.
- Combo readouts tier their colour (white → cyan at 50 → gold at 200) and judge text carries a same-hue glow.

`SkinlessGameplayChromeRuntime` gains `nowMs`, `progressRatio`, `beatPhase`, `rulesetLabel`, `gaugeLabel`,
`gaugeSurvival`, `fast`, and `slow`.
- Notes are parallelogram cuts (cream / crimson / gold hazard) with matching slash bombs, lane beams, and a beating judgement line.
- Gameplay chrome opens with a wipe, stamps PLAY / BPM / ruleset on a slant, and slams judgement + combo with a chromatic offset on each hit.
- Song select slams the focused title and the active row; PLAY / AUTO are cut-in stamps on the same list geometry the click hit-test already used.
- Result opens on a CLEARED / FAILED slash banner; the rank letter stays veiled until timer 151, then slams from 3x with speed lines, while judge bars stagger in.
3 changes: 2 additions & 1 deletion docs/player-web.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ LR2 gameplay scene は default renderer を import せず、共有 gameplay runt

default gameplay chrome は `scene/gameplay-lanes.ts` の共通 lane geometry helper を使います。
scratch lane は key lane より幅広く、2P scratch は右側に表示し、DP layout では両 side を SP 幅へ押し込んで全 lane を縮小するのではなく、片側分の lane 幅を保持します。
default family は通常テキストに LINE Seed JP、判定と combo 表示に Azeret Mono を使います。
default family はタイトルと日本語ラベルに Dela Gothic One、HUD の stamp / rank / 判定ポップに Staatliches を使います。
chrome は静止した HUD ではなく、overshoot 付きの slam-in、斜めの cut、拍に同期した slash で入ります。

## beatoraja skin と theme support

Expand Down
5 changes: 3 additions & 2 deletions docs/player-web.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ skin rendering remains tied to parsed `.lr2skin` data.

Default gameplay chrome uses the shared lane geometry helpers in `scene/gameplay-lanes.ts`. Scratch lanes are wider
than key lanes, 2P scratch lanes render on the right side, and DP layouts preserve the single-side lane width instead
of shrinking every lane to fit both sides into the original SP footprint. The default family uses LINE Seed JP for
general text and Azeret Mono for judgment and combo readouts.
of shrinking every lane to fit both sides into the original SP footprint. The default family uses Dela Gothic One for
titles and Japanese labels, and Staatliches for HUD stamps, ranks, and judgement popups. Chrome slams in with overshoot,
diagonal cuts, and beat-synced slashes rather than sitting as a static HUD.

## beatoraja skin and theme support

Expand Down
2 changes: 1 addition & 1 deletion packages/player-web-demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Azeret+Mono:wght@400;500;600;700;800;900&family=LINE+Seed+JP:wght@400;700;800&display=swap"
href="https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=Staatliches&family=LINE+Seed+JP:wght@400;700;800&display=swap"
rel="stylesheet"
/>
<title>be-music web player</title>
Expand Down
7 changes: 6 additions & 1 deletion packages/player-web-demo/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,12 @@ if (!app) {

app.innerHTML = DEMO_APP_HTML;

const DEFAULT_UI_FONT_LOADS = ['400 22px "LINE Seed JP"', '700 18px "LINE Seed JP"', '900 32px "Azeret Mono"'] as const;
const DEFAULT_UI_FONT_LOADS = [
'400 22px "Dela Gothic One"',
'400 48px "Dela Gothic One"',
'400 32px "Staatliches"',
'400 22px "LINE Seed JP"',
] as const;

async function waitForDefaultUiFonts(): Promise<void> {
if (!('fonts' in document)) return;
Expand Down
105 changes: 105 additions & 0 deletions packages/player-web/src/scene/default/chrome-text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Container, Text, TextStyle } from 'pixi.js';
import type { ChildPool } from '../pixi-utils.ts';
import { DEFAULT_TEXT_FONT } from './fonts.ts';
import { DEFAULT_THEME } from './theme.ts';

export type ChromeTextWeight = '400' | '500' | '600' | '700' | '800' | '900';

export interface ChromeTextOptions {
size?: number;
weight?: ChromeTextWeight;
fill?: number;
fontFamily?: string;
letterSpacing?: number;
anchorX?: number;
anchorY?: number;
maxWidth?: number;
rotation?: number;
slam?: number;
offsetX?: number;
offsetY?: number;
alpha?: number;
stroke?: { color: number; width: number; alignment?: number; join?: 'round' | 'bevel' | 'miter' };
dropShadow?: { color: number; alpha: number; blur: number; distance: number };
}

const TEXT_STYLE_CACHE = new Map<string, TextStyle>();

/**
* Pooled (or one-shot) stamp text. Always writes scale / rotation / alpha so a recycled `Text` from {@link ChildPool}
* cannot leak the previous pass's slam.
*/
export function addChromeText(
layer: Container,
text: string,
x: number,
y: number,
opts: ChromeTextOptions = {},
pool?: ChildPool,
): Text {
const node = pool?.acquireText() ?? new Text();
const style = resolveChromeTextStyle(opts);
node.text = text;
if (node.style !== style) {
node.style = style;
}
node.anchor.set(opts.anchorX ?? 0, opts.anchorY ?? 0);
node.position.set(x + (opts.offsetX ?? 0), y + (opts.offsetY ?? 0));
node.rotation = opts.rotation ?? 0;
node.alpha = opts.alpha ?? 1;
node.scale.set(1, 1);
let widthScale = 1;
if (opts.maxWidth !== undefined && node.width > opts.maxWidth) {
widthScale = opts.maxWidth / node.width;
}
const slam = opts.slam ?? 1;
node.scale.set(widthScale * slam, slam);
if (!pool) {
layer.addChild(node);
}
return node;
}

function resolveChromeTextStyle(opts: ChromeTextOptions): TextStyle {
const stroke = opts.stroke;
const shadow = opts.dropShadow;
const key = [
opts.fill ?? DEFAULT_THEME.paper,
opts.size ?? 10,
opts.weight ?? '400',
opts.fontFamily ?? DEFAULT_TEXT_FONT,
opts.letterSpacing ?? 0,
stroke?.color ?? '',
stroke?.width ?? '',
stroke?.alignment ?? '',
stroke?.join ?? '',
shadow?.color ?? '',
shadow?.alpha ?? '',
shadow?.blur ?? '',
shadow?.distance ?? '',
].join('|');
let style = TEXT_STYLE_CACHE.get(key);
if (!style) {
style = new TextStyle({
fill: opts.fill ?? DEFAULT_THEME.paper,
fontSize: opts.size ?? 10,
fontWeight: opts.weight ?? '400',
fontFamily: opts.fontFamily ?? DEFAULT_TEXT_FONT,
letterSpacing: opts.letterSpacing ?? 0,
stroke: opts.stroke,
...(shadow
? {
dropShadow: {
color: shadow.color,
alpha: shadow.alpha,
blur: shadow.blur,
distance: shadow.distance,
angle: Math.PI / 2,
},
}
: {}),
});
TEXT_STYLE_CACHE.set(key, style);
}
return style;
}
8 changes: 5 additions & 3 deletions packages/player-web/src/scene/default/fonts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const DEFAULT_TEXT_FONT = 'LINE Seed JP, ui-sans-serif, system-ui, sans-serif';
export const DEFAULT_NUMERIC_FONT = DEFAULT_TEXT_FONT;
export const DEFAULT_JUDGE_FONT = 'Azeret Mono, ui-monospace, SFMono-Regular, Menlo, monospace';
/** Display face for titles and Japanese labels. Single-weight (400) — avoid faux-bold in TextStyle. */
export const DEFAULT_TEXT_FONT = 'Dela Gothic One, ui-sans-serif, system-ui, sans-serif';
/** All-caps stamp face for HUD numbers, ranks, and judgement popups. */
export const DEFAULT_NUMERIC_FONT = 'Staatliches, ui-sans-serif, system-ui, sans-serif';
export const DEFAULT_JUDGE_FONT = DEFAULT_NUMERIC_FONT;
Loading