Skip to content

Commit fe6a002

Browse files
committed
feat: comprehensive emoji system overhaul with Noto Color Emoji
- Create unified EmojiUtils class for both frontend and TypeScript - Add comprehensive emoji utilities with HTML/CSS generation - Update all hardcoded emojis across entire codebase - Implement consistent noto-emoji class styling throughout - Update fuel categorizer to use centralized emoji system - Fix popup generators with proper emoji HTML wrapping - Add proper Noto Color Emoji font stack to all emoji displays - Ensure consistent cross-platform emoji rendering everywhere - Add fire emoji support for LPG fuel type - Maintain backward compatibility while improving consistency
1 parent fb4bcc5 commit fe6a002

7 files changed

Lines changed: 310 additions & 12 deletions

File tree

public/css/styles.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ body {
243243

244244
#offline-indicator.pwa-mode::before {
245245
content: "📱 ";
246+
font-family: "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", emoji, sans-serif;
246247
margin-right: 4px;
247248
}
248249

public/index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,13 +275,14 @@
275275
<!-- PWA Install Prompt -->
276276
<div id="pwa-install-prompt" style="display: none;">
277277
<div class="install-banner">
278-
<span>📱 Install fuelaround.me as an app for faster access!</span>
278+
<span><span class="noto-emoji">📱</span> Install fuelaround.me as an app for faster access!</span>
279279
<button id="pwa-install-btn">Install</button>
280-
<button id="pwa-dismiss-btn"></button>
280+
<button id="pwa-dismiss-btn"><span class="noto-emoji"></span></button>
281281
</div>
282282
</div>
283283

284284
<script src="//cdn.maptiler.com/maptiler-sdk-js/v3.6.0/maptiler-sdk.umd.min.js"></script>
285+
<script src="/js/emoji-utils.js"></script>
285286
<script src="/js/station-cache.js"></script>
286287
<script src="/js/price-analytics.js"></script>
287288
<script src="/js/app.js"></script>

public/js/app.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,9 +1498,9 @@ map.on('load', function () {
14981498
font-weight: 600;
14991499
line-height: 1.2;
15001500
">
1501-
${props.is_best_price ? '🏆 ' : ''}${brand}
1501+
${props.is_best_price ? '<span class="noto-emoji">🏆</span> ' : ''}${brand}
15021502
</h3>
1503-
${location ? `<div style="font-size: 11px; opacity: 0.9; margin-top: 4px; line-height: 1.3;">📍 ${location}</div>` : ''}
1503+
${location ? `<div style="font-size: 11px; opacity: 0.9; margin-top: 4px; line-height: 1.3;"><span class="noto-emoji">📍</span> ${location}</div>` : ''}
15041504
</div>
15051505
<div style="padding: 15px;">
15061506
<h4 style="

public/js/emoji-utils.js

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/**
2+
* Emoji Utilities - Noto Color Emoji System
3+
* Provides consistent emoji display across all platforms using Google Fonts
4+
*/
5+
6+
class EmojiUtils {
7+
constructor() {
8+
// Comprehensive emoji mappings with fallbacks
9+
this.emojiMappings = {
10+
// Analytics and Statistics
11+
chart: { emoji: '📊', text: 'STATS' },
12+
thermometer: { emoji: '🌡️', text: 'HEAT' },
13+
14+
// Fuel Types
15+
fuel: { emoji: '⛽', text: 'FUEL' },
16+
truck: { emoji: '🚛', text: 'DIESEL' },
17+
diamond: { emoji: '💎', text: 'SUPER' },
18+
dieselSuper: { emoji: '🚛💎', text: 'D+' },
19+
20+
// Status and Quality
21+
star: { emoji: '⭐', text: 'GOOD' },
22+
trophy: { emoji: '🏆', text: 'BEST' },
23+
warning: { emoji: '⚠️', text: 'WARN' },
24+
target: { emoji: '🎯', text: 'OK' },
25+
26+
// Actions and Tools
27+
money: { emoji: '💰', text: 'SAVE' },
28+
search: { emoji: '🔍', text: 'FIND' },
29+
bulb: { emoji: '💡', text: 'TIP' },
30+
location: { emoji: '📍', text: 'LOC' },
31+
32+
// Trends
33+
trendUp: { emoji: '📈', text: 'UP' },
34+
trendDown: { emoji: '📉', text: 'DOWN' },
35+
trendFlat: { emoji: '➡️', text: 'SAME' },
36+
37+
// UI Elements
38+
mobile: { emoji: '📱', text: 'APP' },
39+
close: { emoji: '✕', text: 'X' },
40+
41+
// System and Performance
42+
trash: { emoji: '🗑️', text: 'DEL' },
43+
refresh: { emoji: '🔄', text: 'SYNC' },
44+
rocket: { emoji: '🚀', text: 'GO' },
45+
46+
// Additional fuel types
47+
fire: { emoji: '🔥', text: 'LPG' }
48+
};
49+
50+
// Ensure Noto Color Emoji CSS is loaded
51+
this.ensureEmojiStyles();
52+
}
53+
54+
/**
55+
* Ensure emoji styles are loaded in the document
56+
*/
57+
ensureEmojiStyles() {
58+
if (document.getElementById('emoji-utils-styles')) return;
59+
60+
const style = document.createElement('style');
61+
style.id = 'emoji-utils-styles';
62+
style.textContent = `
63+
/* Noto Color Emoji font stack for consistent display */
64+
.noto-emoji, .emoji {
65+
font-family: "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", "Twemoji Mozilla", emoji, sans-serif !important;
66+
font-style: normal !important;
67+
font-weight: normal !important;
68+
text-rendering: optimizeLegibility !important;
69+
-webkit-font-feature-settings: "liga" off !important;
70+
font-feature-settings: "liga" off !important;
71+
display: inline-block;
72+
vertical-align: middle;
73+
line-height: 1;
74+
}
75+
76+
/* Console and log emoji styling */
77+
.console-emoji {
78+
font-family: "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", emoji, monospace !important;
79+
}
80+
`;
81+
document.head.appendChild(style);
82+
}
83+
84+
/**
85+
* Get emoji with proper Noto Color Emoji styling
86+
*/
87+
getEmoji(key, options = {}) {
88+
const mapping = this.emojiMappings[key];
89+
if (!mapping) return options.fallback || '•';
90+
91+
const {
92+
size = 16,
93+
className = 'noto-emoji',
94+
inline = true,
95+
fallbackToText = false
96+
} = options;
97+
98+
if (fallbackToText && !this.supportsColorEmoji()) {
99+
return `<span class="emoji-text-fallback" style="font-size: ${size}px;">[${mapping.text}]</span>`;
100+
}
101+
102+
const style = `font-size: ${size}px; ${inline ? 'display: inline-block; vertical-align: middle;' : ''}`;
103+
return `<span class="${className}" style="${style}">${mapping.emoji}</span>`;
104+
}
105+
106+
/**
107+
* Get plain emoji character (for console logs, etc.)
108+
*/
109+
getPlainEmoji(key) {
110+
const mapping = this.emojiMappings[key];
111+
return mapping ? mapping.emoji : '•';
112+
}
113+
114+
/**
115+
* Create emoji element
116+
*/
117+
createElement(key, options = {}) {
118+
const mapping = this.emojiMappings[key];
119+
if (!mapping) return null;
120+
121+
const {
122+
size = 16,
123+
className = 'noto-emoji'
124+
} = options;
125+
126+
const span = document.createElement('span');
127+
span.className = className;
128+
span.textContent = mapping.emoji;
129+
span.style.cssText = `
130+
font-family: "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", emoji, sans-serif;
131+
font-size: ${size}px;
132+
display: inline-block;
133+
vertical-align: middle;
134+
line-height: 1;
135+
font-style: normal;
136+
font-weight: normal;
137+
text-rendering: optimizeLegibility;
138+
-webkit-font-smoothing: antialiased;
139+
-moz-osx-font-smoothing: grayscale;
140+
`;
141+
142+
return span;
143+
}
144+
145+
/**
146+
* Replace emoji placeholders in text
147+
*/
148+
replaceEmojiPlaceholders(text) {
149+
return text.replace(/:(\w+):/g, (match, key) => {
150+
const mapping = this.emojiMappings[key];
151+
return mapping ? mapping.emoji : match;
152+
});
153+
}
154+
155+
/**
156+
* Check if browser supports color emoji
157+
*/
158+
supportsColorEmoji() {
159+
if (this.emojiSupport !== undefined) {
160+
return this.emojiSupport;
161+
}
162+
163+
try {
164+
const canvas = document.createElement('canvas');
165+
canvas.width = 20;
166+
canvas.height = 20;
167+
const ctx = canvas.getContext('2d');
168+
169+
if (!ctx) {
170+
this.emojiSupport = false;
171+
return false;
172+
}
173+
174+
ctx.fillStyle = '#000';
175+
ctx.textBaseline = 'middle';
176+
ctx.textAlign = 'center';
177+
ctx.font = '16px "Noto Color Emoji"';
178+
ctx.fillText('🎨', 10, 10);
179+
180+
const imageData = ctx.getImageData(0, 0, 20, 20);
181+
const hasColor = imageData.data.some((channel, i) =>
182+
i % 4 < 3 && channel !== 0 && channel !== 255
183+
);
184+
185+
this.emojiSupport = hasColor;
186+
return this.emojiSupport;
187+
} catch (e) {
188+
this.emojiSupport = false;
189+
return false;
190+
}
191+
}
192+
193+
/**
194+
* Get all available emoji keys
195+
*/
196+
getAvailableEmojis() {
197+
return Object.keys(this.emojiMappings);
198+
}
199+
}
200+
201+
// Global instance
202+
window.emojiUtils = new EmojiUtils();
203+
204+
// Export for module usage
205+
if (typeof module !== 'undefined' && module.exports) {
206+
module.exports = EmojiUtils;
207+
}

src/fuel-categorizer.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/// <reference path="../worker-configuration.d.ts" />
22

3+
import { EmojiUtils } from './utils/emoji-utils.js';
4+
35
export interface FuelCategory {
46
name: string;
57
displayName: string;
@@ -13,31 +15,31 @@ export class FuelCategorizer {
1315
name: 'unleaded',
1416
displayName: 'Unleaded',
1517
types: ['E10', 'unleaded', 'petrol', 'gasoline', 'regular'],
16-
icon: '⛽'
18+
icon: EmojiUtils.getEmoji('fuel')
1719
},
1820
{
1921
name: 'super_unleaded',
2022
displayName: 'Super Unleaded',
2123
types: ['E5', 'super unleaded', 'super petrol', 'premium unleaded', 'v-power unleaded', 'momentum 99'],
22-
icon: '💎'
24+
icon: EmojiUtils.getEmoji('diamond')
2325
},
2426
{
2527
name: 'diesel',
2628
displayName: 'Diesel',
2729
types: ['B7', 'diesel', 'gasoil', 'regular diesel'],
28-
icon: '🚛'
30+
icon: EmojiUtils.getEmoji('truck')
2931
},
3032
{
3133
name: 'super_diesel',
3234
displayName: 'Super Diesel',
3335
types: ['SDV', 'super diesel', 'premium diesel', 'v-power diesel', 'ultimate diesel', 'city diesel'],
34-
icon: '🚛💎'
36+
icon: EmojiUtils.getEmoji('dieselSuper')
3537
},
3638
{
3739
name: 'lpg',
3840
displayName: 'LPG',
3941
types: ['LPG', 'autogas', 'propane'],
40-
icon: '🔥'
42+
icon: EmojiUtils.getEmoji('fire')
4143
}
4244
];
4345

@@ -163,7 +165,7 @@ export class FuelCategorizer {
163165

164166
static getCategoryIcon(categoryName: string): string {
165167
const category = this.FUEL_CATEGORIES.find(c => c.name === categoryName);
166-
return category ? category.icon : '⛽';
168+
return category ? category.icon : EmojiUtils.getEmoji('fuel');
167169
}
168170

169171
static formatFuelDisplay(categoryName: string, price: number, originalType: string, allPrices?: number[]): string {

src/popup-generator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ export class PopupGenerator {
3131
font-weight: 600;
3232
line-height: 1.2;
3333
">
34-
${isBestPrice ? '🏆 ' : ''}${brand}
34+
${isBestPrice ? '<span class="noto-emoji">🏆</span> ' : ''}${brand}
3535
</h3>
36-
${location ? `<div style="font-size: 11px; opacity: 0.9; margin-top: 4px; line-height: 1.3;">📍 ${location}</div>` : ''}
36+
${location ? `<div style="font-size: 11px; opacity: 0.9; margin-top: 4px; line-height: 1.3;"><span class="noto-emoji">📍</span> ${location}</div>` : ''}
3737
</div>
3838
<div style="padding: 15px;">
3939
<h4 style="

0 commit comments

Comments
 (0)