1// Font and styling configuration for chat messages
2
3// Retro Windows fonts
4// Primary font is Pixelated MS Sans Serif from xp.css which works for all users
5// Secondary fonts use local system fonts with proper fallbacks
6export const RETRO_FONTS = {
7 pixelated: {
8 name: 'Pixelated MS Sans Serif',
9 shortName: 'MS Sans Serif',
10 stack: '"Pixelated MS Sans Serif", "Tahoma", Geneva, sans-serif'
11 },
12 tahoma: {
13 name: 'Tahoma',
14 shortName: 'Tahoma',
15 stack: 'Tahoma, "Pixelated MS Sans Serif", Geneva, sans-serif'
16 },
17 verdana: {
18 name: 'Verdana',
19 shortName: 'Verdana',
20 stack: 'Verdana, "Pixelated MS Sans Serif", Geneva, sans-serif'
21 },
22 comicSans: {
23 name: 'Comic Sans MS',
24 shortName: 'Comic Sans',
25 stack: '"Comic Sans MS", "Pixelated MS Sans Serif", cursive, sans-serif'
26 },
27 timesNewRoman: {
28 name: 'Times New Roman',
29 shortName: 'Times',
30 stack: '"Times New Roman", Times, serif'
31 },
32 arial: {
33 name: 'Arial',
34 shortName: 'Arial',
35 stack: 'Arial, "Pixelated MS Sans Serif", Helvetica, sans-serif'
36 },
37 courierNew: {
38 name: 'Courier New',
39 shortName: 'Courier',
40 stack: '"Courier New", "Perfect DOS VGA 437 Win", Courier, monospace'
41 },
42 dos: {
43 name: 'Perfect DOS VGA 437',
44 shortName: 'DOS VGA',
45 stack: '"Perfect DOS VGA 437 Win", "Courier New", Courier, monospace'
46 }
47} as const;
48
49export type FontFamily = keyof typeof RETRO_FONTS;
50
51// Generate 256-color web-safe palette ordered by hue
52export function generate256Colors(): string[] {
53 const colors: string[] = [];
54
55 // Start with grayscale colors
56 colors.push('#000000', '#333333', '#666666', '#999999', '#CCCCCC', '#FFFFFF');
57
58 // Then add colors in a more logical order by hue
59 // Red hues
60 colors.push('#330000', '#660000', '#990000', '#CC0000', '#FF0000');
61 colors.push('#FF3333', '#FF6666', '#FF9999', '#FFCCCC');
62
63 // Orange hues
64 colors.push('#663300', '#993300', '#CC3300', '#FF3300');
65 colors.push('#FF6600', '#FF9900', '#FFCC00', '#FFCC33', '#FFCC66', '#FFCC99');
66
67 // Yellow hues
68 colors.push('#666600', '#999900', '#CCCC00', '#FFFF00');
69 colors.push('#FFFF33', '#FFFF66', '#FFFF99', '#FFFFCC');
70
71 // Green hues
72 colors.push('#003300', '#006600', '#009900', '#00CC00', '#00FF00');
73 colors.push('#33FF33', '#66FF66', '#99FF99', '#CCFFCC');
74 colors.push('#336633', '#669966', '#99CC99');
75
76 // Cyan hues
77 colors.push('#006666', '#009999', '#00CCCC', '#00FFFF');
78 colors.push('#33FFFF', '#66FFFF', '#99FFFF', '#CCFFFF');
79 colors.push('#336666', '#669999', '#99CCCC');
80
81 // Blue hues
82 colors.push('#000033', '#000066', '#000099', '#0000CC', '#0000FF');
83 colors.push('#3333FF', '#6666FF', '#9999FF', '#CCCCFF');
84 colors.push('#003366', '#006699', '#0099CC', '#3399FF', '#66CCFF');
85
86 // Purple hues
87 colors.push('#330033', '#660066', '#990099', '#CC00CC', '#FF00FF');
88 colors.push('#FF33FF', '#FF66FF', '#FF99FF', '#FFCCFF');
89 colors.push('#663366', '#996699', '#CC99CC');
90
91 // Pink hues
92 colors.push('#660033', '#990066', '#CC0099', '#FF0099');
93 colors.push('#FF3399', '#FF66CC', '#FF99CC');
94
95 // Browns
96 colors.push('#663333', '#996633', '#CC9966', '#996666');
97
98 // Now fill in any missing combinations to get full 216 web-safe colors
99 const webSafeValues = ['00', '33', '66', '99', 'CC', 'FF'];
100 const existingColors = new Set(colors);
101
102 for (const r of webSafeValues) {
103 for (const g of webSafeValues) {
104 for (const b of webSafeValues) {
105 const color = `#${r}${g}${b}`.toUpperCase();
106 if (!existingColors.has(color)) {
107 colors.push(color);
108 }
109 }
110 }
111 }
112
113 return colors;
114}
115
116// Classic retro colors for backward compatibility
117export const CLASSIC_COLORS = {
118 black: '#000000',
119 darkBlue: '#000080',
120 green: '#008000',
121 teal: '#008080',
122 darkRed: '#800000',
123 purple: '#800080',
124 brown: '#808000',
125 silver: '#C0C0C0',
126 gray: '#808080',
127 blue: '#0000FF',
128 lime: '#00FF00',
129 aqua: '#00FFFF',
130 red: '#FF0000',
131 fuchsia: '#FF00FF',
132 yellow: '#FFFF00',
133 white: '#FFFFFF'
134} as const;
135
136export type ClassicColor = keyof typeof CLASSIC_COLORS;
137
138// Gradient presets inspired by AOL faders
139export const GRADIENT_PRESETS = {
140 rainbow: [
141 '#FF0000',
142 '#FF8000',
143 '#FFFF00',
144 '#80FF00',
145 '#00FF00',
146 '#00FF80',
147 '#00FFFF',
148 '#0080FF',
149 '#0000FF',
150 '#8000FF',
151 '#FF00FF',
152 '#FF0080'
153 ],
154 fire: ['#FF0000', '#FF4000', '#FF8000', '#FFBF00', '#FFFF00'],
155 ocean: ['#000080', '#0040FF', '#0080FF', '#00BFFF', '#00FFFF'],
156 sunset: ['#FF4500', '#FF6347', '#FFD700', '#FFFF00'],
157 neon: ['#FF00FF', '#8000FF', '#0080FF', '#00FFFF'],
158 forest: ['#006400', '#228B22', '#32CD32', '#90EE90'],
159 royal: ['#4B0082', '#6A5ACD', '#9370DB', '#DDA0DD']
160} as const;
161
162export type GradientPreset = keyof typeof GRADIENT_PRESETS;
163
164// Text styling options
165export interface TextStyle {
166 fontFamily: FontFamily;
167 fontSize: number; // in pixels
168 color?: string; // hex color string
169 gradient?: string[]; // array of 1-3 hex colors for gradients
170 bold: boolean;
171 italic: boolean;
172 underline: boolean;
173 strikethrough: boolean;
174}
175
176// Default text style
177export const DEFAULT_TEXT_STYLE: TextStyle = {
178 fontFamily: 'pixelated',
179 fontSize: 14,
180 color: '#000000',
181 bold: false,
182 italic: false,
183 underline: false,
184 strikethrough: false
185};
186
187// User preferences for text formatting
188export interface UserTextPreferences {
189 defaultStyle: TextStyle;
190 allowFormatting: boolean;
191 maxMessageLength: number;
192}
193
194// Default user preferences
195export const DEFAULT_USER_PREFERENCES: UserTextPreferences = {
196 defaultStyle: DEFAULT_TEXT_STYLE,
197 allowFormatting: true,
198 maxMessageLength: 500
199};
200
201// Hex color validation
202export function isValidHexColor(color: string): boolean {
203 return /^#[0-9A-Fa-f]{6}$/.test(color);
204}
205
206// Formatting validation
207export function validateTextStyle(style: Partial<TextStyle>): TextStyle {
208 return {
209 fontFamily:
210 style.fontFamily && style.fontFamily in RETRO_FONTS
211 ? style.fontFamily
212 : DEFAULT_TEXT_STYLE.fontFamily,
213 fontSize: Math.max(8, Math.min(18, style.fontSize || DEFAULT_TEXT_STYLE.fontSize)),
214 color: style.color && isValidHexColor(style.color) ? style.color : DEFAULT_TEXT_STYLE.color,
215 gradient:
216 style.gradient &&
217 Array.isArray(style.gradient) &&
218 style.gradient.length <= 3 &&
219 style.gradient.every(isValidHexColor)
220 ? style.gradient
221 : undefined,
222 bold: Boolean(style.bold),
223 italic: Boolean(style.italic),
224 underline: Boolean(style.underline),
225 strikethrough: Boolean(style.strikethrough)
226 };
227}
228
229// Generate CSS style string from TextStyle
230export function generateCSSStyle(style: TextStyle): string {
231 const styles: string[] = [];
232
233 // Font family
234 styles.push(`font-family: ${RETRO_FONTS[style.fontFamily].stack}`);
235
236 // Font size
237 styles.push(`font-size: ${style.fontSize}px`);
238
239 // Color or gradient
240 if (style.gradient && style.gradient.length > 1) {
241 const gradientStops = style.gradient.join(', ');
242 styles.push(`background: linear-gradient(to right, ${gradientStops})`);
243 styles.push(`-webkit-background-clip: text`);
244 styles.push(`-webkit-text-fill-color: transparent`);
245 styles.push(`background-clip: text`);
246 } else if (style.color) {
247 styles.push(`color: ${style.color}`);
248 }
249
250 // Font weight
251 if (style.bold) {
252 // Use correct weight for Tahoma (200) vs standard bold (700)
253 if (style.fontFamily === 'tahoma') {
254 styles.push(`font-weight: 200`);
255 } else {
256 styles.push(`font-weight: 700`);
257 }
258 }
259
260 // Font style
261 if (style.italic) {
262 styles.push(`font-style: italic`);
263 }
264
265 // Text decoration
266 const decorations: string[] = [];
267 if (style.underline) decorations.push('underline');
268 if (style.strikethrough) decorations.push('line-through');
269 if (decorations.length > 0) {
270 styles.push(`text-decoration: ${decorations.join(' ')}`);
271 }
272
273 return styles.join('; ');
274}
275
276// Generate CSS style string for input fields (without font-family which should be handled by classes)
277export function generateInputCSSStyle(style: TextStyle): string {
278 const styles: string[] = [];
279
280 // Font size
281 styles.push(`font-size: ${style.fontSize}px`);
282
283 // Color handling for inputs
284 // IMPORTANT: Always set -webkit-text-fill-color to ensure proper color reset when switching from gradient to solid
285 if (style.gradient && style.gradient.length > 1) {
286 // Apply gradient background with text clipping for gradient effect
287 const gradientStops = style.gradient.join(', ');
288 styles.push(`background: linear-gradient(to right, ${gradientStops})`);
289 styles.push(`-webkit-background-clip: text`);
290 styles.push(`-webkit-text-fill-color: transparent`);
291 styles.push(`background-clip: text`);
292 styles.push(`letter-spacing: 0`);
293 styles.push(`word-spacing: 0`);
294 } else if (style.gradient && style.gradient.length === 1) {
295 // Single color from gradient array
296 const color = style.gradient[0];
297 styles.push(`color: ${color}`);
298 styles.push(`-webkit-text-fill-color: ${color}`);
299 } else if (style.color) {
300 styles.push(`color: ${style.color}`);
301 styles.push(`-webkit-text-fill-color: ${style.color}`);
302 } else {
303 // Fallback to black if no color is set
304 styles.push(`color: #000000`);
305 styles.push(`-webkit-text-fill-color: #000000`);
306 }
307
308 // Font weight
309 if (style.bold) {
310 // Use correct weight for Tahoma (200) vs standard bold (700)
311 if (style.fontFamily === 'tahoma') {
312 styles.push(`font-weight: 200`);
313 } else {
314 styles.push(`font-weight: 700`);
315 }
316 }
317
318 // Font style
319 if (style.italic) {
320 styles.push(`font-style: italic`);
321 }
322
323 // Text decoration
324 const decorations: string[] = [];
325 if (style.underline) decorations.push('underline');
326 if (style.strikethrough) decorations.push('line-through');
327 if (decorations.length > 0) {
328 styles.push(`text-decoration: ${decorations.join(' ')}`);
329 }
330
331 return styles.join('; ');
332}