import Phaser from 'phaser'; import type { DoorDressCodeRule } from '../../rules/dressCode'; import { DOOR_UI } from '../../data/strings/door'; import { DOOR_PALETTE, MONO, panel, text } from './ui'; // The laminated card on the desk. Rules land the moment Dazza's text arrives // (design §3.1), so the NEW badge is the only warning the player gets that the // punter they are already looking at just became a violation. const NEW_BADGE_MS = 12_000; interface Row { rule: DoorDressCodeRule; label: Phaser.GameObjects.Text; badge: Phaser.GameObjects.Text; addedAt: number; /** Rescinded by Dazza (rules cap at ACTIVE_RULE_CAP) — greyed, never judged. */ retired?: boolean; } export class DressCodeCard { private readonly root: Phaser.GameObjects.Container; private readonly emptyText: Phaser.GameObjects.Text; private readonly rows: Row[] = []; private readonly x: number; private readonly y: number; constructor(scene: Phaser.Scene, x: number, y: number, w: number, h: number) { this.x = x; this.y = y; this.root = scene.add.container(0, 0).setDepth(50); this.root.add(panel(scene, x, y, w, h, 0x1a2018, 0x3c4a38)); this.emptyText = text(scene, x + 5, y + 6, DOOR_UI.dressCodeEmpty, 7, DOOR_PALETTE.inkDim); this.root.add(this.emptyText); } add(scene: Phaser.Scene, rule: DoorDressCodeRule, nowMs: number): void { if (this.rows.some((r) => r.rule.id === rule.id)) return; this.emptyText.setVisible(false); const y = this.y + 6 + this.rows.length * 11; const label = scene.add.text(this.x + 5, y, `· ${rule.short}`, { fontFamily: MONO, fontSize: '7px', color: '#e0e8c8', wordWrap: { width: 150 }, }); const badge = scene.add.text(this.x + 5, y, 'NEW', { fontFamily: MONO, fontSize: '7px', color: '#ffd050', }); badge.x = this.x + 5 + label.width + 4; this.root.add([label, badge]); // A flash on arrival — the card is peripheral vision, it has to shout once. scene.tweens.add({ targets: badge, alpha: { from: 1, to: 0.25 }, duration: 400, yoyo: true, repeat: 8 }); this.rows.push({ rule, label, badge, addedAt: nowMs }); } /** ROOM shuffle: the board rewrites itself wholesale — clear and restate. */ setAll(scene: Phaser.Scene, rules: readonly DoorDressCodeRule[], nowMs: number): void { for (const r of this.rows) { r.label.destroy(); r.badge.destroy(); } this.rows.length = 0; this.emptyText.setVisible(rules.length === 0); for (const rule of rules) this.add(scene, rule, nowMs); } /** Highlight the rules the patron currently in front of you is breaking. */ highlight(violatedIds: readonly string[]): void { for (const r of this.rows) { if (r.retired) continue; const hit = violatedIds.includes(r.rule.id); r.label.setColor(hit ? '#ff9090' : '#e0e8c8'); } } /** Dazza moved on: grey the rule out. It stays on the card as a monument. */ retire(ruleId: string): void { const row = this.rows.find((r) => r.rule.id === ruleId && !r.retired); if (!row) return; row.retired = true; row.label.setColor('#4a5a48'); row.label.setText(`- ${row.rule.short}`); row.badge.setVisible(false); } update(nowMs: number): void { for (const r of this.rows) { if (r.badge.visible && nowMs - r.addedAt > NEW_BADGE_MS) r.badge.setVisible(false); } } /** The clipboard tab shares this panel's footprint — see ClipboardPanel. */ setVisible(on: boolean): void { this.root.setVisible(on); } destroy(): void { this.root.destroy(); } }