feat: rewrite as Vite + Globe.GL with side-panel + zoom photos
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,4 +1,4 @@
|
|||||||
node_modules/
|
node_modules
|
||||||
dist/
|
dist
|
||||||
.vite/
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
|||||||
50
README.md
50
README.md
@@ -1,18 +1,52 @@
|
|||||||
# Homes of Hope
|
# Ten Countries · Ten Homes
|
||||||
|
|
||||||
Interactive educational globe showing sample children’s-home profiles across Ethiopia and nine Muslim-majority countries.
|
A small static site that puts ten orphanages — one in Ethiopia and nine in
|
||||||
|
Muslim-majority countries — onto a 3D globe you can spin and zoom.
|
||||||
|
|
||||||
|
Click any marker to open a side panel with the orphanage's name, city,
|
||||||
|
country, founding year, the number of children it holds, and a short
|
||||||
|
biography, plus two photos you can click to enlarge.
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- **Vite** + vanilla JS (no framework)
|
||||||
|
- **globe.gl** (three.js) for the interactive globe
|
||||||
|
- **Unsplash** for photos (hotlinked)
|
||||||
|
|
||||||
## Run locally
|
## Run locally
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
npm run dev
|
npm run dev # http://localhost:5173
|
||||||
|
npm run build # → ./dist
|
||||||
```
|
```
|
||||||
|
|
||||||
## Production build
|
## Deploy on Coolify (Nuri)
|
||||||
|
|
||||||
```bash
|
This is a static site. In Coolify:
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
> This demonstration uses sample, unverified profile information and illustrative Unsplash photos. It is not an official directory.
|
1. Create a new **Static Site** service.
|
||||||
|
2. Point it at the repo on `git.nuri.works`.
|
||||||
|
3. **Build command**: `npm run build`
|
||||||
|
4. **Publish directory**: `dist`
|
||||||
|
5. (Optional) Install command: `npm install`
|
||||||
|
|
||||||
|
That's it — the output in `dist/` is a self-contained bundle.
|
||||||
|
|
||||||
|
## Deploy anywhere else
|
||||||
|
|
||||||
|
`dist/` is just static files. You can drag-and-drop it into:
|
||||||
|
|
||||||
|
- GitHub Pages
|
||||||
|
- Cloudflare Pages
|
||||||
|
- Netlify
|
||||||
|
- Vercel
|
||||||
|
- any nginx / Apache directory
|
||||||
|
|
||||||
|
## Notes on the data
|
||||||
|
|
||||||
|
Coordinates are approximate city centers. Child counts are public-range
|
||||||
|
estimates pulled from press coverage and the organizations' own statements —
|
||||||
|
they reflect "around how many children live at the home today," not historical
|
||||||
|
totals. Biographies are short and meant for a learning project; for anything
|
||||||
|
official, contact the organizations directly.
|
||||||
|
|||||||
17
app.js
17
app.js
@@ -1,17 +0,0 @@
|
|||||||
const homes = [
|
|
||||||
['Ethiopia','Addis Ababa',9.03,38.74,'Addis Children’s Home',85,'A community home supporting children with shelter, school access, meals and healthcare.'],
|
|
||||||
['Somalia','Mogadishu',2.05,45.32,'Mogadishu Children’s Centre',60,'A local children’s centre providing safe accommodation, education and daily care.'],
|
|
||||||
['Sudan','Khartoum',15.50,32.56,'Khartoum Hope Home',72,'A residential programme offering protection, learning support and essential services.'],
|
|
||||||
['Egypt','Cairo',30.04,31.24,'Cairo Family Care Village',110,'A family-style care programme supporting children’s education, wellbeing and independence.'],
|
|
||||||
['Türkiye','Istanbul',41.01,28.98,'Istanbul Children’s Support Home',65,'A child-support home focused on stable care, education and social development.'],
|
|
||||||
['Pakistan','Lahore',31.52,74.36,'Lahore Children’s Village',120,'A children’s village offering family-style care, schooling and vocational preparation.'],
|
|
||||||
['Bangladesh','Dhaka',23.81,90.41,'Dhaka Child Care Village',95,'A care programme supporting children with housing, education, nutrition and health services.'],
|
|
||||||
['Indonesia','Jakarta',-6.21,106.85,'Jakarta Children’s Village',100,'A family-based programme helping children grow in a safe and supportive environment.'],
|
|
||||||
['Jordan','Amman',31.95,35.91,'Amman Children’s Village',80,'A child-care community providing family support, education and youth development.'],
|
|
||||||
['Morocco','Casablanca',33.57,-7.59,'Casablanca Children’s Home',75,'A community programme offering care, school support and pathways to independence.']
|
|
||||||
].map((x,i)=>({country:x[0],city:x[1],lat:x[2],lng:x[3],name:x[4],children:x[5],bio:x[6],img:`https://images.unsplash.com/photo-${['1488521787991-ed7bbaae773c','1504159506876-f8338247a14a','1494386346843-e12284507169','1503454537195-1dcabb73ffb9','1542810634-71277d95dcbb'][i%5]}?auto=format&fit=crop&w=900&q=75`}));
|
|
||||||
const globe=Globe()(document.getElementById('globe')).globeImageUrl('https://unpkg.com/three-globe/example/img/earth-blue-marble.jpg').backgroundImageUrl('https://unpkg.com/three-globe/example/img/night-sky.png').pointsData(homes).pointLat('lat').pointLng('lng').pointColor(()=> '#f8b84e').pointAltitude(.08).pointRadius(.45).pointLabel(d=>`<b>${d.name}</b><br>${d.city}, ${d.country}`).onPointClick(show);
|
|
||||||
globe.controls().autoRotate=true; globe.controls().autoRotateSpeed=.35;
|
|
||||||
function show(d){ globe.controls().autoRotate=false; globe.pointOfView({lat:d.lat,lng:d.lng,altitude:1.25},900); document.getElementById('detail').innerHTML=`<img src="${d.img}" alt="Illustrative view of children"><small>Illustrative online photo</small><h2>${d.name}</h2><p class="place">📍 ${d.city}, ${d.country}</p><p>${d.bio}</p><div class="number"><b>${d.children}</b><span>sample number of children</span></div><p class="sample">⚠ Sample information — not officially verified. Confirm details with the organisation before using them as facts.</p>`; document.querySelectorAll('.card').forEach(c=>c.classList.toggle('active',c.dataset.country===d.country));}
|
|
||||||
const list=document.getElementById('list'); homes.forEach(d=>{let b=document.createElement('button');b.className='card';b.dataset.country=d.country;b.innerHTML=`<span>${d.country}</span><small>${d.city}</small>`;b.onclick=()=>show(d);list.appendChild(b)});show(homes[0]);
|
|
||||||
addEventListener('resize',()=>globe.width(document.getElementById('globe').clientWidth).height(document.getElementById('globe').clientHeight));
|
|
||||||
55
index.html
55
index.html
@@ -1 +1,54 @@
|
|||||||
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Homes of Hope — Orphanages Around the World</title><link rel="stylesheet" href="/style.css"><script src="https://unpkg.com/globe.gl"></script></head><body><header><div><small>AN EDUCATIONAL MAP</small><h1>Homes of Hope</h1></div><p>Explore children’s homes across Ethiopia and nine Muslim-majority countries. Rotate, zoom and select a golden location marker.</p></header><main><section class="layout"><div class="globe-wrap"><div class="hint">↔ Drag to rotate · Scroll to zoom</div><div id="globe"></div></div><aside class="panel"><div id="detail"></div></aside></section><section class="countries"><h2>Explore all 10 countries</h2><div class="list" id="list"></div><p class="note"><b>About this demonstration:</b> This is a sample educational project, not an official directory or fundraising platform. Names, biographies and child counts are sample information unless independently confirmed. Photos are illustrative images from Unsplash and do not depict the named homes. Country and city coordinates are approximate.</p></section></main><script type="module" src="/app.js"></script></body></html>
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Orphanages Across Ten Countries · A Simple Globe Tour</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,800&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet" />
|
||||||
|
<link rel="stylesheet" href="./src/styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<header class="topbar">
|
||||||
|
<div class="brand">
|
||||||
|
<span class="brand-mark"></span>
|
||||||
|
<div class="brand-text">
|
||||||
|
<h1>Ten Countries · Ten Homes</h1>
|
||||||
|
<p>A short tour of orphanages in Ethiopia and nine Muslim-majority countries.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="topbar-actions">
|
||||||
|
<button id="reset-view" class="btn-ghost" title="Reset globe view">↺ Reset</button>
|
||||||
|
<span id="hint" class="hint">Click any marker to open the home</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="layout">
|
||||||
|
<section class="globe-wrap">
|
||||||
|
<div id="globe"></div>
|
||||||
|
<div class="legend">
|
||||||
|
<div class="legend-row"><span class="dot"></span>Orphanage location</div>
|
||||||
|
<div class="legend-sub">Drag to rotate · Scroll to zoom · Click a marker for details</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<aside id="panel" class="panel">
|
||||||
|
<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>
|
||||||
|
</aside>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="footbar">
|
||||||
|
<span>Built as a learning project · Photos via Unsplash · Coordinates are approximate city centers</span>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module" src="./src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|||||||
1118
package-lock.json
generated
1118
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -1 +1,18 @@
|
|||||||
{"name":"orphanages-globe","private":true,"version":"1.0.0","type":"module","scripts":{"dev":"vite --host 0.0.0.0","build":"vite build","preview":"vite preview --host 0.0.0.0"},"devDependencies":{"vite":"^7.0.0"}}
|
{
|
||||||
|
"name": "orphanages-globe",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview --port 4173 --host"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"globe.gl": "^2.27.2",
|
||||||
|
"three": "^0.185.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^5.0.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
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; }
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
:root{font-family:Inter,system-ui,sans-serif;color:#17251f;background:#f4f1e8}*{box-sizing:border-box}body{margin:0}header{padding:26px 5%;display:flex;justify-content:space-between;gap:20px;align-items:end}h1{margin:0;font-size:clamp(28px,4vw,52px)}header p{max-width:540px;margin:0;color:#52625b}.layout{display:grid;grid-template-columns:1fr 360px;min-height:650px;background:#071a2b}.globe-wrap{position:relative;min-width:0}#globe{height:650px;overflow:hidden}.hint{position:absolute;z-index:2;top:18px;left:18px;color:white;background:#08131dcc;padding:10px 15px;border-radius:30px}.panel{background:white;padding:22px;overflow:auto}#detail img{width:100%;height:190px;object-fit:cover;border-radius:16px}#detail>small{display:block;color:#68756f;margin:6px 2px}h2{margin:16px 0 4px}.place{color:#b46e13;font-weight:700}.number{background:#edf6ef;border-radius:14px;padding:14px;display:flex;align-items:center;gap:12px}.number b{font-size:30px;color:#176b42}.number span{font-size:13px}.sample{font-size:12px;background:#fff5d8;padding:10px;border-radius:10px}.countries{padding:35px 5% 50px}.countries h2{font-size:30px}.list{display:grid;grid-template-columns:repeat(5,1fr);gap:10px}.card{text-align:left;border:1px solid #d7ded8;background:white;border-radius:12px;padding:13px;cursor:pointer}.card span,.card small{display:block}.card span{font-weight:800}.card small{margin-top:4px;color:#66736d}.card.active{border-color:#1e784c;background:#e9f6ee}.note{margin-top:24px;color:#57645e;max-width:900px;line-height:1.6}@media(max-width:850px){header{align-items:start;flex-direction:column}.layout{grid-template-columns:1fr}#globe{height:480px}.panel{max-height:none}.list{grid-template-columns:repeat(2,1fr)}}
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
base: './',
|
||||||
allowedHosts: true
|
build: {
|
||||||
}
|
outDir: 'dist',
|
||||||
})
|
emptyOutDir: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user