Skip to content

Commit ebcac6c

Browse files
authored
Add files via upload
1 parent 63aad95 commit ebcac6c

File tree

2 files changed

+90
-86
lines changed

2 files changed

+90
-86
lines changed

043-country-explorer/app.js

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/* ============================================================
2-
Country Explorer — app.js (full, with robust fade-in/out)
3-
------------------------------------------------------------
2+
Country Explorer — app.js
43
- CSV load via PapaParse
54
- Filters (search, continent, language, population, GDP pc)
65
- Sort chips with null-last comparator
7-
- Dialog animations: open (.open), close (.closing) with RAF/reflow
6+
- Flags via FlagCDN (PNG) with emoji fallback
7+
- Dialog animations: .open (fade-in) / .closing (fade-out)
88
============================================================ */
99

1010
const CSV_PATH = "countries_dataset_2025_v1_0.csv";
@@ -15,6 +15,30 @@ const GDP_ALIASES = [
1515
"gdp_per_capita_current_usd"
1616
];
1717

18+
/* ----- Flag rendering: robust on Chrome with PNG from FlagCDN ----- */
19+
const USE_SVG_FLAGS = true; // set false to go emoji-only
20+
const FLAG_CDN_BASE = "https://flagcdn.com";
21+
function flagPng(iso2, w){ return `${FLAG_CDN_BASE}/w${w}/${String(iso2||"").toLowerCase()}.png`; }
22+
function renderFlag(container, iso2, emoji, size="sm"){
23+
container.innerHTML = "";
24+
if (USE_SVG_FLAGS && iso2){
25+
const img = new Image();
26+
img.alt = `${iso2} flag`;
27+
img.decoding = "async";
28+
img.loading = "lazy";
29+
const base = (size === "lg") ? 80 : 40; // pick a bucket size
30+
img.src = flagPng(iso2, base);
31+
img.srcset = [
32+
`${flagPng(iso2, Math.round(base/2))} 0.5x`,
33+
`${flagPng(iso2, base)} 1x`,
34+
`${flagPng(iso2, base*2)} 2x`
35+
].join(", ");
36+
container.appendChild(img);
37+
return;
38+
}
39+
container.textContent = emoji || "🏳️";
40+
}
41+
1842
/* DOM */
1943
const els = {
2044
search: document.getElementById("searchInput"),
@@ -56,7 +80,6 @@ let rawData = [];
5680
let data = [];
5781
let filtered = [];
5882
let gdpColumnFound = null;
59-
6083
let sortKey = "name";
6184
let sortDir = "asc";
6285

@@ -208,7 +231,7 @@ function makeCard(d){
208231

209232
const flag=document.createElement("div");
210233
flag.className="flag";
211-
flag.textContent=d.flagEmoji||"🏳️";
234+
renderFlag(flag, d.iso2, d.flagEmoji, "sm"); // <-- IMG flag (Chrome-safe)
212235

213236
const titleWrap=document.createElement("div");
214237
const title=document.createElement("div");
@@ -249,7 +272,7 @@ function makeCard(d){
249272
/* Modal — open with fade-in, close with fade-out */
250273
function openModal(d){
251274
// fill content
252-
els.modalFlag.textContent = d.flagEmoji || "🏳️";
275+
renderFlag(els.modalFlag, d.iso2, d.flagEmoji, "lg"); // <-- IMG flag in modal
253276
els.modalName.textContent = d.name;
254277
els.modalOfficial.textContent = "";
255278
els.modalCapital.textContent = d.capital || "—";
@@ -272,27 +295,17 @@ function openModal(d){
272295
if(d.isoNum) codes.push(`ISO numeric: ${d.isoNum}`);
273296
els.modalCodes.textContent = codes.join(" • ") || "—";
274297

275-
// prepare animation classes
298+
// open with fade-in: [open] first, then .open (double RAF for Safari)
276299
els.modal.classList.remove("closing","open");
277-
278-
// open dialog (adds [open] so backdrop exists)
279300
if(typeof els.modal.showModal==="function"){ els.modal.showModal(); }
280301
else { els.modal.setAttribute("open",""); }
281-
282-
// Force a reflow so the base styles (opacity 0 / scale .97) apply before adding .open
283-
// This ensures the transition runs reliably across browsers.
284-
// Using double RAF improves Safari reliability.
285-
requestAnimationFrame(()=>{ requestAnimationFrame(()=>{
286-
els.modal.classList.add("open");
287-
}); });
302+
requestAnimationFrame(()=>{ requestAnimationFrame(()=>{ els.modal.classList.add("open"); }); });
288303
}
289304

290305
function closeModalSmooth(){
291306
if(!els.modal.hasAttribute("open")) return;
292-
// remove .open to go back to base state, then add .closing (backdrop to 0)
293307
els.modal.classList.remove("open");
294308
els.modal.classList.add("closing");
295-
296309
const onEnd=(e)=>{
297310
if(e.propertyName!=="opacity") return;
298311
els.modal.removeEventListener("transitionend", onEnd);
@@ -352,7 +365,7 @@ function bindEvents(){
352365
applyFilters();
353366
});
354367

355-
// Close actions
368+
// Modal close handlers
356369
document.querySelectorAll('[value="close"]').forEach(btn=>{
357370
btn.addEventListener("click",(e)=>{ e.preventDefault(); closeModalSmooth(); });
358371
});

043-country-explorer/styles.css

Lines changed: 58 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* ============================================================
22
Country Explorer — styles.css
3-
Dark, modern, fully responsive UI with accessible contrast.
4-
Includes sort chips and a smooth fading modal (open/close).
3+
Dark, modern, responsive UI with sort chips and fading modal.
4+
Includes robust flag <img> support (Chrome-friendly).
55
============================================================ */
66

77
:root{
@@ -25,11 +25,11 @@ body{
2525
margin:0;
2626
font-family: 'Inter', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
2727
color:var(--text);
28-
background: var(--bg); /* solid background, no gradient */
28+
background: var(--bg);
2929
letter-spacing:.2px;
3030
}
3131

32-
/* Normalize button look (Reset & chips) */
32+
/* Normalize buttons */
3333
button{
3434
font: inherit;
3535
color: inherit;
@@ -90,35 +90,35 @@ button{
9090

9191
.hint{ margin-top: 6px; font-size:12px; color:var(--muted) }
9292

93-
/* Buttons (Reset) */
93+
/* Buttons */
9494
.btn{
95-
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
96-
height: 42px; padding: 0 14px;
97-
border-radius: 12px;
95+
display:inline-flex; align-items:center; justify-content:center; gap:8px;
96+
height:42px; padding:0 14px;
97+
border-radius:12px;
9898
border:1px solid #2c3858;
9999
background: linear-gradient(180deg, color-mix(in oklab, var(--brand) 22%, #0d1222), #0d1222);
100-
color: var(--text); font-weight: 600; cursor: pointer;
100+
color:var(--text); font-weight:600; cursor:pointer;
101101
transition: filter .15s ease, transform .05s ease, box-shadow .15s ease, background .2s ease;
102102
}
103-
.btn:hover{ filter: brightness(1.06) }
103+
.btn:hover{ filter:brightness(1.06) }
104104
.btn:active{ transform: translateY(1px) }
105-
.btn:focus-visible{ outline: none; box-shadow: var(--ring) }
106-
.btn-ghost{ background: transparent; border-color: #283352 }
107-
.btn-ghost:hover{ background:#12172a; filter: none }
105+
.btn:focus-visible{ outline:none; box-shadow: var(--ring) }
106+
.btn-ghost{ background:transparent; border-color:#283352 }
107+
.btn-ghost:hover{ background:#12172a; filter:none }
108108

109109
/* Sort bar & chips */
110110
.sortbar{
111-
display:flex; align-items:center; flex-wrap: wrap; gap:10px;
112-
margin-top: 10px;
111+
display:flex; align-items:center; flex-wrap:wrap; gap:10px;
112+
margin-top:10px;
113113
}
114114
.chip{
115-
position: relative;
115+
position:relative;
116116
display:inline-flex; align-items:center; gap:8px;
117117
padding:8px 12px;
118-
border-radius: 999px;
118+
border-radius:999px;
119119
background:#0e1426;
120120
border:1px solid #223056;
121-
color: var(--text);
121+
color:var(--text);
122122
cursor:pointer;
123123
transition: transform .05s ease, box-shadow .15s ease, border-color .15s ease, background .15s ease;
124124
}
@@ -129,43 +129,52 @@ button{
129129
border-color: color-mix(in oklab, var(--brand) 45%, #223056);
130130
}
131131
.chip::after{
132-
content: "";
133-
width: 0; height: 0;
134-
border-left: 5px solid transparent;
135-
border-right: 5px solid transparent;
136-
margin-left: 4px;
132+
content:"";
133+
width:0;height:0;
134+
border-left:5px solid transparent;
135+
border-right:5px solid transparent;
136+
margin-left:4px;
137137
transform: translateY(1px);
138138
}
139-
.chip.asc::after{ border-bottom: 7px solid var(--text) } /* ▲ */
140-
.chip.desc::after{ border-top: 7px solid var(--text) } /* ▼ */
139+
.chip.asc::after{ border-bottom:7px solid var(--text) } /* ▲ */
140+
.chip.desc::after{ border-top:7px solid var(--text) } /* ▼ */
141141

142142
/* Results grid */
143-
.results{ margin: 18px 0 36px }
143+
.results{ margin:18px 0 36px }
144144
.grid{
145145
display:grid;
146146
grid-template-columns: repeat( auto-fill, minmax(220px, 1fr) );
147-
gap: 14px;
147+
gap:14px;
148148
}
149149
.card{
150150
position:relative;
151151
background: linear-gradient(180deg, var(--card), #111625);
152-
border: 1px solid #212842;
153-
border-radius: var(--radius);
154-
padding: 14px;
155-
box-shadow: var(--shadow);
152+
border:1px solid #212842;
153+
border-radius:var(--radius);
154+
padding:14px;
155+
box-shadow:var(--shadow);
156156
transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
157-
cursor: pointer;
157+
cursor:pointer;
158158
}
159159
.card:hover{
160160
transform: translateY(-2px);
161161
border-color: color-mix(in oklab, var(--brand) 45%, #212842);
162162
box-shadow: 0 12px 36px -14px rgba(0,0,0,.55);
163163
}
164+
165+
/* Flag container (supports emoji or <img>) */
164166
.flag{
165-
width: 28px; height: 24px; border-radius:6px;
166-
display:grid; place-items:center; font-size:18px; background:#0f1424; border:1px solid #26304a;
167+
width:28px; height:24px; border-radius:6px;
168+
display:grid; place-items:center; font-size:18px;
169+
background:#0f1424; border:1px solid #26304a; overflow:hidden;
167170
}
168171
.flag.flag-lg{ width:52px; height:42px; font-size:28px; border-radius:10px }
172+
/* Make <img> flags fill the container nicely */
173+
.flag img{
174+
width:100%; height:100%;
175+
display:block; object-fit:cover; border-radius:inherit;
176+
}
177+
169178
.card-header{ display:flex; gap:10px; align-items:center; margin-bottom:10px }
170179
.country-name{ font-weight:700; letter-spacing:.2px }
171180
.country-capital{ color:var(--muted); font-size:12px }
@@ -176,75 +185,57 @@ button{
176185
.kpi{ display:flex; gap:6px; align-items:center; background:#0e1426; border:1px solid #223056; padding:6px 8px; border-radius:999px }
177186
.kpi strong{ color:var(--text) }
178187

179-
.empty{ margin: 28px 0; text-align:center; color:var(--muted) }
188+
.empty{ margin:28px 0; text-align:center; color:var(--muted) }
180189
.hidden{ display:none }
181190

182191
/* Footer */
183-
.site-footer{ border-top:1px solid #1f2434; margin-top: 12px; background: #0b0d13 }
192+
.site-footer{ border-top:1px solid #1f2434; margin-top:12px; background:#0b0d13 }
184193
.footer-inner{ padding:14px 0; display:flex; gap:10px; justify-content:space-between; color:var(--muted); font-size:12px }
185194
.footer-inner .buy-link{ color:var(--brand) }
186195
@media (max-width:600px){ .footer-inner{ flex-direction:column } }
187196

188-
/* ------------------------------
189-
Modal (fade-in / fade-out with classes)
190-
------------------------------ */
197+
/* Modal with fade-in/out */
191198
.modal{
192199
width: min(860px, 92vw);
193-
border: none;
194-
padding: 0;
195-
background: transparent;
196-
197-
/* animation base state */
198-
opacity: 0;
199-
transform: scale(0.97);
200-
transition: opacity 0.25s ease, transform 0.25s ease;
201-
}
202-
203-
/* when dialog is open AND we've added .open, play fade-in */
204-
.modal.open{
205-
opacity: 1;
206-
transform: scale(1);
207-
}
208-
209-
/* play fade-out before closing() */
210-
.modal.closing{
211-
opacity: 0 !important;
212-
transform: scale(0.97);
200+
border:none; padding:0; background:transparent;
201+
opacity:0; transform:scale(0.97);
202+
transition: opacity .25s ease, transform .25s ease;
213203
}
204+
.modal.open{ opacity:1; transform:scale(1) }
205+
.modal.closing{ opacity:0 !important; transform:scale(0.97) }
214206

215-
/* Backdrop animates too — it only exists while [open] is set by the browser */
216207
.modal::backdrop{
217208
background: rgba(2,6,20,0);
218209
backdrop-filter: blur(3px);
219-
transition: background 0.25s ease;
210+
transition: background .25s ease;
220211
}
221212
.modal.open::backdrop{ background: rgba(2,6,20,0.6) }
222213
.modal.closing::backdrop{ background: rgba(2,6,20,0) !important }
223214

224215
.modal-card{
225216
width:100%;
226217
background: linear-gradient(180deg, var(--panel), var(--panel-2));
227-
border: 1px solid #223056; border-radius: 20px; overflow:hidden;
218+
border:1px solid #223056; border-radius:20px; overflow:hidden;
228219
}
229220
.modal-header{
230221
display:flex; align-items:center; justify-content:space-between;
231-
padding: 14px 16px; border-bottom:1px solid #223056;
222+
padding:14px 16px; border-bottom:1px solid #223056;
232223
}
233224
.modal-flag-name{ display:flex; align-items:center; gap:12px }
234225
.modal-title{ margin:0; font-size:20px; color:var(--text) }
235226
.modal-subtitle{ color:var(--muted); font-size:12px }
236227
.icon-btn{ width:36px;height:36px;border-radius:10px;border:1px solid #2b3858;background:#0f1322;color:var(--text);cursor:pointer }
237228
.icon-btn:hover{ background:#121a30 }
238-
.modal-content{ padding: 16px }
229+
.modal-content{ padding:16px }
239230
.details-grid{ display:grid; gap:12px; grid-template-columns: repeat(2, 1fr) }
240231
@media (max-width:700px){ .details-grid{ grid-template-columns: 1fr } }
241232

242233
.detail{
243234
background:#0d1324; border:1px solid #213056; border-radius:14px; padding:12px;
244-
color: var(--text);
235+
color:var(--text);
245236
}
246237
.dt{ font-size:11px; color:var(--muted); margin-bottom:6px }
247-
.dd{ font-size:14px; color: var(--text) }
238+
.dd{ font-size:14px; color:var(--text) }
248239
.taglist{ display:flex; gap:6px; flex-wrap:wrap }
249240
.taglist .tag{ border:1px solid #2b395c; background:#0c1224; padding:4px 8px; border-radius:999px; font-size:12px; color:var(--text) }
250-
.modal-footer{ padding: 12px 16px; border-top:1px solid #223056; display:flex; justify-content:flex-end }
241+
.modal-footer{ padding:12px 16px; border-top:1px solid #223056; display:flex; justify-content:flex-end }

0 commit comments

Comments
 (0)