Reference implementation for the Phoenix Architecture. Work in progress.
aicoding.leaflet.pub/
ai
coding
crazy
1export interface ScoreboardEntry {
2 teamId: string;
3 teamName: string;
4 score: number;
5 isPlayer: boolean;
6}
7
8export interface LayoutConfig {
9 backgroundColor?: string;
10 gridWidth?: number;
11 gridHeight?: number;
12 showScoreboard?: boolean;
13}
14
15export interface LayoutState {
16 config: LayoutConfig;
17 scoreboard: ScoreboardEntry[];
18 gridContent: string;
19}
20
21export class Layout {
22 private state: LayoutState;
23
24 constructor(config: LayoutConfig = {}) {
25 this.state = {
26 config: {
27 backgroundColor: '#1a1a2e',
28 gridWidth: 800,
29 gridHeight: 600,
30 showScoreboard: true,
31 ...config
32 },
33 scoreboard: [],
34 gridContent: ''
35 };
36 }
37
38 public setGridContent(content: string): void {
39 this.state.gridContent = content;
40 }
41
42 public updateScoreboard(entries: ScoreboardEntry[]): void {
43 this.state.scoreboard = [...entries];
44 }
45
46 public setPlayerTeam(teamId: string): void {
47 this.state.scoreboard = this.state.scoreboard.map(entry => ({
48 ...entry,
49 isPlayer: entry.teamId === teamId
50 }));
51 }
52
53 public render(): string {
54 const { config } = this.state;
55
56 return `
57 <div class="layout-container" style="
58 background-color: ${config.backgroundColor};
59 min-height: 100vh;
60 display: flex;
61 flex-direction: column;
62 align-items: center;
63 justify-content: center;
64 padding: 20px;
65 box-sizing: border-box;
66 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
67 ">
68 ${this.renderScoreboard()}
69 <div class="grid-container" style="
70 width: ${config.gridWidth}px;
71 height: ${config.gridHeight}px;
72 border: 2px solid #333;
73 border-radius: 8px;
74 background-color: rgba(255, 255, 255, 0.05);
75 display: flex;
76 align-items: center;
77 justify-content: center;
78 margin: 20px 0;
79 ">
80 ${this.state.gridContent}
81 </div>
82 </div>
83 `;
84 }
85
86 private renderScoreboard(): string {
87 if (!this.state.config.showScoreboard || this.state.scoreboard.length === 0) {
88 return '';
89 }
90
91 const scoreboardEntries = this.state.scoreboard
92 .sort((a, b) => b.score - a.score)
93 .map(entry => this.renderScoreboardEntry(entry))
94 .join('');
95
96 return `
97 <div class="scoreboard" style="
98 background-color: rgba(255, 255, 255, 0.1);
99 border-radius: 8px;
100 padding: 16px;
101 margin-bottom: 20px;
102 min-width: 300px;
103 ">
104 <h3 style="
105 color: #fff;
106 margin: 0 0 12px 0;
107 font-size: 18px;
108 text-align: center;
109 ">Scoreboard</h3>
110 <div class="scoreboard-entries">
111 ${scoreboardEntries}
112 </div>
113 </div>
114 `;
115 }
116
117 private renderScoreboardEntry(entry: ScoreboardEntry): string {
118 const isPlayerStyle = entry.isPlayer
119 ? 'background-color: rgba(74, 144, 226, 0.3); border-left: 4px solid #4a90e2;'
120 : 'background-color: rgba(255, 255, 255, 0.05);';
121
122 return `
123 <div class="scoreboard-entry" style="
124 ${isPlayerStyle}
125 padding: 8px 12px;
126 margin: 4px 0;
127 border-radius: 4px;
128 display: flex;
129 justify-content: space-between;
130 align-items: center;
131 ">
132 <span style="
133 color: ${entry.isPlayer ? '#4a90e2' : '#fff'};
134 font-weight: ${entry.isPlayer ? 'bold' : 'normal'};
135 ">${this.escapeHtml(entry.teamName)}</span>
136 <span style="
137 color: ${entry.isPlayer ? '#4a90e2' : '#ccc'};
138 font-weight: bold;
139 ">${entry.score}</span>
140 </div>
141 `;
142 }
143
144 private escapeHtml(text: string): string {
145 return text
146 .replace(/&/g, '&')
147 .replace(/</g, '<')
148 .replace(/>/g, '>')
149 .replace(/"/g, '"')
150 .replace(/'/g, ''');
151 }
152
153 public getState(): Readonly<LayoutState> {
154 return { ...this.state };
155 }
156
157 public updateConfig(updates: Partial<LayoutConfig>): void {
158 this.state.config = { ...this.state.config, ...updates };
159 }
160}
161
162export function createLayout(config?: LayoutConfig): Layout {
163 return new Layout(config);
164}
165
166export function renderCenteredGrid(content: string, width = 800, height = 600): string {
167 const layout = createLayout({ gridWidth: width, gridHeight: height });
168 layout.setGridContent(content);
169 return layout.render();
170}
171
172/** @internal Phoenix VCS traceability — do not remove. */
173export const _phoenix = {
174 iu_id: '9a35a9f5ebc71f65e83ff408274437068be7102b862e2935ac1476754d238566',
175 name: 'Layout',
176 risk_tier: 'low',
177 canon_ids: [2 as const],
178} as const;