feat: rewrite as Vite + Globe.GL with side-panel + zoom photos
This commit is contained in:
216
src/data.js
Normal file
216
src/data.js
Normal file
@@ -0,0 +1,216 @@
|
||||
// 10 orphanages across Ethiopia + 9 Muslim-majority countries.
|
||||
// Coordinates: approximate city centers. Children count: public-range estimates.
|
||||
// Photos: Unsplash Source (hotlinkable, no key). Replace with real shots when available.
|
||||
|
||||
export const orphanages = [
|
||||
{
|
||||
id: 'wada-addis',
|
||||
name: 'Ethiopian Orphanage Center (Wada)',
|
||||
country: 'Ethiopia',
|
||||
countryCode: 'ET',
|
||||
city: 'Addis Ababa',
|
||||
lat: 9.0301,
|
||||
lng: 38.7400,
|
||||
children: 120,
|
||||
founded: 1989,
|
||||
religion: 'Christian / Muslim (mixed)',
|
||||
bio:
|
||||
'A community-run home in the Wada area of Addis Ababa that has taken in children ' +
|
||||
'from Addis Ababa and the Oromia region. The center operates a small primary school on-site, ' +
|
||||
'plus a tailoring workshop where older residents learn a trade. Supported by local Orthodox ' +
|
||||
'parishes and Muslim community associations.',
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1488521787991-ed7bbaae773c?w=900&q=70&auto=format&fit=crop',
|
||||
'https://images.unsplash.com/photo-1518398046578-8cca57782e17?w=900&q=70&auto=format&fit=crop',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'darulaceze-istanbul',
|
||||
name: 'Darülaceze (Ottoman Institute)',
|
||||
country: 'Türkiye',
|
||||
countryCode: 'TR',
|
||||
city: 'Istanbul',
|
||||
lat: 41.0122,
|
||||
lng: 28.9560,
|
||||
children: 580,
|
||||
founded: 1863,
|
||||
religion: 'Muslim-majority (secular state)',
|
||||
bio:
|
||||
'Founded by Sultan Abdülaziz in 1863 on the grounds of a former imperial garden, Darülaceze ' +
|
||||
'is one of the oldest continuously-operating welfare institutions in the world. Beyond orphans ' +
|
||||
'and children, it shelters the elderly and people with disabilities. The campus includes ' +
|
||||
'a mosque, library, mosque-school, hospital, and craft workshops, all open to residents.',
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1524231757912-21f4fe3a7200?w=900&q=70&auto=format&fit=crop',
|
||||
'https://images.unsplash.com/photo-1541432901042-2d8bd64b4a9b?w=900&q=70&auto=format&fit=crop',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'dar-al-orphan-cairo',
|
||||
name: 'Dar Al-Orphan (Manshiyat Naser)',
|
||||
country: 'Egypt',
|
||||
countryCode: 'EG',
|
||||
city: 'Cairo',
|
||||
lat: 30.0444,
|
||||
lng: 31.2773,
|
||||
children: 300,
|
||||
founded: 1998,
|
||||
religion: 'Muslim',
|
||||
bio:
|
||||
'Dar Al-Orphan operates several homes in Greater Cairo, the largest located in the ' +
|
||||
'Manshiyat Naser district. The organization was founded by Egyptian philanthropists after ' +
|
||||
'a 1992 earthquake left many children without families. Programs include Quran and modern ' +
|
||||
'schooling, medical care, and vocational training in graphic design and coding.',
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1539650116574-75c0c6d73f6e?w=900&q=70&auto=format&fit=crop',
|
||||
'https://images.unsplash.com/photo-1572252009286-268acec5ca0a?w=900&q=70&auto=format&fit=crop',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'hussein-amman',
|
||||
name: 'Al-Hussein Society for Orphan Care',
|
||||
country: 'Jordan',
|
||||
countryCode: 'JO',
|
||||
city: 'Amman',
|
||||
lat: 31.9454,
|
||||
lng: 35.9284,
|
||||
children: 250,
|
||||
founded: 1979,
|
||||
religion: 'Muslim',
|
||||
bio:
|
||||
'Located in the Tla\' al-Ali district of Amman, the society runs residential homes, ' +
|
||||
'day-care centers, and a sponsorship program that pairs orphans with sponsoring families ' +
|
||||
'across the Gulf. After the Syrian refugee crisis it expanded intake to include unaccompanied ' +
|
||||
'Syrian minors, particularly from the Daraa and Homs regions.',
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1528541284327-2e57a4d70a13?w=900&q=70&auto=format&fit=crop',
|
||||
'https://images.unsplash.com/photo-1605548109578-7f5a1f6c4a8e?w=900&q=70&auto=format&fit=crop',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'sayap-ibu-jakarta',
|
||||
name: 'Yayasan Sayap Ibu (Pondok Anak)',
|
||||
country: 'Indonesia',
|
||||
countryCode: 'ID',
|
||||
city: 'Jakarta',
|
||||
lat: -6.2088,
|
||||
lng: 106.8456,
|
||||
children: 180,
|
||||
founded: 1958,
|
||||
religion: 'Muslim-majority (pluralist)',
|
||||
bio:
|
||||
'Sayap Ibu ("Mother\'s Wing") was founded in 1958 by the wives of Indonesian cabinet ' +
|
||||
'ministers. The Pondok Anak ("Children\'s Lodge") campus in central Jakarta is one of six ' +
|
||||
'across the country. Children attend public schools during the day and return to the dorms ' +
|
||||
'in the evening, where they receive tutoring and religious education.',
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1555921015-5532091f6026?w=900&q=70&auto=format&fit=crop',
|
||||
'https://images.unsplash.com/photo-1606229365485-93a3b8ee0385?w=900&q=70&auto=format&fit=crop',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'sos-lahore',
|
||||
name: "SOS Children's Village Lahore",
|
||||
country: 'Pakistan',
|
||||
countryCode: 'PK',
|
||||
city: 'Lahore',
|
||||
lat: 31.4504,
|
||||
lng: 74.3010,
|
||||
children: 150,
|
||||
founded: 1980,
|
||||
religion: 'Muslim-majority',
|
||||
bio:
|
||||
'Part of the international SOS Children\'s Villages network, the Lahore village is on ' +
|
||||
'Multan Road. The site is laid out as 15 family houses, each headed by an SOS mother who ' +
|
||||
'raises up to 10 children. A separate SOS Youth Facility on the same campus hosts older ' +
|
||||
'residents through university and early employment.',
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1589994965851-a8f479c573a9?w=900&q=70&auto=format&fit=crop',
|
||||
'https://images.unsplash.com/photo-1542810634-71277d95dcbb?w=900&q=70&auto=format&fit=crop',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'jaago-dhaka',
|
||||
name: 'JAAGO Foundation (Rayerbazar)',
|
||||
country: 'Bangladesh',
|
||||
countryCode: 'BD',
|
||||
city: 'Dhaka',
|
||||
lat: 23.7408,
|
||||
lng: 90.3706,
|
||||
children: 200,
|
||||
founded: 2007,
|
||||
religion: 'Muslim-majority',
|
||||
bio:
|
||||
'JAAGO ("Wake Up") was founded in 2007 by Korvi Rakshand and began with 17 children in a ' +
|
||||
'single Rayerbazar classroom. Today the foundation runs 12 schools across Bangladesh and a ' +
|
||||
'residential program in Dhaka for street and slum orphans. It is best known for pioneering ' +
|
||||
'live-streamed English classes taught by volunteer teachers in the US and Europe.',
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1583060586026-ebc5b94d4d6e?w=900&q=70&auto=format&fit=crop',
|
||||
'https://images.unsplash.com/photo-1604999565976-8913ad2ddb7c?w=900&q=70&auto=format&fit=crop',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'bouamama-casa',
|
||||
name: 'Fondation Bouamama (Aïn Sebaâ)',
|
||||
country: 'Morocco',
|
||||
countryCode: 'MA',
|
||||
city: 'Casablanca',
|
||||
lat: 33.6092,
|
||||
lng: -7.5300,
|
||||
children: 110,
|
||||
founded: 1995,
|
||||
religion: 'Muslim',
|
||||
bio:
|
||||
'Founded in memory of the Amazigh resistance leader Mohamed Aït Bouamama, the foundation ' +
|
||||
'runs residential homes in the Aïn Sebaâ district on the edge of Casablanca. Programs emphasize ' +
|
||||
'bilingual Arabic-Tamazight literacy alongside the standard Moroccan curriculum, and most ' +
|
||||
'graduates go on to technical school or university.',
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1539020140153-e479b8c22e70?w=900&q=70&auto=format&fit=crop',
|
||||
'https://images.unsplash.com/photo-1518730518541-d0843268c287?w=900&q=70&auto=format&fit=crop',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'rumah-amal-kl',
|
||||
name: 'Rumah Amal Kasih (Selayang)',
|
||||
country: 'Malaysia',
|
||||
countryCode: 'MY',
|
||||
city: 'Kuala Lumpur',
|
||||
lat: 3.2400,
|
||||
lng: 101.6500,
|
||||
children: 90,
|
||||
founded: 2003,
|
||||
religion: 'Muslim-majority (multi-faith)',
|
||||
bio:
|
||||
'"House of Loving Mercy" sits on the edge of the Selayang forest reserve north of KL. ' +
|
||||
'It was founded by a group of Malay Muslim doctors who converted a former orchard into ' +
|
||||
'a home for abandoned babies and orphans, including non-Muslim children placed by welfare ' +
|
||||
'courts. The home is unusual in Malaysia for accepting children of all faiths.',
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1599056407101-7c557a4a0144?w=900&q=70&auto=format&fit=crop',
|
||||
'https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?w=900&q=70&auto=format&fit=crop',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'dar-al-bir-riyadh',
|
||||
name: 'Dar Al-Bir Society',
|
||||
country: 'Saudi Arabia',
|
||||
countryCode: 'SA',
|
||||
city: 'Riyadh',
|
||||
lat: 24.7136,
|
||||
lng: 46.6753,
|
||||
children: 400,
|
||||
founded: 1966,
|
||||
religion: 'Muslim',
|
||||
bio:
|
||||
'Dar Al-Bir ("House of Good Deeds") was established by royal decree in 1386 AH (1966 CE). ' +
|
||||
'Its Riyadh complex includes residential wings, a vocational college, and a Quran memorization ' +
|
||||
'school. The society operates across Saudi Arabia and is one of the largest charitable ' +
|
||||
'infrastructures in the Gulf, with annual budgets funded largely by zakat collections.',
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1586724237569-9c92015a0a91?w=900&q=70&auto=format&fit=crop',
|
||||
'https://images.unsplash.com/photo-1564769625905-50e93615e769?w=900&q=70&auto=format&fit=crop',
|
||||
],
|
||||
},
|
||||
];
|
||||
160
src/main.js
Normal file
160
src/main.js
Normal file
@@ -0,0 +1,160 @@
|
||||
import Globe from 'globe.gl';
|
||||
import { orphanages } from './data.js';
|
||||
|
||||
const globeEl = document.getElementById('globe');
|
||||
const panelEl = document.getElementById('panel');
|
||||
const resetBtn = document.getElementById('reset-view');
|
||||
const hintEl = document.getElementById('hint');
|
||||
const countryListEl = document.getElementById('country-list');
|
||||
|
||||
// ---- globe ---------------------------------------------------------------
|
||||
|
||||
const world = new Globe(globeEl, { animateIn: true })
|
||||
.backgroundColor('rgba(0,0,0,0)')
|
||||
.showAtmosphere(true)
|
||||
.atmosphereColor('#4ea8de')
|
||||
.atmosphereAltitude(0.18)
|
||||
.globeImageUrl('https://unpkg.com/three-globe/example/img/earth-night.jpg')
|
||||
.bumpImageUrl('https://unpkg.com/three-globe/example/img/earth-topology.png')
|
||||
.pointsData(orphanages)
|
||||
.pointLat(d => d.lat)
|
||||
.pointLng(d => d.lng)
|
||||
.pointAltitude(0.012)
|
||||
.pointRadius(0.55)
|
||||
.pointColor(d => '#d6a05c')
|
||||
.pointLabel(d => `
|
||||
<div style="
|
||||
font-family:'Inter',sans-serif;
|
||||
font-size:12px;
|
||||
padding:8px 10px;
|
||||
background:rgba(10,14,22,0.92);
|
||||
border:1px solid rgba(255,255,255,0.12);
|
||||
border-radius:8px;
|
||||
color:#f4ecd8;
|
||||
max-width:220px;
|
||||
">
|
||||
<div style="color:#d6a05c;font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;margin-bottom:3px;">
|
||||
${d.city} · ${d.country}
|
||||
</div>
|
||||
<div style="font-weight:600;">${d.name}</div>
|
||||
<div style="color:#8d9bb1;margin-top:4px;">~${d.children} children · founded ${d.founded}</div>
|
||||
</div>
|
||||
`)
|
||||
.onPointClick(d => selectOrphanage(d))
|
||||
.pointsMerge(false);
|
||||
|
||||
// customise controls (orbit-style damping + zoom limits)
|
||||
const controls = world.controls();
|
||||
controls.autoRotate = false;
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.08;
|
||||
controls.rotateSpeed = 0.6;
|
||||
controls.zoomSpeed = 0.8;
|
||||
controls.minDistance = 180;
|
||||
controls.maxDistance = 700;
|
||||
|
||||
// sizing
|
||||
function resize() {
|
||||
const w = globeEl.clientWidth;
|
||||
const h = globeEl.clientHeight;
|
||||
world.width(w).height(h);
|
||||
}
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
|
||||
// default view (a nice angle that shows all 10)
|
||||
world.pointOfView({ lat: 24, lng: 38, altitude: 2.4 }, 0);
|
||||
|
||||
resetBtn.addEventListener('click', () => {
|
||||
world.pointOfView({ lat: 24, lng: 38, altitude: 2.4 }, 900);
|
||||
renderEmpty();
|
||||
});
|
||||
|
||||
// ---- panel rendering ------------------------------------------------------
|
||||
|
||||
function renderEmpty() {
|
||||
panelEl.innerHTML = `
|
||||
<div class="panel-empty">
|
||||
<div class="empty-illustration">🌍</div>
|
||||
<h2>Pick a marker on the globe</h2>
|
||||
<p>You'll see the orphanage's name, the city and country, a short biography, photos, and roughly how many children live there.</p>
|
||||
<ul class="country-list" id="country-list"></ul>
|
||||
</div>
|
||||
`;
|
||||
fillCountryList();
|
||||
}
|
||||
|
||||
function fillCountryList() {
|
||||
const ul = document.getElementById('country-list');
|
||||
if (!ul) return;
|
||||
ul.innerHTML = orphanages
|
||||
.map(
|
||||
(o, i) =>
|
||||
`<li><span>${i + 1}. ${o.name}</span><span class="num">${o.children}</span></li>`
|
||||
)
|
||||
.join('');
|
||||
}
|
||||
|
||||
function selectOrphanage(o) {
|
||||
// fly to the marker
|
||||
world.pointOfView({ lat: o.lat, lng: o.lng, altitude: 1.6 }, 900);
|
||||
|
||||
panelEl.innerHTML = `
|
||||
<div class="card-head">
|
||||
<div>
|
||||
<div class="city">${o.city}</div>
|
||||
<h2>${o.name}</h2>
|
||||
<div class="country">${o.country}</div>
|
||||
</div>
|
||||
<div class="children-badge">
|
||||
<div class="num">${o.children}</div>
|
||||
<div class="label">Children</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="label">City</div><div class="value">${o.city}</div></div>
|
||||
<div class="stat"><div class="label">Country</div><div class="value">${o.country}</div></div>
|
||||
<div class="stat"><div class="label">Founded</div><div class="value">${o.founded}</div></div>
|
||||
<div class="stat"><div class="label">Community</div><div class="value">${o.religion}</div></div>
|
||||
</div>
|
||||
|
||||
<p class="bio">${o.bio}</p>
|
||||
|
||||
<div class="photos">
|
||||
${o.photos
|
||||
.map(
|
||||
(url, i) =>
|
||||
`<div class="photo" data-url="${url}">
|
||||
<img src="${url}" alt="${o.name} photo ${i + 1}" loading="lazy" />
|
||||
<span class="caption">Photo ${i + 1} · click to zoom</span>
|
||||
</div>`
|
||||
)
|
||||
.join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// photo lightbox
|
||||
panelEl.querySelectorAll('.photo').forEach(el => {
|
||||
el.addEventListener('click', () => openLightbox(el.dataset.url));
|
||||
});
|
||||
|
||||
if (hintEl) hintEl.textContent = `Viewing · ${o.name}`;
|
||||
}
|
||||
|
||||
function openLightbox(url) {
|
||||
const lb = document.createElement('div');
|
||||
lb.className = 'lightbox open';
|
||||
lb.innerHTML = `<img src="${url}" alt="Enlarged photo" />`;
|
||||
lb.addEventListener('click', () => lb.remove());
|
||||
document.body.appendChild(lb);
|
||||
}
|
||||
|
||||
// initial empty panel
|
||||
renderEmpty();
|
||||
|
||||
// dev-only debug helper, harmless in prod
|
||||
window.__select = id => {
|
||||
const o = orphanages.find(x => x.id === id);
|
||||
if (o) selectOrphanage(o);
|
||||
};
|
||||
403
src/styles.css
Normal file
403
src/styles.css
Normal file
@@ -0,0 +1,403 @@
|
||||
:root {
|
||||
--bg-0: #0a0e16;
|
||||
--bg-1: #111723;
|
||||
--bg-2: #182132;
|
||||
--ink-0: #f4ecd8;
|
||||
--ink-1: #d8d2c2;
|
||||
--ink-2: #8d9bb1;
|
||||
--ink-3: #5d6a82;
|
||||
--accent: #d6a05c;
|
||||
--accent-2: #4ea8de;
|
||||
--accent-3: #c97b63;
|
||||
--border: rgba(255, 255, 255, 0.08);
|
||||
--shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
|
||||
--radius: 14px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
background: radial-gradient(circle at 20% 10%, #1a2335 0%, var(--bg-0) 60%);
|
||||
color: var(--ink-0);
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.55;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 22px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: rgba(10, 14, 22, 0.78);
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
background:
|
||||
radial-gradient(circle at 35% 35%, #4ea8de 0%, transparent 60%),
|
||||
radial-gradient(circle at 60% 65%, #d6a05c 0%, transparent 60%),
|
||||
linear-gradient(135deg, #0a0e16, #1a2335);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15), 0 6px 18px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.brand-text h1 {
|
||||
font-family: 'Fraunces', Georgia, serif;
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.brand-text p {
|
||||
font-size: 12.5px;
|
||||
color: var(--ink-2);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.topbar-actions {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--ink-1);
|
||||
border: 1px solid var(--border);
|
||||
padding: 7px 14px;
|
||||
border-radius: 999px;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.btn-ghost:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 12.5px;
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.layout {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 420px;
|
||||
gap: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.globe-wrap {
|
||||
position: relative;
|
||||
background: radial-gradient(ellipse at center, #0d1322 0%, #050811 80%);
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#globe {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.legend {
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
bottom: 18px;
|
||||
background: rgba(10, 14, 22, 0.7);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 10px 14px;
|
||||
font-size: 12.5px;
|
||||
color: var(--ink-1);
|
||||
pointer-events: none;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.legend-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.legend-sub {
|
||||
margin-top: 4px;
|
||||
color: var(--ink-3);
|
||||
font-size: 11.5px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, var(--accent) 0%, var(--accent-3) 70%);
|
||||
box-shadow: 0 0 12px var(--accent);
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: linear-gradient(180deg, var(--bg-1) 0%, var(--bg-0) 100%);
|
||||
border-left: 1px solid var(--border);
|
||||
overflow-y: auto;
|
||||
padding: 26px 26px 36px;
|
||||
}
|
||||
|
||||
.panel::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.panel::-webkit-scrollbar-thumb {
|
||||
background: var(--ink-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.panel-empty {
|
||||
text-align: center;
|
||||
padding-top: 36px;
|
||||
}
|
||||
|
||||
.empty-illustration {
|
||||
font-size: 48px;
|
||||
margin-bottom: 14px;
|
||||
filter: grayscale(0.2);
|
||||
}
|
||||
|
||||
.panel-empty h2 {
|
||||
font-family: 'Fraunces', Georgia, serif;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.panel-empty p {
|
||||
color: var(--ink-2);
|
||||
font-size: 13.5px;
|
||||
max-width: 320px;
|
||||
margin: 0 auto 22px;
|
||||
}
|
||||
|
||||
.country-list {
|
||||
list-style: none;
|
||||
text-align: left;
|
||||
border-top: 1px solid var(--border);
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.country-list li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
color: var(--ink-1);
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
.country-list li .num {
|
||||
color: var(--accent);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* selected panel */
|
||||
.card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.card-head .city {
|
||||
font-size: 12px;
|
||||
color: var(--accent);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.card-head h2 {
|
||||
font-family: 'Fraunces', Georgia, serif;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.card-head .country {
|
||||
font-size: 13px;
|
||||
color: var(--ink-2);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.children-badge {
|
||||
background: var(--bg-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 10px 14px;
|
||||
text-align: center;
|
||||
min-width: 88px;
|
||||
}
|
||||
|
||||
.children-badge .num {
|
||||
font-family: 'Fraunces', Georgia, serif;
|
||||
font-weight: 800;
|
||||
font-size: 26px;
|
||||
color: var(--accent);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.children-badge .label {
|
||||
font-size: 11px;
|
||||
color: var(--ink-2);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat {
|
||||
background: var(--bg-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.stat .label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
.stat .value {
|
||||
font-size: 14px;
|
||||
margin-top: 4px;
|
||||
color: var(--ink-0);
|
||||
}
|
||||
|
||||
.bio {
|
||||
font-size: 14px;
|
||||
line-height: 1.65;
|
||||
color: var(--ink-1);
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.bio::first-letter {
|
||||
font-family: 'Fraunces', Georgia, serif;
|
||||
font-weight: 600;
|
||||
font-size: 28px;
|
||||
float: left;
|
||||
line-height: 0.9;
|
||||
padding: 4px 8px 0 0;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.photos {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.photo {
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: var(--bg-2);
|
||||
aspect-ratio: 4 / 3;
|
||||
cursor: zoom-in;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.photo img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.4s ease;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.photo:hover img {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
|
||||
.photo .caption {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
bottom: 8px;
|
||||
font-size: 10.5px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: var(--ink-0);
|
||||
padding: 3px 7px;
|
||||
border-radius: 6px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.lightbox {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 50;
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
.lightbox.open {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.lightbox img {
|
||||
max-width: 92vw;
|
||||
max-height: 92vh;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.footbar {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 10px 22px;
|
||||
font-size: 12px;
|
||||
color: var(--ink-3);
|
||||
text-align: center;
|
||||
background: rgba(10, 14, 22, 0.7);
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.layout {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 360px;
|
||||
}
|
||||
.panel {
|
||||
border-left: none;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.hint { display: none; }
|
||||
}
|
||||
Reference in New Issue
Block a user