Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fbf0ebd614 | |||
| 0daff4a0f3 | |||
| e46418acfc | |||
| 0a3b7a9049 | |||
| 79a7eada6a | |||
| 8911d1525e | |||
| 768cbf9151 | |||
| 6702cbc342 | |||
| 85db756e43 | |||
| 25a67e43ef | |||
| 90719f2e16 | |||
| 4ee8757577 | |||
| 1cd6764078 | |||
| fc172cfa36 | |||
| 58ab00f08d | |||
| 4dd4317d66 | |||
| fe07f166cf | |||
| fba47c142c | |||
| 3ee3ffeb17 |
6
.gitignore
vendored
@@ -43,6 +43,11 @@ Temporary Items
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
# Backup files
|
||||||
|
*.bak
|
||||||
|
*.bak.*
|
||||||
|
*.tmp
|
||||||
|
|
||||||
# Svelte related
|
# Svelte related
|
||||||
.svelte-kit/
|
.svelte-kit/
|
||||||
|
|
||||||
@@ -56,3 +61,4 @@ Temporary Items
|
|||||||
# Make favicon generation script executable
|
# Make favicon generation script executable
|
||||||
chmod +x ./scripts/generate-favicons.js
|
chmod +x ./scripts/generate-favicons.js
|
||||||
chmod +x ./scripts/update-data.js
|
chmod +x ./scripts/update-data.js
|
||||||
|
chmod +x ./scripts/*
|
||||||
|
|||||||
132
CLAUDE.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is a Svelte-based logo gallery and quiz game application that displays company/brand logos and provides interactive games like flag quizzes. The project uses Docker for development and deploys to GitHub Pages.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Docker-based Development (Recommended)
|
||||||
|
- `make dev` - Start development server with live reload at http://localhost:5006
|
||||||
|
- `make build` - Build the Docker container
|
||||||
|
- `make start` - Start application in background
|
||||||
|
- `make stop` - Stop the application
|
||||||
|
- `make restart` - Restart the application
|
||||||
|
- `make logs` - View application logs
|
||||||
|
- `make run CMD="command"` - Run any command inside the container
|
||||||
|
|
||||||
|
### Data Management
|
||||||
|
- `make update-data` - Scan logos directory and regenerate data files
|
||||||
|
- `npm run update-data` - Same as above, but run directly (inside container)
|
||||||
|
|
||||||
|
### Asset Generation
|
||||||
|
- `make favicon` - Generate favicon variants
|
||||||
|
- `npm run generate-variants` - Generate SVG color variants
|
||||||
|
- `npm run generate-favicons` - Generate favicon files
|
||||||
|
- `npm run pwa-cache-list` - Generate PWA cache manifest
|
||||||
|
|
||||||
|
### Build Commands
|
||||||
|
- `npm run build` - Build production bundle using Rollup
|
||||||
|
- `npm run dev` - Development build with live reload
|
||||||
|
- `npm start` - Start sirv server on port 5006
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Tech Stack
|
||||||
|
- **Frontend**: Svelte 3.59.2 with SPA routing
|
||||||
|
- **Bundler**: Rollup with plugins for Svelte, CSS, and minification
|
||||||
|
- **Routing**: svelte-spa-router for single-page navigation
|
||||||
|
- **Styling**: CSS with theme support (light/dark/system)
|
||||||
|
- **Development**: Docker with live reload
|
||||||
|
|
||||||
|
### Key Application Structure
|
||||||
|
|
||||||
|
#### Main App (`src/App.svelte`)
|
||||||
|
- Central state management through `window.appData` global object
|
||||||
|
- Handles routing, theme management, and data collection switching
|
||||||
|
- Manages search, filtering (tags, brands, variants), and view modes
|
||||||
|
- Supports multiple collections (logos, flags) via dynamic data loading
|
||||||
|
|
||||||
|
#### Pages (`src/pages/`)
|
||||||
|
- `Home.svelte` - Main logo gallery with grid/list/compact views
|
||||||
|
- `Game.svelte` - Game selection landing page
|
||||||
|
- `FlagQuiz.svelte` - Flag identification quiz with adaptive learning
|
||||||
|
- `CapitalsQuiz.svelte` - Country capitals quiz
|
||||||
|
- `GeographyQuiz.svelte` - Geography-based quiz game
|
||||||
|
- `Preview.svelte` - Individual logo preview modal
|
||||||
|
|
||||||
|
#### Components (`src/components/`)
|
||||||
|
- `CardFull.svelte` - Full logo display card with actions
|
||||||
|
- `Header.svelte` - Navigation and search interface
|
||||||
|
- `Actions.svelte` - Action buttons for copy/download
|
||||||
|
- `Achievements.svelte` - Quiz achievement system
|
||||||
|
- Various card sizes (`CardSmall`, `CardMiddle`) for different views
|
||||||
|
|
||||||
|
#### Data Flow
|
||||||
|
- Logo data loaded from JSON files in `public/data/` (logos.json, flags.json)
|
||||||
|
- Collections switchable via dropdown, stored in localStorage
|
||||||
|
- Global state shared via `window.appData` object
|
||||||
|
- Theme persistence with system preference detection
|
||||||
|
- URL-based state for search/filter sharing
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
|
||||||
|
#### Multi-Collection Support
|
||||||
|
- Supports different data collections (logos, flags)
|
||||||
|
- Collection switching triggers data reload and state reset
|
||||||
|
- Each collection has its own data file structure
|
||||||
|
|
||||||
|
#### Advanced Filtering
|
||||||
|
- Text search across name, title, brand, and metadata
|
||||||
|
- Tag-based filtering with colored tags
|
||||||
|
- Brand filtering for logo variants
|
||||||
|
- Variant filtering (different logo styles)
|
||||||
|
- Compact mode to show unique brands only
|
||||||
|
|
||||||
|
#### Theme System
|
||||||
|
- Light/dark/system theme options
|
||||||
|
- CSS custom properties for theme switching
|
||||||
|
- Persistent theme preferences
|
||||||
|
|
||||||
|
#### Quiz System
|
||||||
|
- Adaptive learning algorithms
|
||||||
|
- Achievement tracking
|
||||||
|
- Score persistence
|
||||||
|
- Multiple quiz types with shared logic in `src/quizLogic/`
|
||||||
|
|
||||||
|
## File Structure Conventions
|
||||||
|
|
||||||
|
### Static Assets
|
||||||
|
- `public/logos/` - Logo files (SVG, PNG)
|
||||||
|
- `public/data/` - JSON data files generated from asset scanning
|
||||||
|
- `public/build/` - Compiled JS/CSS output (generated)
|
||||||
|
|
||||||
|
### Scripts (`scripts/`)
|
||||||
|
- `update-data.js` - Scans asset directories and generates JSON manifests
|
||||||
|
- `generate-svg-variants.js` - Creates color variants of SVG logos
|
||||||
|
- `generate-favicons.js` - Generates favicon files from source images
|
||||||
|
- `cleanup_worldmap.py` - SVG cleanup utilities
|
||||||
|
|
||||||
|
### Development Files
|
||||||
|
- `rollup.config.js` - Bundler configuration with dev/prod modes
|
||||||
|
- `Makefile` - Docker development commands
|
||||||
|
- `compose.dev.yml` - Docker Compose configuration
|
||||||
|
- `Dockerfile.dev` - Development container setup
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
1. **Adding New Logos**: Place files in `public/logos/`, run `make update-data`
|
||||||
|
2. **UI Changes**: Edit Svelte files in `src/`, changes auto-reload in dev mode
|
||||||
|
3. **Asset Changes**: Regenerate variants with `npm run generate-variants`
|
||||||
|
4. **Deployment**: Push to `main` branch triggers automatic GitHub Pages deployment
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- All development should use Docker containers for consistency
|
||||||
|
- The app uses a global `window.appData` object for component communication
|
||||||
|
- Theme changes require CSS custom property updates
|
||||||
|
- Data files are auto-generated - don't edit JSON files directly
|
||||||
|
- Quiz logic is modular and shared between different quiz types
|
||||||
|
- SVG logos support automatic color variant generation
|
||||||
248
public/data/ISO3166-1.json
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
{
|
||||||
|
"AF": "Afghanistan",
|
||||||
|
"AX": "Aland Islands",
|
||||||
|
"AL": "Albania",
|
||||||
|
"DZ": "Algeria",
|
||||||
|
"AS": "American Samoa",
|
||||||
|
"AD": "Andorra",
|
||||||
|
"AO": "Angola",
|
||||||
|
"AI": "Anguilla",
|
||||||
|
"AQ": "Antarctica",
|
||||||
|
"AG": "Antigua And Barbuda",
|
||||||
|
"AR": "Argentina",
|
||||||
|
"AM": "Armenia",
|
||||||
|
"AW": "Aruba",
|
||||||
|
"AU": "Australia",
|
||||||
|
"AT": "Austria",
|
||||||
|
"AZ": "Azerbaijan",
|
||||||
|
"BS": "Bahamas",
|
||||||
|
"BH": "Bahrain",
|
||||||
|
"BD": "Bangladesh",
|
||||||
|
"BB": "Barbados",
|
||||||
|
"BY": "Belarus",
|
||||||
|
"BE": "Belgium",
|
||||||
|
"BZ": "Belize",
|
||||||
|
"BJ": "Benin",
|
||||||
|
"BM": "Bermuda",
|
||||||
|
"BT": "Bhutan",
|
||||||
|
"BO": "Bolivia",
|
||||||
|
"BA": "Bosnia And Herzegovina",
|
||||||
|
"BW": "Botswana",
|
||||||
|
"BV": "Bouvet Island",
|
||||||
|
"BR": "Brazil",
|
||||||
|
"IO": "British Indian Ocean Territory",
|
||||||
|
"BN": "Brunei Darussalam",
|
||||||
|
"BG": "Bulgaria",
|
||||||
|
"BF": "Burkina Faso",
|
||||||
|
"BI": "Burundi",
|
||||||
|
"KH": "Cambodia",
|
||||||
|
"CM": "Cameroon",
|
||||||
|
"CA": "Canada",
|
||||||
|
"CV": "Cape Verde",
|
||||||
|
"KY": "Cayman Islands",
|
||||||
|
"CF": "Central African Republic",
|
||||||
|
"TD": "Chad",
|
||||||
|
"CL": "Chile",
|
||||||
|
"CN": "China",
|
||||||
|
"CX": "Christmas Island",
|
||||||
|
"CC": "Cocos (Keeling) Islands",
|
||||||
|
"CO": "Colombia",
|
||||||
|
"KM": "Comoros",
|
||||||
|
"CG": "Congo",
|
||||||
|
"CD": "Congo, Democratic Republic",
|
||||||
|
"CK": "Cook Islands",
|
||||||
|
"CR": "Costa Rica",
|
||||||
|
"CI": "Cote D\"Ivoire",
|
||||||
|
"HR": "Croatia",
|
||||||
|
"CU": "Cuba",
|
||||||
|
"CY": "Cyprus",
|
||||||
|
"CZ": "Czech Republic",
|
||||||
|
"DK": "Denmark",
|
||||||
|
"DJ": "Djibouti",
|
||||||
|
"DM": "Dominica",
|
||||||
|
"DO": "Dominican Republic",
|
||||||
|
"EC": "Ecuador",
|
||||||
|
"EG": "Egypt",
|
||||||
|
"SV": "El Salvador",
|
||||||
|
"GQ": "Equatorial Guinea",
|
||||||
|
"ER": "Eritrea",
|
||||||
|
"EE": "Estonia",
|
||||||
|
"ET": "Ethiopia",
|
||||||
|
"FK": "Falkland Islands (Malvinas)",
|
||||||
|
"FO": "Faroe Islands",
|
||||||
|
"FJ": "Fiji",
|
||||||
|
"FI": "Finland",
|
||||||
|
"FR": "France",
|
||||||
|
"GF": "French Guiana",
|
||||||
|
"PF": "French Polynesia",
|
||||||
|
"TF": "French Southern Territories",
|
||||||
|
"GA": "Gabon",
|
||||||
|
"GM": "Gambia",
|
||||||
|
"GE": "Georgia",
|
||||||
|
"DE": "Germany",
|
||||||
|
"GH": "Ghana",
|
||||||
|
"GI": "Gibraltar",
|
||||||
|
"GR": "Greece",
|
||||||
|
"GL": "Greenland",
|
||||||
|
"GD": "Grenada",
|
||||||
|
"GP": "Guadeloupe",
|
||||||
|
"GU": "Guam",
|
||||||
|
"GT": "Guatemala",
|
||||||
|
"GG": "Guernsey",
|
||||||
|
"GN": "Guinea",
|
||||||
|
"GW": "Guinea-Bissau",
|
||||||
|
"GY": "Guyana",
|
||||||
|
"HT": "Haiti",
|
||||||
|
"HM": "Heard Island & Mcdonald Islands",
|
||||||
|
"VA": "Holy See (Vatican City State)",
|
||||||
|
"HN": "Honduras",
|
||||||
|
"HK": "Hong Kong",
|
||||||
|
"HU": "Hungary",
|
||||||
|
"IS": "Iceland",
|
||||||
|
"IN": "India",
|
||||||
|
"ID": "Indonesia",
|
||||||
|
"IR": "Iran, Islamic Republic Of",
|
||||||
|
"IQ": "Iraq",
|
||||||
|
"IE": "Ireland",
|
||||||
|
"IM": "Isle Of Man",
|
||||||
|
"IL": "Israel",
|
||||||
|
"IT": "Italy",
|
||||||
|
"JM": "Jamaica",
|
||||||
|
"JP": "Japan",
|
||||||
|
"JE": "Jersey",
|
||||||
|
"JO": "Jordan",
|
||||||
|
"KZ": "Kazakhstan",
|
||||||
|
"KE": "Kenya",
|
||||||
|
"KI": "Kiribati",
|
||||||
|
"KR": "Korea",
|
||||||
|
"KP": "North Korea",
|
||||||
|
"KW": "Kuwait",
|
||||||
|
"KG": "Kyrgyzstan",
|
||||||
|
"LA": "Lao People\"s Democratic Republic",
|
||||||
|
"LV": "Latvia",
|
||||||
|
"LB": "Lebanon",
|
||||||
|
"LS": "Lesotho",
|
||||||
|
"LR": "Liberia",
|
||||||
|
"LY": "Libyan Arab Jamahiriya",
|
||||||
|
"LI": "Liechtenstein",
|
||||||
|
"LT": "Lithuania",
|
||||||
|
"LU": "Luxembourg",
|
||||||
|
"MO": "Macao",
|
||||||
|
"MK": "Macedonia",
|
||||||
|
"MG": "Madagascar",
|
||||||
|
"MW": "Malawi",
|
||||||
|
"MY": "Malaysia",
|
||||||
|
"MV": "Maldives",
|
||||||
|
"ML": "Mali",
|
||||||
|
"MT": "Malta",
|
||||||
|
"MH": "Marshall Islands",
|
||||||
|
"MQ": "Martinique",
|
||||||
|
"MR": "Mauritania",
|
||||||
|
"MU": "Mauritius",
|
||||||
|
"YT": "Mayotte",
|
||||||
|
"MX": "Mexico",
|
||||||
|
"FM": "Micronesia, Federated States Of",
|
||||||
|
"MD": "Moldova",
|
||||||
|
"MC": "Monaco",
|
||||||
|
"MN": "Mongolia",
|
||||||
|
"ME": "Montenegro",
|
||||||
|
"MS": "Montserrat",
|
||||||
|
"MA": "Morocco",
|
||||||
|
"MZ": "Mozambique",
|
||||||
|
"MM": "Myanmar",
|
||||||
|
"NA": "Namibia",
|
||||||
|
"NR": "Nauru",
|
||||||
|
"NP": "Nepal",
|
||||||
|
"NL": "Netherlands",
|
||||||
|
"AN": "Netherlands Antilles",
|
||||||
|
"NC": "New Caledonia",
|
||||||
|
"NZ": "New Zealand",
|
||||||
|
"NI": "Nicaragua",
|
||||||
|
"NE": "Niger",
|
||||||
|
"NG": "Nigeria",
|
||||||
|
"NU": "Niue",
|
||||||
|
"NF": "Norfolk Island",
|
||||||
|
"MP": "Northern Mariana Islands",
|
||||||
|
"NO": "Norway",
|
||||||
|
"OM": "Oman",
|
||||||
|
"PK": "Pakistan",
|
||||||
|
"PW": "Palau",
|
||||||
|
"PS": "Palestinian Territory, Occupied",
|
||||||
|
"PA": "Panama",
|
||||||
|
"PG": "Papua New Guinea",
|
||||||
|
"PY": "Paraguay",
|
||||||
|
"PE": "Peru",
|
||||||
|
"PH": "Philippines",
|
||||||
|
"PN": "Pitcairn",
|
||||||
|
"PL": "Poland",
|
||||||
|
"PT": "Portugal",
|
||||||
|
"PR": "Puerto Rico",
|
||||||
|
"QA": "Qatar",
|
||||||
|
"RE": "Reunion",
|
||||||
|
"RO": "Romania",
|
||||||
|
"RU": "Russian Federation",
|
||||||
|
"RW": "Rwanda",
|
||||||
|
"BL": "Saint Barthelemy",
|
||||||
|
"SH": "Saint Helena",
|
||||||
|
"KN": "Saint Kitts And Nevis",
|
||||||
|
"LC": "Saint Lucia",
|
||||||
|
"MF": "Saint Martin",
|
||||||
|
"PM": "Saint Pierre And Miquelon",
|
||||||
|
"VC": "Saint Vincent And Grenadines",
|
||||||
|
"WS": "Samoa",
|
||||||
|
"SM": "San Marino",
|
||||||
|
"ST": "Sao Tome And Principe",
|
||||||
|
"SA": "Saudi Arabia",
|
||||||
|
"SN": "Senegal",
|
||||||
|
"RS": "Serbia",
|
||||||
|
"SC": "Seychelles",
|
||||||
|
"SL": "Sierra Leone",
|
||||||
|
"SG": "Singapore",
|
||||||
|
"SK": "Slovakia",
|
||||||
|
"SI": "Slovenia",
|
||||||
|
"SB": "Solomon Islands",
|
||||||
|
"SO": "Somalia",
|
||||||
|
"ZA": "South Africa",
|
||||||
|
"GS": "South Georgia And Sandwich Isl.",
|
||||||
|
"ES": "Spain",
|
||||||
|
"LK": "Sri Lanka",
|
||||||
|
"SD": "Sudan",
|
||||||
|
"SR": "Suriname",
|
||||||
|
"SJ": "Svalbard And Jan Mayen",
|
||||||
|
"SZ": "Swaziland",
|
||||||
|
"SE": "Sweden",
|
||||||
|
"CH": "Switzerland",
|
||||||
|
"SY": "Syrian Arab Republic",
|
||||||
|
"TW": "Taiwan",
|
||||||
|
"TJ": "Tajikistan",
|
||||||
|
"TZ": "Tanzania",
|
||||||
|
"TH": "Thailand",
|
||||||
|
"TL": "Timor-Leste",
|
||||||
|
"TG": "Togo",
|
||||||
|
"TK": "Tokelau",
|
||||||
|
"TO": "Tonga",
|
||||||
|
"TT": "Trinidad And Tobago",
|
||||||
|
"TN": "Tunisia",
|
||||||
|
"TR": "Turkey",
|
||||||
|
"TM": "Turkmenistan",
|
||||||
|
"TC": "Turks And Caicos Islands",
|
||||||
|
"TV": "Tuvalu",
|
||||||
|
"UG": "Uganda",
|
||||||
|
"UA": "Ukraine",
|
||||||
|
"AE": "United Arab Emirates",
|
||||||
|
"GB": "United Kingdom",
|
||||||
|
"US": "United States",
|
||||||
|
"UM": "United States Outlying Islands",
|
||||||
|
"UY": "Uruguay",
|
||||||
|
"UZ": "Uzbekistan",
|
||||||
|
"VU": "Vanuatu",
|
||||||
|
"VE": "Venezuela",
|
||||||
|
"VN": "Vietnam",
|
||||||
|
"VG": "Virgin Islands, British",
|
||||||
|
"VI": "Virgin Islands, U.S.",
|
||||||
|
"WF": "Wallis And Futuna",
|
||||||
|
"EH": "Western Sahara",
|
||||||
|
"YE": "Yemen",
|
||||||
|
"ZM": "Zambia",
|
||||||
|
"ZW": "Zimbabwe"
|
||||||
|
}
|
||||||
4443
public/data/MapChart_Map.svg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
@@ -231,15 +231,14 @@
|
|||||||
"capital": "Tirana",
|
"capital": "Tirana",
|
||||||
"description": "Albania is a country in Southeast Europe on the Balkan Peninsula. It is bordered by Montenegro, Kosovo, North Macedonia, and Greece, and has a coastline along the Adriatic and Ionian Seas.",
|
"description": "Albania is a country in Southeast Europe on the Balkan Peninsula. It is bordered by Montenegro, Kosovo, North Macedonia, and Greece, and has a coastline along the Adriatic and Ionian Seas.",
|
||||||
"area": "28,748 km²",
|
"area": "28,748 km²",
|
||||||
"ISO code": "AL"
|
"ISO code": "AL",
|
||||||
,
|
"cities": [
|
||||||
"cities": [
|
"Durrës",
|
||||||
"Durrës",
|
"Shkodër",
|
||||||
"Shkodër",
|
"Vlorë",
|
||||||
"Vlorë",
|
"Elbasan"
|
||||||
"Elbasan"
|
]
|
||||||
]
|
},
|
||||||
},
|
|
||||||
"variants": [
|
"variants": [
|
||||||
"red",
|
"red",
|
||||||
"black",
|
"black",
|
||||||
@@ -268,15 +267,14 @@
|
|||||||
"capital": "Yerevan",
|
"capital": "Yerevan",
|
||||||
"description": "Armenia is a landlocked country in the South Caucasus region of Eurasia, bordered by Turkey, Georgia, Azerbaijan, and Iran. It is known for its rich cultural heritage and historical significance as one of the first countries to adopt Christianity.",
|
"description": "Armenia is a landlocked country in the South Caucasus region of Eurasia, bordered by Turkey, Georgia, Azerbaijan, and Iran. It is known for its rich cultural heritage and historical significance as one of the first countries to adopt Christianity.",
|
||||||
"area": "29,743 km²",
|
"area": "29,743 km²",
|
||||||
"ISO code": "AM"
|
"ISO code": "AM",
|
||||||
,
|
"cities": [
|
||||||
"cities": [
|
"Gyumri",
|
||||||
"Gyumri",
|
"Vanadzor",
|
||||||
"Vanadzor",
|
"Vagharshapat",
|
||||||
"Vagharshapat",
|
"Hrazdan"
|
||||||
"Hrazdan"
|
]
|
||||||
]
|
},
|
||||||
},
|
|
||||||
"variants": [
|
"variants": [
|
||||||
"horizontal stripes",
|
"horizontal stripes",
|
||||||
"3 colours",
|
"3 colours",
|
||||||
@@ -325,15 +323,14 @@
|
|||||||
"description": "Angola is a country in Southern Africa, bordered by Namibia, Zambia, the Democratic Republic of the Congo, and the Atlantic Ocean. It is known for its rich natural resources, including oil and diamonds.",
|
"description": "Angola is a country in Southern Africa, bordered by Namibia, Zambia, the Democratic Republic of the Congo, and the Atlantic Ocean. It is known for its rich natural resources, including oil and diamonds.",
|
||||||
"area": "1,246,700 km²",
|
"area": "1,246,700 km²",
|
||||||
"ISO code": "AO",
|
"ISO code": "AO",
|
||||||
"wikipedia": "https://en.wikipedia.org/wiki/Angola"
|
"wikipedia": "https://en.wikipedia.org/wiki/Angola",
|
||||||
,
|
"cities": [
|
||||||
"cities": [
|
"Huambo",
|
||||||
"Huambo",
|
"Lobito",
|
||||||
"Lobito",
|
"Benguela",
|
||||||
"Benguela",
|
"Kuito"
|
||||||
"Kuito"
|
]
|
||||||
]
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Antarctica",
|
"name": "Antarctica",
|
||||||
@@ -365,15 +362,14 @@
|
|||||||
"description": "Argentina is a country in South America, bordered by Chile, Bolivia, Paraguay, Brazil, Uruguay, and the South Atlantic Ocean. It is known for its diverse landscapes, rich cultural heritage, and vibrant cities.",
|
"description": "Argentina is a country in South America, bordered by Chile, Bolivia, Paraguay, Brazil, Uruguay, and the South Atlantic Ocean. It is known for its diverse landscapes, rich cultural heritage, and vibrant cities.",
|
||||||
"area": "2,780,400 km²",
|
"area": "2,780,400 km²",
|
||||||
"ISO code": "AR",
|
"ISO code": "AR",
|
||||||
"wikipedia": "https://en.wikipedia.org/wiki/Argentina"
|
"wikipedia": "https://en.wikipedia.org/wiki/Argentina",
|
||||||
,
|
"cities": [
|
||||||
"cities": [
|
"Córdoba",
|
||||||
"Córdoba",
|
"Rosario",
|
||||||
"Rosario",
|
"Mendoza",
|
||||||
"Mendoza",
|
"La Plata"
|
||||||
"La Plata"
|
]
|
||||||
]
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "League of Arab States",
|
"name": "League of Arab States",
|
||||||
@@ -436,15 +432,14 @@
|
|||||||
"description": "Austria is a landlocked country in Central Europe, bordered by Germany, Czech Republic, Slovakia, Hungary, Slovenia, Italy, Switzerland, and Liechtenstein. It is known for its rich cultural heritage, stunning alpine landscapes, and historical significance.",
|
"description": "Austria is a landlocked country in Central Europe, bordered by Germany, Czech Republic, Slovakia, Hungary, Slovenia, Italy, Switzerland, and Liechtenstein. It is known for its rich cultural heritage, stunning alpine landscapes, and historical significance.",
|
||||||
"area": "83,879 km²",
|
"area": "83,879 km²",
|
||||||
"ISO code": "AT",
|
"ISO code": "AT",
|
||||||
"wikipedia": "https://en.wikipedia.org/wiki/Austria"
|
"wikipedia": "https://en.wikipedia.org/wiki/Austria",
|
||||||
,
|
"cities": [
|
||||||
"cities": [
|
"Graz",
|
||||||
"Graz",
|
"Linz",
|
||||||
"Linz",
|
"Salzburg",
|
||||||
"Salzburg",
|
"Innsbruck"
|
||||||
"Innsbruck"
|
]
|
||||||
]
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Australia",
|
"name": "Australia",
|
||||||
@@ -461,15 +456,14 @@
|
|||||||
"description": "Australia is a country and continent surrounded by the Indian and Pacific Oceans. It is known for its unique wildlife, diverse landscapes, and vibrant cities. The country is the sixth-largest in the world by total area.",
|
"description": "Australia is a country and continent surrounded by the Indian and Pacific Oceans. It is known for its unique wildlife, diverse landscapes, and vibrant cities. The country is the sixth-largest in the world by total area.",
|
||||||
"area": "7,692,024 km²",
|
"area": "7,692,024 km²",
|
||||||
"ISO code": "AU",
|
"ISO code": "AU",
|
||||||
"wikipedia": "https://en.wikipedia.org/wiki/Australia"
|
"wikipedia": "https://en.wikipedia.org/wiki/Australia",
|
||||||
,
|
"cities": [
|
||||||
"cities": [
|
"Sydney",
|
||||||
"Sydney",
|
"Melbourne",
|
||||||
"Melbourne",
|
"Brisbane",
|
||||||
"Brisbane",
|
"Perth"
|
||||||
"Perth"
|
]
|
||||||
]
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Aruba",
|
"name": "Aruba",
|
||||||
@@ -5194,15 +5188,14 @@
|
|||||||
"description": "Uzbekistan is a landlocked country in Central Asia, known for its rich history along the Silk Road and its architectural heritage.",
|
"description": "Uzbekistan is a landlocked country in Central Asia, known for its rich history along the Silk Road and its architectural heritage.",
|
||||||
"area": "447,400 km²",
|
"area": "447,400 km²",
|
||||||
"ISO code": "UZ",
|
"ISO code": "UZ",
|
||||||
"wikipedia": "https://en.wikipedia.org/wiki/Uzbekistan"
|
"wikipedia": "https://en.wikipedia.org/wiki/Uzbekistan",
|
||||||
,
|
"cities": [
|
||||||
"cities": [
|
"Samarkand",
|
||||||
"Samarkand",
|
"Bukhara",
|
||||||
"Bukhara",
|
"Namangan",
|
||||||
"Namangan",
|
"Andijan"
|
||||||
"Andijan"
|
]
|
||||||
]
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Holy See",
|
"name": "Holy See",
|
||||||
@@ -7090,4 +7083,4 @@
|
|||||||
"seal"
|
"seal"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1354,7 +1354,23 @@
|
|||||||
],
|
],
|
||||||
"brand": "Dalnoboy Service",
|
"brand": "Dalnoboy Service",
|
||||||
"colors": {
|
"colors": {
|
||||||
"orange": "#ee7800"
|
"orange": "#ee7800",
|
||||||
|
"black": "#000000",
|
||||||
|
"white": "#ffffff"
|
||||||
|
},
|
||||||
|
"targets": {
|
||||||
|
"main": "path"
|
||||||
|
},
|
||||||
|
"sets": {
|
||||||
|
"orange": {
|
||||||
|
"main": "orange"
|
||||||
|
},
|
||||||
|
"black": {
|
||||||
|
"main": "black"
|
||||||
|
},
|
||||||
|
"white": {
|
||||||
|
"main": "white"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2524,7 +2540,40 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"furniture"
|
"furniture"
|
||||||
],
|
],
|
||||||
"brand": "Nikamebel"
|
"brand": "Nikamebel",
|
||||||
|
"colors": {
|
||||||
|
"orange": "#fbc02d",
|
||||||
|
"sandstone": "#CEB17D",
|
||||||
|
"black": "#000000",
|
||||||
|
"transparent": "none"
|
||||||
|
},
|
||||||
|
"targets": {
|
||||||
|
"nika": "#nika-path",
|
||||||
|
"mebli": "#ua-group",
|
||||||
|
"mebel": "#ru-group"
|
||||||
|
},
|
||||||
|
"sets": {
|
||||||
|
"orange-ru": {
|
||||||
|
"nika": "orange",
|
||||||
|
"mebli": "transparent",
|
||||||
|
"mebel": "black"
|
||||||
|
},
|
||||||
|
"sandstone-ru": {
|
||||||
|
"nika": "sandstone",
|
||||||
|
"mebli": "transparent",
|
||||||
|
"mebel": "black"
|
||||||
|
},
|
||||||
|
"orange-ua": {
|
||||||
|
"nika": "orange",
|
||||||
|
"mebli": "black",
|
||||||
|
"mebel": "transparent"
|
||||||
|
},
|
||||||
|
"sandstone-ua": {
|
||||||
|
"nika": "sandstone",
|
||||||
|
"mebli": "black",
|
||||||
|
"mebel": "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nova_post",
|
"name": "nova_post",
|
||||||
@@ -3912,5 +3961,115 @@
|
|||||||
"path": "vite.svg",
|
"path": "vite.svg",
|
||||||
"format": "SVG",
|
"format": "SVG",
|
||||||
"disable": false
|
"disable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "shkafnik-ng",
|
||||||
|
"title": "Shkafnik",
|
||||||
|
"path": "shkafnik-ng.svg",
|
||||||
|
"format": "SVG",
|
||||||
|
"disable": false,
|
||||||
|
"brand": "Shkafnik",
|
||||||
|
"tags": [
|
||||||
|
"furniture"
|
||||||
|
],
|
||||||
|
"colors": {
|
||||||
|
"green": "#3a7456",
|
||||||
|
"gold": "#cc950b",
|
||||||
|
"currentColor": "currentColor"
|
||||||
|
},
|
||||||
|
"targets": {
|
||||||
|
"logo": "#logo",
|
||||||
|
"text": "#shkafnik-text",
|
||||||
|
"text-small": "#eng-text",
|
||||||
|
"tm": "#tm"
|
||||||
|
},
|
||||||
|
"sets": {
|
||||||
|
"main": {
|
||||||
|
"logo": "green",
|
||||||
|
"text": "green",
|
||||||
|
"text-small": "gold",
|
||||||
|
"tm": "green"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"logo": "gold",
|
||||||
|
"text": "gold",
|
||||||
|
"text-small": "green",
|
||||||
|
"tm": "gold"
|
||||||
|
},
|
||||||
|
"monochrome": {
|
||||||
|
"logo": "currentColor",
|
||||||
|
"text": "currentColor",
|
||||||
|
"text-small": "currentColor",
|
||||||
|
"tm": "currentColor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Shkafnik",
|
||||||
|
"name": "Shkafnik-ev",
|
||||||
|
"path": "shkafnik-ev.svg",
|
||||||
|
"format": "SVG",
|
||||||
|
"disable": false,
|
||||||
|
"brand": "Shkafnik",
|
||||||
|
"tags": [
|
||||||
|
"furniture"
|
||||||
|
],
|
||||||
|
"colors": {
|
||||||
|
"green": "#3a7456",
|
||||||
|
"gold": "#cc950b",
|
||||||
|
"currentColor": "currentColor",
|
||||||
|
"transparent": "none"
|
||||||
|
},
|
||||||
|
"targets": {
|
||||||
|
"logoI": "#shkafnik-I",
|
||||||
|
"logoU": "#shkafnik-U",
|
||||||
|
"text-ua": "#shkafnik-ua",
|
||||||
|
"text-en": "#shkafnik-en",
|
||||||
|
"tm": "#tm"
|
||||||
|
},
|
||||||
|
"sets": {
|
||||||
|
"main": {
|
||||||
|
"logoI": "gold",
|
||||||
|
"logoU": "green",
|
||||||
|
"text-ua": "green",
|
||||||
|
"text-en": "gold",
|
||||||
|
"tm": "green"
|
||||||
|
},
|
||||||
|
"ng": {
|
||||||
|
"logoI": "green",
|
||||||
|
"logoU": "green",
|
||||||
|
"text-ua": "green",
|
||||||
|
"text-en": "gold",
|
||||||
|
"tm": "green"
|
||||||
|
},
|
||||||
|
"monochrome": {
|
||||||
|
"logoI": "currentColor",
|
||||||
|
"logoU": "currentColor",
|
||||||
|
"text-ua": "currentColor",
|
||||||
|
"text-en": "currentColor",
|
||||||
|
"tm": "currentColor"
|
||||||
|
},
|
||||||
|
"logo-only": {
|
||||||
|
"logoI": "green",
|
||||||
|
"logoU": "gold",
|
||||||
|
"text-ua": "transparent",
|
||||||
|
"text-en": "transparent",
|
||||||
|
"tm": "transparent"
|
||||||
|
},
|
||||||
|
"logo-only-green": {
|
||||||
|
"logoI": "green",
|
||||||
|
"logoU": "green",
|
||||||
|
"text-ua": "transparent",
|
||||||
|
"text-en": "transparent",
|
||||||
|
"tm": "transparent"
|
||||||
|
},
|
||||||
|
"logo-only-monochrome": {
|
||||||
|
"logoI": "currentColor",
|
||||||
|
"logoU": "currentColor",
|
||||||
|
"text-ua": "transparent",
|
||||||
|
"text-en": "transparent",
|
||||||
|
"tm": "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 223 KiB |
1473
public/data/world_prev.svg
Normal file
|
After Width: | Height: | Size: 155 KiB |
562
public/data/worldmap.svg
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
24
public/data/worldmap_orig.svg
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
@@ -1,13 +1,34 @@
|
|||||||
<svg width="100%" height="100%" viewBox="0 0 2786 400" xmlns="http://www.w3.org/2000/svg">
|
<svg width="100%" height="100%" viewBox="0 0 11667 1667" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g id="g4138">
|
<path id="nika-path" fill="#fbc02d"
|
||||||
<path id="path4150" fill="#fbc02d" stroke="none"
|
d="M0,1666.6l0,-1664.03l202.677,-0c139.101,-0 207.738,13.06 218.811,41.636c41.636,107.456 732.281,1026.96 763.553,1016.57c24.329,-8.082 35.521,-176.667 35.521,-535.037l-0,-523.237l364.583,0.069l0,1664.03l-220.805,0c-192.917,0 -225.373,-8.454 -256.979,-66.94c-98.195,-181.708 -674.291,-939.421 -706.189,-928.819c-25.57,8.498 -36.588,160.279 -36.588,503.994l-0,491.834l-182.292,-0.035l-182.292,-0.034Zm2013.13,-0.004l0,-1664.03l396.287,-0l-0,1664.03l-396.287,0Zm792.573,0l-0,-1664.03l396.286,-0l0,361.062c0,198.584 8.816,361.062 19.592,361.062c28.697,0 212.951,-161.236 526.36,-460.603l276.477,-264.09l250.688,9.134l250.688,9.133l-343.638,298.269c-189.002,164.048 -344.195,321.384 -344.874,349.637c-0.679,28.253 163.451,264.905 364.733,525.895l365.967,474.526l-498.754,0l-251.328,-368.911c-138.23,-202.901 -268.508,-368.911 -289.506,-368.911c-20.998,-0 -103.029,53.685 -182.292,119.3l-144.113,119.3l0,499.222l-396.286,0Zm1948.21,-243.324c64.057,-133.829 236.832,-508.095 383.947,-831.704l267.483,-588.379l3130.66,-0.309l3130.66,-0.309l-0,1664.03l-5482.84,-0l-68.735,-158.237c-37.805,-87.03 -94.329,-171.801 -125.609,-188.38c-74.477,-39.474 -682.468,-39.047 -757.143,0.532c-31.832,16.871 -83.645,101.642 -115.14,188.38l-57.262,157.705l-422.489,-0l116.465,-243.324l-0,-0Zm1096.99,-424.818c19.132,-49.375 -190.332,-534.869 -237.483,-550.434c-35.927,-11.86 -216.393,358.022 -246.051,504.301l-17.505,86.341l242.73,0c168.036,0 247.523,-12.373 258.309,-40.208Z" />
|
||||||
d="M -6.561563 194.457001 L -6.561563 0.125549 L 42.063084 0.125549 C 75.435112 0.125549 91.90197 3.176025 94.55838 9.850372 C 104.547394 34.948578 270.241608 249.715546 277.744019 247.289246 C 283.58078 245.401611 286.265991 206.025452 286.265991 122.321838 L 286.265991 0.110474 L 329.999908 0.1185 L 373.733917 0.126526 L 373.733917 194.457993 L 373.733917 388.789398 L 320.760162 388.789398 C 274.476929 388.789398 266.690277 386.814728 259.107666 373.154388 C 235.549438 330.713257 97.337158 153.735779 89.684387 156.211914 C 83.549988 158.196823 80.906425 193.648026 80.906425 273.928772 L 80.906425 388.805389 L 37.172432 388.797333 L -6.561563 388.789307 L -6.561563 194.457886 Z M 476.413635 194.457001 L 476.413635 0.125549 L 523.950623 0.125549 L 571.487549 0.125549 L 571.487549 194.457001 L 571.487549 388.788422 L 523.950623 388.788422 L 476.413635 388.788422 L 476.413635 194.457001 Z M 666.56134 194.457001 L 666.56134 0.125549 L 714.098328 0.125549 L 761.635254 0.125549 L 761.635254 84.458069 C 761.635254 130.840942 763.750366 168.790588 766.33551 168.790588 C 773.220276 168.790588 817.425171 131.131042 892.615723 61.208527 L 958.945923 -0.474487 L 1019.08905 1.658844 L 1079.232056 3.792175 L 996.789001 73.458191 C 951.445251 111.774475 914.212463 148.523254 914.049561 155.122162 C 913.88678 161.721039 953.263428 216.995483 1001.553406 277.954285 L 1089.353394 388.788422 L 1029.524902 388.788422 L 969.696411 388.788422 L 909.399719 302.62262 C 876.236633 255.231415 844.981384 216.456787 839.943665 216.456787 C 834.906006 216.456787 815.225708 228.995895 796.209656 244.321503 L 761.635254 272.186218 L 761.635254 330.487335 L 761.635254 388.788422 L 714.098328 388.788422 L 666.56134 388.788422 L 666.56134 194.457001 Z M 1133.959961 331.955658 C 1149.328003 300.697632 1190.778931 213.280945 1226.073608 137.696411 L 1290.24585 0.269928 L 2041.329102 0.197784 L 2792.412598 0.125549 L 2792.412598 194.457001 L 2792.412598 388.788391 L 2134.712891 388.788391 L 1477.013428 388.788391 L 1460.522949 351.829437 C 1451.453247 331.502014 1437.892456 311.702209 1430.387939 307.829895 C 1412.519897 298.609924 1266.655518 298.709564 1248.740112 307.953949 C 1241.103271 311.894501 1228.672852 331.694336 1221.116821 351.953522 L 1207.378784 388.788391 L 1156.698608 388.788391 L 1106.018555 388.788391 L 1133.959961 331.955627 Z M 1397.140747 232.731873 C 1401.730713 221.199402 1351.477661 107.80365 1340.165771 104.168182 C 1331.546387 101.398102 1288.250244 187.790558 1281.135132 221.956726 L 1276.935425 242.123199 L 1335.169189 242.123199 C 1375.483154 242.123199 1394.553101 239.233276 1397.140747 232.731873 Z" />
|
<g id="ru-group" fill="none">
|
||||||
<text xml:space="preserve"
|
<path
|
||||||
style="font-style:normal;font-weight:normal;font-size:397.18811035px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
d="M7038.77,1336.34c-10.536,-31.46 -24.496,-71.44 -41.88,-119.941c-17.384,-48.501 -36.086,-100.935 -56.104,-157.301c-20.018,-56.366 -41.354,-114.37 -64.006,-174.013c-22.652,-59.643 -43.988,-116.009 -64.006,-169.098c-20.018,-53.089 -38.719,-100.607 -56.104,-142.554c-17.384,-41.946 -31.344,-74.062 -41.88,-96.346c-11.59,154.679 -21.072,322.138 -28.447,502.378c-7.375,180.24 -13.697,362.119 -18.965,545.636l-150.137,-0c4.214,-117.976 8.955,-236.934 14.223,-356.876c5.268,-119.941 11.326,-237.916 18.175,-353.925c6.848,-116.009 14.223,-229.069 22.125,-339.179c7.902,-110.111 16.594,-214.322 26.077,-312.635l134.333,0c28.447,57.677 59.001,125.84 91.663,204.491c32.661,78.65 65.323,160.905 97.984,246.765c32.662,85.859 64.27,171.719 94.824,257.579c30.554,85.86 58.474,164.182 83.761,234.967c25.286,-70.785 53.206,-149.107 83.761,-234.967c30.554,-85.86 62.162,-171.72 94.823,-257.579c32.662,-85.86 65.323,-168.115 97.985,-246.765c32.661,-78.651 63.215,-146.814 91.662,-204.491l134.334,0c35.822,439.13 62.689,893.335 80.6,1362.62l-150.137,-0c-5.268,-183.517 -11.59,-365.396 -18.965,-545.636c-7.375,-180.24 -16.858,-347.699 -28.447,-502.378c-10.536,22.284 -24.496,54.4 -41.881,96.346c-17.384,41.947 -36.085,89.465 -56.104,142.554c-20.018,53.089 -41.353,109.455 -64.006,169.098c-22.652,59.643 -43.987,117.647 -64.005,174.013c-20.019,56.366 -38.72,108.8 -56.104,157.301c-17.385,48.501 -31.345,88.481 -41.881,119.941l-123.271,0Z"
|
||||||
x="246" y="309" transform="scale(0.95881214,1.0429572)">
|
style="fill-rule:nonzero;" />
|
||||||
<tspan id="tspan4168" x="246" y="309"
|
<path
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Ubuntu;-inkscape-font-specification:Ubuntu">
|
d="M7923.79,1525.11l-0,-1362.62l668.506,0l0,163.199l-515.208,0l-0,405.049l458.314,-0l0,159.266l-458.314,0l-0,471.902l554.718,-0l0,163.199l-708.016,-0Z"
|
||||||
MEBEL</tspan>
|
style="fill-rule:nonzero;" />
|
||||||
</text>
|
<path
|
||||||
</g>
|
d="M9121.73,1536.9c-22.125,0 -46.095,-0.655 -71.908,-1.966c-25.813,-1.311 -51.626,-3.277 -77.439,-5.899c-25.813,-2.621 -51.363,-5.899 -76.649,-9.831c-25.286,-3.933 -48.466,-9.176 -69.537,-15.73l-0,-1321.32c21.071,-6.555 44.251,-11.798 69.537,-15.73c25.286,-3.933 50.836,-7.21 76.649,-9.832c25.813,-2.621 51.363,-4.588 76.649,-5.898c25.286,-1.311 48.992,-1.967 71.118,-1.967c63.215,0 122.48,5.899 177.794,17.697c55.314,11.797 103.252,31.787 143.816,59.97c40.563,28.183 72.435,65.215 95.614,111.094c23.179,45.879 34.768,102.245 34.768,169.098c0,74.717 -14.223,135.999 -42.67,183.845c-28.447,47.845 -66.377,83.565 -113.789,107.16c64.27,23.595 115.369,60.954 153.298,112.077c37.93,51.123 56.895,123.219 56.895,216.288c-0,136.327 -40.3,238.9 -120.9,307.719c-80.601,68.819 -208.349,103.228 -383.246,103.228Zm-143.816,-646.898l0,479.767c11.59,1.31 25.287,2.621 41.091,3.932c13.696,1.311 29.764,2.294 48.202,2.949c18.437,0.656 39.773,0.984 64.005,0.984c45.305,-0 88.239,-3.605 128.803,-10.815c40.563,-7.209 76.122,-19.99 106.676,-38.342c30.554,-18.351 55.05,-43.913 73.488,-76.684c18.438,-32.771 27.657,-74.062 27.657,-123.874c0,-44.568 -6.848,-82.255 -20.545,-113.059c-13.697,-30.805 -33.451,-55.383 -59.265,-73.735c-25.813,-18.352 -56.63,-31.46 -92.453,-39.325c-35.822,-7.865 -75.858,-11.798 -120.109,-11.798l-197.55,0Zm0,-153.367l161.2,-0c37.93,-0 73.752,-3.278 107.467,-9.832c33.715,-6.554 62.953,-18.351 87.712,-35.392c24.76,-17.041 44.251,-39.325 58.475,-66.853c14.223,-27.528 21.335,-62.265 21.335,-104.212c-0,-39.325 -7.375,-72.423 -22.126,-99.295c-14.75,-26.873 -35.032,-48.501 -60.845,-64.887c-25.813,-16.385 -56.104,-28.51 -90.872,-36.376c-34.769,-7.865 -71.645,-11.797 -110.628,-11.797c-38.983,-0 -69.537,0.655 -91.663,1.966c-22.125,1.311 -42.144,3.277 -60.055,5.899l0,420.779Z"
|
||||||
|
style="fill-rule:nonzero;" />
|
||||||
|
<path
|
||||||
|
d="M9842.39,1525.11l-0,-1362.62l668.506,0l0,163.199l-515.208,0l-0,405.049l458.314,-0l-0,159.266l-458.314,0l-0,471.902l554.718,-0l-0,163.199l-708.016,-0Z"
|
||||||
|
style="fill-rule:nonzero;" />
|
||||||
|
<path d="M11403.8,1359.94l0,165.166l-659.024,-0l0,-1362.62l153.298,0l0,1197.45l505.726,0Z"
|
||||||
|
style="fill-rule:nonzero;" />
|
||||||
|
</g>
|
||||||
|
<g id="ua-group" fill="black">
|
||||||
|
<path
|
||||||
|
d="M7185.19,1329.23c-11.729,-31.46 -27.271,-71.441 -46.624,-119.942c-19.354,-48.5 -40.174,-100.934 -62.46,-157.3c-22.286,-56.366 -46.038,-114.37 -71.256,-174.013c-25.219,-59.644 -48.971,-116.01 -71.257,-169.098c-22.286,-53.089 -43.106,-100.607 -62.459,-142.554c-19.354,-41.947 -34.896,-74.062 -46.625,-96.346c-12.902,154.678 -23.459,322.138 -31.67,502.378c-8.21,180.24 -15.248,362.118 -21.113,545.635l-167.145,0c4.692,-117.975 9.97,-236.933 15.835,-356.875c5.865,-119.941 12.609,-237.917 20.234,-353.926c7.624,-116.009 15.834,-229.068 24.631,-339.179c8.798,-110.11 18.474,-214.321 29.031,-312.634l149.551,-0c31.669,57.677 65.685,125.84 102.046,204.49c36.361,78.651 72.723,160.906 109.084,246.765c36.361,85.86 71.55,171.72 105.565,257.58c34.016,85.86 65.099,164.182 93.25,234.967c28.15,-70.785 59.233,-149.107 93.249,-234.967c34.015,-85.86 69.204,-171.72 105.565,-257.58c36.362,-85.859 72.723,-168.114 109.084,-246.765c36.362,-78.65 70.377,-146.813 102.047,-204.49l149.55,-0c39.881,439.13 69.791,893.335 89.731,1362.61l-167.145,0c-5.865,-183.517 -12.903,-365.395 -21.113,-545.635c-8.211,-180.24 -18.767,-347.7 -31.67,-502.378c-11.729,22.284 -27.271,54.399 -46.624,96.346c-19.354,41.947 -40.174,89.465 -62.46,142.554c-22.286,53.088 -46.038,109.454 -71.256,169.098c-25.219,59.643 -48.971,117.647 -71.257,174.013c-22.286,56.366 -43.106,108.8 -62.459,157.3c-19.354,48.501 -34.896,88.482 -46.625,119.942l-137.235,-0Z"
|
||||||
|
style="fill-rule:nonzero;" />
|
||||||
|
<path
|
||||||
|
d="M8170.47,1517.99l-0,-1362.61l744.235,-0l-0,163.199l-573.571,0l-0,405.049l510.232,-0l-0,159.266l-510.232,0l-0,471.901l617.556,0l0,163.199l-788.22,0Z"
|
||||||
|
style="fill-rule:nonzero;" />
|
||||||
|
<path
|
||||||
|
d="M9504.11,1529.79c-24.632,-0 -51.317,-0.655 -80.054,-1.966c-28.737,-1.311 -57.474,-3.277 -86.212,-5.899c-28.737,-2.622 -57.181,-5.899 -85.332,-9.831c-28.15,-3.933 -53.955,-9.176 -77.414,-15.73l-0,-1321.32c23.459,-6.554 49.264,-11.797 77.414,-15.73c28.151,-3.932 56.595,-7.209 85.332,-9.831c28.738,-2.622 57.182,-4.588 85.332,-5.899c28.151,-1.311 54.542,-1.966 79.174,-1.966c70.377,0 136.355,5.899 197.935,17.696c61.58,11.798 114.949,31.788 160.107,59.971c45.159,28.183 80.64,65.214 106.445,111.094c25.805,45.879 38.707,102.245 38.707,169.097c0,74.718 -15.834,136 -47.504,183.845c-31.669,47.846 -73.896,83.566 -126.678,107.161c71.55,23.595 128.438,60.954 170.664,112.077c42.226,51.122 63.339,123.218 63.339,216.288c-0,136.327 -44.865,238.9 -134.596,307.719c-89.73,68.819 -231.95,103.228 -426.659,103.228Zm-160.108,-646.898l0,479.766c12.903,1.311 28.151,2.622 45.745,3.933c15.249,1.311 33.136,2.294 53.663,2.949c20.526,0.656 44.278,0.983 71.256,0.983c50.437,0 98.234,-3.604 143.393,-10.814c45.158,-7.21 84.745,-19.99 118.761,-38.342c34.015,-18.352 61.286,-43.913 81.813,-76.684c20.526,-32.771 30.79,-74.062 30.79,-123.874c-0,-44.568 -7.624,-82.255 -22.873,-113.06c-15.248,-30.804 -37.241,-55.383 -65.978,-73.734c-28.737,-18.352 -63.046,-31.46 -102.926,-39.325c-39.88,-7.865 -84.452,-11.798 -133.716,-11.798l-219.928,0Zm0,-153.368l179.461,0c42.226,0 82.107,-3.277 119.641,-9.831c37.534,-6.554 70.083,-18.352 97.648,-35.393c27.564,-17.04 49.263,-39.325 65.098,-66.852c15.835,-27.528 23.752,-62.265 23.752,-104.212c0,-39.325 -8.21,-72.423 -24.631,-99.296c-16.422,-26.872 -39.001,-48.501 -67.738,-64.886c-28.737,-16.385 -62.46,-28.511 -101.167,-36.376c-38.707,-7.865 -79.76,-11.797 -123.159,-11.797c-43.399,-0 -77.415,0.655 -102.047,1.966c-24.632,1.311 -46.918,3.277 -66.858,5.899l0,420.778Z"
|
||||||
|
style="fill-rule:nonzero;" />
|
||||||
|
<path d="M11040.1,1352.83l-0,165.165l-733.679,0l0,-1362.61l170.664,-0l0,1197.45l563.015,0Z"
|
||||||
|
style="fill-rule:nonzero;" />
|
||||||
|
<rect x="11219.5" y="155.38" width="170.664" height="1362.62" style="fill-rule:nonzero;" />
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 9.3 KiB |
@@ -26,4 +26,4 @@
|
|||||||
<stop offset="1" style="stop-color:#56bda8;stop-opacity:1" />
|
<stop offset="1" style="stop-color:#56bda8;stop-opacity:1" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
46
public/images/logos/shkafnik-ev.svg
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<svg width="100%" height="100%" viewBox="0 0 380 145" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="shkafnik-ua" fill="#3a7456">
|
||||||
|
<path
|
||||||
|
d="M124.755,107.695l-7.598,-12.566l13.646,-21.978l7.023,0l-13.913,21.863l14.906,24.513l-6.89,0l-7.174,-11.832Z" />
|
||||||
|
<rect x="101.454" y="73.151" width="5.963" height="46.376" />
|
||||||
|
<path
|
||||||
|
d="M149.158,103.626l-0.004,-0l0,15.901l-5.962,-0l0,-41.076l5.3,-5.3l25.175,0l5.3,5.3l-0,41.076l-5.962,0l-0,-15.901l-14.223,0l-0,-5.3l14.223,0l-0,-19.875l-23.851,0l-0,19.875l0.004,0l0,5.3Z" />
|
||||||
|
<path
|
||||||
|
d="M186.917,109.589l0,-26.5l5.3,-5.3l34.45,0l5.3,5.3l0,26.5l-5.3,5.3l-34.45,0l-5.3,-5.3Zm5.963,-26.5l-0,26.5l33.125,0l-0,-26.5l-33.125,0Zm19.544,36.438l-5.963,0l-0,-46.376l5.963,0l-0,46.376Z" />
|
||||||
|
<path
|
||||||
|
d="M245.913,98.326l-0.033,0l-0,21.201l-5.963,-0l-0,-46.376l5.963,0l-0,19.875l0.033,0l-0,5.3Zm9.623,-5.3l14.857,0l-0,-19.875l5.962,0l-0,46.376l-5.962,0l-0,-21.201l-14.857,0l0,-5.3Z" />
|
||||||
|
<path
|
||||||
|
d="M321.405,119.527l-5.962,0l-0,-40.214l-22.658,40.214l-8.48,0l-0,-46.376l5.963,0l-0,40.214l22.657,-40.214l8.48,0l-0,46.376Z" />
|
||||||
|
<path
|
||||||
|
d="M352.555,107.645l-7.597,-12.566l13.646,-21.978l7.023,0l-13.913,21.863l14.906,24.513l-6.89,0l-7.175,-11.832Z" />
|
||||||
|
<rect x="329.255" y="73.101" width="5.963" height="46.376" />
|
||||||
|
</g>
|
||||||
|
<g id="shkafnik-logo">
|
||||||
|
<path id="shkafnik-U" fill="#3a7456"
|
||||||
|
d="M85.182,138.2l-82.673,-0.017l-0,-103.783l20.453,13.792l0,70.051l31.651,0.464l30.569,19.493Zm0.773,-118.688l0,108.677l-20.454,-12.531l0,-112.658l20.454,16.512Z" />
|
||||||
|
<path id="shkafnik-I" fill="#cc950b"
|
||||||
|
d="M34.205,138.2l-0.045,-125.982l20.453,14.578l-0.098,111.281l-9.828,5.768l-10.482,-5.645Z" />
|
||||||
|
</g>
|
||||||
|
<g id="shkafnik-en" fill="#cc950b">
|
||||||
|
<path
|
||||||
|
d="M249.152,137.983l-0,-1.66c1.786,0.521 3.714,0.781 5.785,0.781c2.928,0 4.392,-0.879 4.392,-2.637c0,-1.497 -1.079,-2.246 -3.237,-2.246l-2.191,0c-3.562,0 -5.344,-1.269 -5.344,-3.808c0,-2.67 2.282,-4.004 6.844,-4.004c1.984,0 3.829,0.195 5.535,0.586l0,1.66c-1.706,-0.521 -3.551,-0.782 -5.535,-0.782c-3.094,0 -4.642,0.847 -4.642,2.54c0,1.497 1.048,2.246 3.142,2.246l2.191,0c3.626,0 5.439,1.269 5.439,3.808c0,2.735 -2.198,4.102 -6.594,4.102c-2.071,0 -3.999,-0.196 -5.785,-0.586Z" />
|
||||||
|
<path
|
||||||
|
d="M264.745,138.471l0,-13.965l2.202,0l0,6.153l10.057,0l0,-6.153l2.202,0l0,13.965l-2.202,0l0,-6.348l-10.057,0l0,6.348l-2.202,0Z" />
|
||||||
|
<path
|
||||||
|
d="M285.455,124.506l0,13.965l-2.202,0l0,-13.965l2.202,0Zm11.974,0l-7.665,6.592l7.832,7.373l-3.059,0l-6.939,-6.719l0,-1.093l7.046,-6.153l2.785,0Z" />
|
||||||
|
<path
|
||||||
|
d="M300.476,138.471l-2.285,0l7.225,-13.965l2.44,0l7.26,13.965l-2.44,0l-2.202,-4.394l-5.63,0l0.607,-1.465l4.285,0l-3.178,-6.328l-6.082,12.187Z" />
|
||||||
|
<path
|
||||||
|
d="M328.566,124.506l-0,1.465l-8.927,0l-0,4.688l8.511,0l-0,1.464l-8.511,0l-0,6.348l-2.202,0l-0,-13.965l11.129,0Z" />
|
||||||
|
<path
|
||||||
|
d="M331.185,138.471l0,-13.965l2.202,0l10.414,11.25l0,-11.25l2.083,0l0,13.965l-2.202,0l-10.414,-11.348l0,11.348l-2.083,0Z" />
|
||||||
|
<rect x="350.288" y="124.506" width="2.202" height="13.965" />
|
||||||
|
<path
|
||||||
|
d="M359.096,124.506l0,13.965l-2.202,0l0,-13.965l2.202,0Zm11.974,0l-7.665,6.592l7.832,7.373l-3.059,0l-6.939,-6.719l0,-1.093l7.046,-6.153l2.785,0Z" />
|
||||||
|
</g>
|
||||||
|
<g id="tm" fill="#3a7456">
|
||||||
|
<path d="M369.945,72.895l0,-4.515l-1.565,0l0,-0.583l3.787,0l0,0.583l-1.566,0l0,4.515l-0.656,0Z" />
|
||||||
|
<path
|
||||||
|
d="M372.749,72.895l0,-5.098l0.831,0l1.573,2.753l1.573,-2.753l0.83,0l0,5.098l-0.641,0l0,-4.297l-1.544,2.695l-0.437,0l-1.544,-2.695l0,4.297l-0.641,0Z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
40
public/images/logos/shkafnik-ng.svg
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<svg width="100%" height="100%" viewBox="0 0 380 145" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path id="logo" fill="#3a7456" fill-rule="nonzero"
|
||||||
|
d="M32.087,137.2l-31.189,-0l-0,-0.017l-0.389,-0l-0,-103.783l20.453,13.792l-0,70.051l11.198,-0l-0,-106.025l20.453,14.578l0,91.911l30.569,19.493l-29.894,-0l-10.601,5.645l-10.6,-5.645Zm51.868,-118.688l0,108.677l-20.454,-12.531l0,-112.658l20.454,16.512Z" />
|
||||||
|
<g id="shkafnik-text" fill="#3a7456" fill-rule="nonzero">
|
||||||
|
<path
|
||||||
|
d="M99.454,118.527l0,-46.376l5.963,0l-0,20.207l10.931,-0l12.455,-20.207l7.023,0l-13.913,21.863l14.906,24.513l-6.89,-0l-12.653,-20.869l-11.859,-0l-0,20.869l-5.963,-0Z" />
|
||||||
|
<path
|
||||||
|
d="M176.967,77.451l0,41.076l-5.962,-0l-0,-15.901l-23.851,0l0,15.901l-5.962,-0l-0,-41.076l5.3,-5.3l25.175,0l5.3,5.3Zm-29.813,0l0,19.875l23.851,0l-0,-19.875l-23.851,0Z" />
|
||||||
|
<path
|
||||||
|
d="M184.917,108.589l0,-26.5l5.3,-5.3l34.45,-0l5.3,5.3l0,26.5l-5.3,5.3l-34.45,0l-5.3,-5.3Zm5.963,-26.5l-0,26.5l33.125,0l-0,-26.5l-33.125,-0Zm19.544,36.438l-5.963,-0l-0,-46.376l5.963,0l-0,46.376Z" />
|
||||||
|
<path
|
||||||
|
d="M237.917,118.527l0,-46.376l5.963,0l-0,19.875l24.513,0l-0,-19.875l5.962,0l0,46.376l-5.962,-0l-0,-21.201l-24.513,0l-0,21.201l-5.963,-0Z" />
|
||||||
|
<path
|
||||||
|
d="M319.405,118.527l-5.962,-0l-0,-40.214l-22.658,40.214l-8.48,-0l0,-46.376l5.963,0l-0,40.214l22.657,-40.214l8.48,0l0,46.376Z" />
|
||||||
|
<path
|
||||||
|
d="M327.355,118.527l0,-46.376l5.963,0l-0,20.207l10.931,-0l12.455,-20.207l7.023,0l-13.913,21.863l14.907,24.513l-6.89,-0l-12.654,-20.869l-11.859,-0l-0,20.869l-5.963,-0Z" />
|
||||||
|
</g>
|
||||||
|
<g id="eng-text" fill="#cc950b">
|
||||||
|
<path
|
||||||
|
d="M247.152,136.983l0,-1.66c1.786,0.521 3.714,0.781 5.785,0.781c2.928,-0 4.392,-0.879 4.392,-2.637c0,-1.497 -1.079,-2.246 -3.237,-2.246l-2.191,0c-3.562,0 -5.344,-1.269 -5.344,-3.808c0,-2.67 2.282,-4.004 6.844,-4.004c1.984,-0 3.829,0.195 5.535,0.586l-0,1.66c-1.706,-0.521 -3.551,-0.782 -5.535,-0.782c-3.094,0 -4.642,0.847 -4.642,2.54c0,1.497 1.048,2.246 3.142,2.246l2.191,-0c3.626,-0 5.439,1.269 5.439,3.808c0,2.735 -2.198,4.102 -6.594,4.102c-2.071,-0 -3.999,-0.196 -5.785,-0.586Z" />
|
||||||
|
<path
|
||||||
|
d="M262.745,137.471l-0,-13.965l2.202,0l-0,6.153l10.057,-0l0,-6.153l2.202,0l0,13.965l-2.202,0l0,-6.348l-10.057,0l-0,6.348l-2.202,0Z" />
|
||||||
|
<path
|
||||||
|
d="M283.455,123.506l0,13.965l-2.202,0l0,-13.965l2.202,0Zm11.974,0l-7.665,6.592l7.832,7.373l-3.059,0l-6.939,-6.719l-0,-1.093l7.046,-6.153l2.785,0Z" />
|
||||||
|
<path
|
||||||
|
d="M298.476,137.471l-2.285,0l7.225,-13.965l2.44,0l7.26,13.965l-2.44,0l-2.202,-4.394l-5.63,-0l0.607,-1.465l4.285,-0l-3.178,-6.328l-6.082,12.187Z" />
|
||||||
|
<path
|
||||||
|
d="M326.566,123.506l0,1.465l-8.927,0l0,4.688l8.511,-0l-0,1.464l-8.511,0l0,6.348l-2.202,0l0,-13.965l11.129,0Z" />
|
||||||
|
<path
|
||||||
|
d="M329.185,137.471l-0,-13.965l2.202,0l10.414,11.25l0,-11.25l2.083,0l0,13.965l-2.202,0l-10.414,-11.348l-0,11.348l-2.083,0Z" />
|
||||||
|
<rect x="348.288" y="123.506" width="2.202" height="13.965" />
|
||||||
|
<path
|
||||||
|
d="M357.096,123.506l0,13.965l-2.202,0l0,-13.965l2.202,0Zm11.974,0l-7.665,6.592l7.832,7.373l-3.059,0l-6.939,-6.719l-0,-1.093l7.046,-6.153l2.785,0Z" />
|
||||||
|
</g>
|
||||||
|
<g id="tm" fill="#3a7456" fill-rule="nonzero">
|
||||||
|
<path d="M367.945,71.895l0,-4.515l-1.565,-0l-0,-0.583l3.787,-0l-0,0.583l-1.566,-0l-0,4.515l-0.656,0Z" />
|
||||||
|
<path
|
||||||
|
d="M370.749,71.895l0,-5.098l0.831,-0l1.573,2.753l1.573,-2.753l0.83,-0l0,5.098l-0.641,0l0,-4.297l-1.544,2.695l-0.437,-0l-1.544,-2.695l0,4.297l-0.641,0Z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
492
scripts/cleanup_worldmap.py
Normal file
@@ -0,0 +1,492 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
SVG cleanup utility for worldmap.svg
|
||||||
|
|
||||||
|
This script performs a series of conservative, text-based transforms on
|
||||||
|
an SVG file to normalize path tags, strip unwanted attributes, add
|
||||||
|
data-iso attributes when possible, and pretty-print the result.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 scripts/cleanup_worldmap.py [--in-place]
|
||||||
|
|
||||||
|
By default the script does a dry run and prints a small preview. Use
|
||||||
|
--in-place to overwrite the file (a timestamped backup will be created).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
from xml.dom import minidom
|
||||||
|
from xml.parsers.expat import ExpatError
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from xml.dom import Node
|
||||||
|
import json
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
|
FILE_PATH = Path("public/data/worldmap.svg")
|
||||||
|
ISO_JSON = FILE_PATH.parent / "ISO3166-1.json"
|
||||||
|
|
||||||
|
def read_text(p: Path) -> str:
|
||||||
|
"""Read a text file using UTF-8 and return its contents as a string."""
|
||||||
|
return p.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def write_text(p: Path, s: str) -> None:
|
||||||
|
"""Write the given string to path using UTF-8 encoding."""
|
||||||
|
p.write_text(s, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def backup(p: Path) -> Path:
|
||||||
|
"""Create a timestamped backup of path and return the backup Path."""
|
||||||
|
ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
bak = p.with_suffix(p.suffix + f".bak.{ts}")
|
||||||
|
bak.write_text(p.read_text(encoding="utf-8"), encoding="utf-8")
|
||||||
|
return bak
|
||||||
|
|
||||||
|
|
||||||
|
def validate_xml(s: str) -> (bool, str):
|
||||||
|
"""Validate that the provided string is well-formed XML using minidom.
|
||||||
|
|
||||||
|
Returns (True, "OK") on success or (False, error_message) on failure.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
minidom.parseString(s)
|
||||||
|
return True, "OK"
|
||||||
|
except ExpatError as e:
|
||||||
|
return False, str(e)
|
||||||
|
except Exception as e:
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_name(s: str) -> str:
|
||||||
|
"""Normalize a country name for loose matching.
|
||||||
|
|
||||||
|
Removes diacritics, lowercases, replaces punctuation with spaces and
|
||||||
|
collapses runs of whitespace.
|
||||||
|
"""
|
||||||
|
if not s:
|
||||||
|
return ""
|
||||||
|
s = unicodedata.normalize("NFKD", s)
|
||||||
|
s = "".join(ch for ch in s if not unicodedata.combining(ch))
|
||||||
|
s = s.lower()
|
||||||
|
s = s.replace("&", " and ")
|
||||||
|
s = re.sub(r"[^a-z0-9]+", " ", s)
|
||||||
|
return re.sub(r"\s+", " ", s).strip()
|
||||||
|
|
||||||
|
def extract_inner_svg(svg: str) -> str:
|
||||||
|
"""If the file contains a nested <svg>, extract and return the inner SVG block.
|
||||||
|
|
||||||
|
This helps when the source file wraps the actual map inside an outer svg.
|
||||||
|
"""
|
||||||
|
m = re.search(r"<svg\b", svg, flags=re.IGNORECASE)
|
||||||
|
if not m:
|
||||||
|
return svg
|
||||||
|
first = m.start()
|
||||||
|
m2 = re.search(r"<svg\b", svg[first + 1 :], flags=re.IGNORECASE)
|
||||||
|
if not m2:
|
||||||
|
return svg
|
||||||
|
inner_open = first + 1 + m2.start()
|
||||||
|
patt = re.compile(r"</?svg\b", flags=re.IGNORECASE)
|
||||||
|
depth = 0
|
||||||
|
for match in patt.finditer(svg, inner_open):
|
||||||
|
token = match.group(0)
|
||||||
|
if token.lower().startswith("<svg"):
|
||||||
|
depth += 1
|
||||||
|
else:
|
||||||
|
depth -= 1
|
||||||
|
if depth == 0:
|
||||||
|
gt = svg.find('>', match.start())
|
||||||
|
if gt == -1:
|
||||||
|
return svg
|
||||||
|
return svg[inner_open: gt + 1]
|
||||||
|
return svg
|
||||||
|
|
||||||
|
|
||||||
|
def collapse_path_tags(svg: str) -> str:
|
||||||
|
"""Replace full <path>...</path> pairs with compact self-closing <path ... /> tags."""
|
||||||
|
return re.sub(r"<path\b([^>]*)>\s*</path\s*>", r"<path\1 />", svg, flags=re.IGNORECASE | re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
|
def split_attributes_multiline(svg: str) -> str:
|
||||||
|
"""Format attributes of <path> and <g> tags so each attribute appears on its own indented line.
|
||||||
|
|
||||||
|
This is purely for editor readability; it doesn't change element names or attribute values.
|
||||||
|
"""
|
||||||
|
# attributes that can be very long and benefit from value-wrapping
|
||||||
|
LONG_ATTRS = {"d", "points", "style"}
|
||||||
|
MAX_WIDTH = 120
|
||||||
|
|
||||||
|
attr_pair_re = re.compile(r'([A-Za-z_:][-A-Za-z0-9_:.]*)\s*=\s*"([^"]*)"', flags=re.DOTALL)
|
||||||
|
|
||||||
|
def wrap_value(name: str, val: str) -> str:
|
||||||
|
"""Wrap long attribute values into newline-separated chunks inside the quotes.
|
||||||
|
|
||||||
|
We break on spaces to avoid splitting tokens. Returns the wrapped value (no surrounding quotes).
|
||||||
|
"""
|
||||||
|
if not val:
|
||||||
|
return val
|
||||||
|
if name not in LONG_ATTRS or len(val) <= MAX_WIDTH:
|
||||||
|
return val
|
||||||
|
parts = val.split()
|
||||||
|
lines = []
|
||||||
|
cur = []
|
||||||
|
for p in parts:
|
||||||
|
if cur and len(" ".join(cur + [p])) > MAX_WIDTH:
|
||||||
|
lines.append(" ".join(cur))
|
||||||
|
cur = [p]
|
||||||
|
else:
|
||||||
|
cur.append(p)
|
||||||
|
if cur:
|
||||||
|
lines.append(" ".join(cur))
|
||||||
|
# indent wrapped lines with two spaces so they align under attribute
|
||||||
|
return "\n ".join(lines)
|
||||||
|
|
||||||
|
def repl(m):
|
||||||
|
tag = m.group(1)
|
||||||
|
attrs = m.group(2) or ""
|
||||||
|
closing = m.group(3) or ">"
|
||||||
|
attrs = attrs.strip()
|
||||||
|
if not attrs:
|
||||||
|
return f"<{tag}{closing}"
|
||||||
|
|
||||||
|
pieces = []
|
||||||
|
for am in attr_pair_re.finditer(attrs):
|
||||||
|
aname = am.group(1)
|
||||||
|
aval = am.group(2)
|
||||||
|
wval = wrap_value(aname, aval)
|
||||||
|
if "\n" in wval:
|
||||||
|
# keep newline inside quoted value; indent continuation lines
|
||||||
|
piece = f'{aname}="{wval}"'
|
||||||
|
else:
|
||||||
|
piece = f'{aname}="{wval}"'
|
||||||
|
pieces.append(piece)
|
||||||
|
|
||||||
|
# keep any remaining raw text (rare) appended
|
||||||
|
tail = attr_pair_re.sub("", attrs).strip()
|
||||||
|
if tail:
|
||||||
|
pieces.append(tail)
|
||||||
|
|
||||||
|
lines = [f"<{tag}"]
|
||||||
|
for p in pieces:
|
||||||
|
# if the attribute value contains internal newlines, ensure it's indented properly
|
||||||
|
if "\n" in p:
|
||||||
|
# split first line and continuation
|
||||||
|
idx = p.find('="')
|
||||||
|
name = p[:idx]
|
||||||
|
val = p[idx+2:-1]
|
||||||
|
first, *rest = val.split('\n')
|
||||||
|
lines.append(f" {name}=\"{first}\"")
|
||||||
|
for r in rest:
|
||||||
|
lines.append(f" {r}")
|
||||||
|
else:
|
||||||
|
lines.append(f" {p}")
|
||||||
|
|
||||||
|
lines.append(closing)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
return re.sub(r"<(path|g)\b([^>]*)\s*(/?>)", repl, svg, flags=re.IGNORECASE | re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
|
def add_svg_attributes(svg: str) -> str:
|
||||||
|
"""Ensure the root <svg> tag has fill, stroke and stroke-width attributes.
|
||||||
|
|
||||||
|
This updates (or inserts) only the specified attributes on the opening
|
||||||
|
<svg ...> tag and preserves any other existing attributes.
|
||||||
|
"""
|
||||||
|
def repl(m):
|
||||||
|
start, attrs, end = m.group(1), m.group(2), m.group(3)
|
||||||
|
# remove existing occurrences of these specific attributes (only on svg)
|
||||||
|
attrs = re.sub(r"\sfill\s*=\s*\"[^\"]*\"", "", attrs, flags=re.IGNORECASE)
|
||||||
|
attrs = re.sub(r"\sstroke\s*=\s*\"[^\"]*\"", "", attrs, flags=re.IGNORECASE)
|
||||||
|
attrs = re.sub(r"\sstroke-width\s*=\s*\"[^\"]*\"", "", attrs, flags=re.IGNORECASE)
|
||||||
|
attrs = re.sub(r"\s+", " ", attrs).strip()
|
||||||
|
mid = f" {attrs}" if attrs else ""
|
||||||
|
# add/overwrite desired attributes
|
||||||
|
return f"{start}{mid} fill=\"#fff\" stroke=\"#000\" stroke-width=\"0.2\"{end}"
|
||||||
|
|
||||||
|
return re.sub(r"(<svg\b)([^>]*?)(/?>)", repl, svg, flags=re.IGNORECASE | re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
|
def collapse_newlines_after_svg(svg: str) -> str:
|
||||||
|
"""Ensure there is exactly one newline immediately after the opening <svg> tag.
|
||||||
|
|
||||||
|
This prevents the script from accumulating blank lines between the
|
||||||
|
opening <svg> and the first child element across multiple runs.
|
||||||
|
"""
|
||||||
|
pattern = re.compile(r'(<svg\b[^>]*>)\s*\n+', flags=re.IGNORECASE)
|
||||||
|
return pattern.sub(r'\1\n', svg, count=1)
|
||||||
|
|
||||||
|
|
||||||
|
def strip_whitespace_text_nodes(node):
|
||||||
|
"""Recursively remove text nodes that contain only whitespace from a DOM node.
|
||||||
|
|
||||||
|
This reduces extra blank lines produced by minidom.toprettyxml when
|
||||||
|
the source contains whitespace-only text nodes between elements.
|
||||||
|
"""
|
||||||
|
for child in list(node.childNodes):
|
||||||
|
if child.nodeType == Node.TEXT_NODE:
|
||||||
|
if not child.data.strip():
|
||||||
|
node.removeChild(child)
|
||||||
|
continue
|
||||||
|
if child.hasChildNodes():
|
||||||
|
strip_whitespace_text_nodes(child)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_defs(svg: str) -> str:
|
||||||
|
"""Remove any <defs>...</defs> blocks from the SVG (case-insensitive)."""
|
||||||
|
return re.sub(r"<defs\b[^>]*>.*?</defs\s*>", "", svg, flags=re.IGNORECASE | re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_data_geo(svg: str) -> str:
|
||||||
|
# only operate on path and g opening tags
|
||||||
|
def repl(m):
|
||||||
|
"""Replace function used to strip data-geo* attributes from a tag match."""
|
||||||
|
start, attrs, end = m.group(1), m.group(2), m.group(3)
|
||||||
|
attrs2 = re.sub(r"\sdata-geo[-\w]*\s*=\s*\"[^\"]*\"", "", attrs, flags=re.IGNORECASE)
|
||||||
|
attrs2 = re.sub(r"\s+", " ", attrs2).strip()
|
||||||
|
return f"{start} {attrs2}{end}" if attrs2 else f"{start}{end}"
|
||||||
|
return re.sub(r"(<(?:path|g)\b)([^>]*?)(/?>)", repl, svg, flags=re.IGNORECASE | re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_original_strokewidth(svg: str) -> str:
|
||||||
|
"""Remove data-originalStrokeWidth attributes from the SVG text."""
|
||||||
|
return re.sub(r"\sdata-originalStrokeWidth\s*=\s*\"[^\"]*\"", "", svg, flags=re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
|
def uppercase_data_iso(svg: str) -> str:
|
||||||
|
"""Uppercase all data-iso attribute values for consistency."""
|
||||||
|
return re.sub(r'data-iso\s*=\s*"([^\"]*)"', lambda m: f'data-iso="{m.group(1).strip().upper()}"', svg, flags=re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_fill_stroke(svg: str) -> str:
|
||||||
|
"""Remove inline fill, stroke, stroke-width and filter/style entries from path/g tags."""
|
||||||
|
def repl(m):
|
||||||
|
start, attrs, end = m.group(1), m.group(2), m.group(3)
|
||||||
|
# remove explicit attributes
|
||||||
|
attrs = re.sub(r"\s(?:fill|stroke|stroke-width)\s*=\s*\"[^\"]*\"", "", attrs, flags=re.IGNORECASE)
|
||||||
|
# strip fill/stroke/filter from style
|
||||||
|
def style_repl(mm):
|
||||||
|
"""Clean style attribute content by removing fill/stroke/filter entries."""
|
||||||
|
style = mm.group(1)
|
||||||
|
props = [p.strip() for p in style.split(";") if p.strip()]
|
||||||
|
keep = [p for p in props if p.split(":", 1)[0].strip().lower() not in ("fill", "stroke", "filter", "stroke-width")]
|
||||||
|
if not keep:
|
||||||
|
return ""
|
||||||
|
return f'style="{";".join(keep)}"'
|
||||||
|
attrs = re.sub(r'style\s*=\s*"([^"]*)"', style_repl, attrs, flags=re.IGNORECASE)
|
||||||
|
attrs = re.sub(r"\s+", " ", attrs).strip()
|
||||||
|
return f"{start} {attrs}{end}" if attrs else f"{start}{end}"
|
||||||
|
return re.sub(r"(<(?:path|g)\b)([^>]*?)(/?>)", repl, svg, flags=re.IGNORECASE | re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_empty_groups(svg: str) -> str:
|
||||||
|
"""Remove empty <g>...</g> groups from the SVG to tidy the markup."""
|
||||||
|
return re.sub(r"<g\b([^>]*)>\s*</g>", "", svg, flags=re.IGNORECASE | re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
|
def add_data_iso(svg: str, iso_path: Path) -> str:
|
||||||
|
"""Try to infer and add data-iso attributes using an ISO JSON mapping.
|
||||||
|
|
||||||
|
Looks at id, name, data-name attributes or inner <title> to guess a country
|
||||||
|
name and maps it to an ISO alpha-2 code using the provided JSON.
|
||||||
|
"""
|
||||||
|
if not iso_path.exists():
|
||||||
|
return svg
|
||||||
|
try:
|
||||||
|
with iso_path.open(encoding="utf-8") as fh:
|
||||||
|
mapping = json.load(fh)
|
||||||
|
except Exception:
|
||||||
|
return svg
|
||||||
|
norm_map = { normalize_name(v): k.upper() for k, v in mapping.items() if v }
|
||||||
|
|
||||||
|
# Prefer an XML-aware edit: parse and modify elements, then serialize.
|
||||||
|
try:
|
||||||
|
root = ET.fromstring(svg)
|
||||||
|
# detect and register default namespace if present so ET doesn't
|
||||||
|
# emit ns0 prefixes when serializing
|
||||||
|
ns_uri = None
|
||||||
|
if isinstance(root.tag, str) and root.tag.startswith('{'):
|
||||||
|
ns_uri = root.tag.split('}')[0].strip('{')
|
||||||
|
elif 'xmlns' in root.attrib:
|
||||||
|
ns_uri = root.attrib.get('xmlns')
|
||||||
|
if ns_uri:
|
||||||
|
ET.register_namespace('', ns_uri)
|
||||||
|
|
||||||
|
# iterate over all elements and handle local tag names (ignore namespace)
|
||||||
|
for elem in root.iter():
|
||||||
|
tag = elem.tag
|
||||||
|
local = tag.split('}', 1)[1] if '}' in tag else tag
|
||||||
|
if local not in ('path', 'g'):
|
||||||
|
continue
|
||||||
|
if 'data-iso' in elem.attrib:
|
||||||
|
continue
|
||||||
|
# candidate sources
|
||||||
|
cand = elem.get('id') or elem.get('name') or elem.get('data-name')
|
||||||
|
if not cand:
|
||||||
|
title = None
|
||||||
|
for child in elem:
|
||||||
|
ctag = child.tag
|
||||||
|
c_local = ctag.split('}', 1)[1] if '}' in ctag else ctag
|
||||||
|
if c_local == 'title' and child.text:
|
||||||
|
title = child.text
|
||||||
|
break
|
||||||
|
cand = title
|
||||||
|
if not cand:
|
||||||
|
continue
|
||||||
|
code = norm_map.get(normalize_name(cand))
|
||||||
|
if code:
|
||||||
|
elem.set('data-iso', code)
|
||||||
|
|
||||||
|
# serialize back to a string; namespace registration prevents ns0 prefixes
|
||||||
|
return ET.tostring(root, encoding='unicode')
|
||||||
|
except Exception:
|
||||||
|
# fallback: keep the original regex-based approach (conservative)
|
||||||
|
# non-self-closing
|
||||||
|
def repl_pair(m):
|
||||||
|
start, attrs, inner = m.group(1), m.group(2), m.group(3)
|
||||||
|
if re.search(r'data-iso\s*=\s*"[^\"]*"', attrs, flags=re.IGNORECASE):
|
||||||
|
return m.group(0)
|
||||||
|
cand = None
|
||||||
|
for pat in (r'id\s*=\s*"([^\"]*)"', r'name\s*=\s*"([^\"]*)"', r'data-name\s*=\s*"([^\"]*)"'):
|
||||||
|
mm = re.search(pat, attrs, flags=re.IGNORECASE)
|
||||||
|
if mm:
|
||||||
|
cand = mm.group(1)
|
||||||
|
break
|
||||||
|
if not cand:
|
||||||
|
t = re.search(r"<title>(.*?)</title>", inner, flags=re.IGNORECASE | re.DOTALL)
|
||||||
|
if t:
|
||||||
|
cand = t.group(1)
|
||||||
|
if not cand:
|
||||||
|
return m.group(0)
|
||||||
|
code = norm_map.get(normalize_name(cand))
|
||||||
|
if not code:
|
||||||
|
return m.group(0)
|
||||||
|
tag = start[1:]
|
||||||
|
attrs_str = attrs.strip()
|
||||||
|
mid = f" {attrs_str}" if attrs_str else ""
|
||||||
|
return f"{start}{mid} data-iso=\"{code}\">{inner}</{tag}>"
|
||||||
|
|
||||||
|
svg = re.sub(r"(<(?:path|g)\b)([^>]*?)>(.*?)</(?:path|g)\s*>", repl_pair, svg, flags=re.IGNORECASE | re.DOTALL)
|
||||||
|
|
||||||
|
# self-closing
|
||||||
|
def repl_self(m):
|
||||||
|
start, attrs, tail = m.group(1), m.group(2), m.group(3)
|
||||||
|
if re.search(r'data-iso\s*=\s*"[^\"]*"', attrs, flags=re.IGNORECASE):
|
||||||
|
return m.group(0)
|
||||||
|
cand = None
|
||||||
|
for pat in (r'id\s*=\s*"([^\"]*)"', r'name\s*=\s*"([^\"]*)"', r'data-name\s*=\s*"([^\"]*)"'):
|
||||||
|
mm = re.search(pat, attrs, flags=re.IGNORECASE)
|
||||||
|
if mm:
|
||||||
|
cand = mm.group(1)
|
||||||
|
break
|
||||||
|
if not cand:
|
||||||
|
return m.group(0)
|
||||||
|
code = norm_map.get(normalize_name(cand))
|
||||||
|
if not code:
|
||||||
|
return m.group(0)
|
||||||
|
attrs_str = attrs.strip()
|
||||||
|
mid = f" {attrs_str}" if attrs_str else ""
|
||||||
|
return f"{start}{mid} data-iso=\"{code}\"{tail}"
|
||||||
|
|
||||||
|
svg = re.sub(r"(<(?:path|g)\b)([^>]*?)(/>)", repl_self, svg, flags=re.IGNORECASE | re.DOTALL)
|
||||||
|
return svg
|
||||||
|
|
||||||
|
# ---------------------- main flow ----------------------
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
"""CLI entrypoint: parse arguments, run the pipeline and optionally write the file."""
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--in-place", action="store_true")
|
||||||
|
parser.add_argument("--file", type=Path, default=FILE_PATH)
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
svg_path = args.file
|
||||||
|
if not svg_path.exists():
|
||||||
|
print("SVG not found:", svg_path)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
original = read_text(svg_path)
|
||||||
|
|
||||||
|
# extract text blocks to protect them
|
||||||
|
text_blocks = []
|
||||||
|
def extract_text(s):
|
||||||
|
"""Extract <text>...</text> blocks and replace them with unique markers."""
|
||||||
|
nonlocal text_blocks
|
||||||
|
pat = re.compile(r"(<text\b[^>]*>.*?</text\s*>)", flags=re.IGNORECASE | re.DOTALL)
|
||||||
|
def r(m):
|
||||||
|
idx = len(text_blocks)
|
||||||
|
text_blocks.append(m.group(1))
|
||||||
|
return f"<!--__TEXT_BLOCK_{idx}__-->"
|
||||||
|
return pat.sub(r, s)
|
||||||
|
|
||||||
|
def restore_text(s):
|
||||||
|
"""Restore previously extracted <text> blocks back into the SVG string."""
|
||||||
|
for i, b in enumerate(text_blocks):
|
||||||
|
s = s.replace(f"<!--__TEXT_BLOCK_{i}__-->", b)
|
||||||
|
return s
|
||||||
|
|
||||||
|
svg = extract_text(original)
|
||||||
|
|
||||||
|
steps = [
|
||||||
|
(extract_inner_svg, "extract_inner_svg"),
|
||||||
|
(add_svg_attributes, "add_svg_attributes"),
|
||||||
|
(collapse_path_tags, "collapse_path_tags"),
|
||||||
|
(remove_defs, "remove_defs"),
|
||||||
|
(lambda s: add_data_iso(s, ISO_JSON), "add_data_iso"),
|
||||||
|
(remove_data_geo, "remove_data_geo"),
|
||||||
|
(remove_original_strokewidth, "remove_original_strokewidth"),
|
||||||
|
(uppercase_data_iso, "uppercase_data_iso"),
|
||||||
|
(clear_fill_stroke, "clear_fill_stroke"),
|
||||||
|
(remove_empty_groups, "remove_empty_groups"),
|
||||||
|
(lambda s: s, "noop_compact"),
|
||||||
|
]
|
||||||
|
|
||||||
|
last_good = svg
|
||||||
|
for func, name in steps:
|
||||||
|
print(f"[stage] {name}")
|
||||||
|
try:
|
||||||
|
svg = func(svg)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR in {name}: {e}")
|
||||||
|
svg = last_good
|
||||||
|
break
|
||||||
|
ok, msg = validate_xml(svg)
|
||||||
|
if not ok:
|
||||||
|
print(f"Invalid XML after {name}: {msg}")
|
||||||
|
svg = last_good
|
||||||
|
break
|
||||||
|
last_good = svg
|
||||||
|
|
||||||
|
# pretty print
|
||||||
|
print("[stage] pretty_format")
|
||||||
|
try:
|
||||||
|
dom = minidom.parseString(svg)
|
||||||
|
# remove whitespace-only text nodes to avoid accumulating blank lines
|
||||||
|
strip_whitespace_text_nodes(dom)
|
||||||
|
pretty = dom.toprettyxml(indent=" ")
|
||||||
|
# remove xml decl if present
|
||||||
|
if pretty.startswith("<?xml"):
|
||||||
|
pretty = "\n".join(pretty.splitlines()[1:]) + "\n"
|
||||||
|
svg_out = pretty
|
||||||
|
except Exception:
|
||||||
|
svg_out = svg
|
||||||
|
|
||||||
|
# restore text blocks after pretty-format so their internal formatting
|
||||||
|
# is preserved and not mangled by the DOM pass.
|
||||||
|
svg_out = restore_text(svg_out)
|
||||||
|
|
||||||
|
if args.in_place:
|
||||||
|
bak = backup(svg_path)
|
||||||
|
write_text(svg_path, svg_out)
|
||||||
|
print(f"Wrote {svg_path} (backup: {bak})")
|
||||||
|
else:
|
||||||
|
print("Dry run - changes ready. Preview (head):\n")
|
||||||
|
print(svg_out[:2000])
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -166,8 +166,7 @@
|
|||||||
<div class="right-column">
|
<div class="right-column">
|
||||||
{#if logo.tags && logo.tags.some((tagObj) => (tagObj.text || tagObj) === "Country") && logo.meta && logo.meta["ISO code"]}
|
{#if logo.tags && logo.tags.some((tagObj) => (tagObj.text || tagObj) === "Country") && logo.meta && logo.meta["ISO code"]}
|
||||||
<CountryMap
|
<CountryMap
|
||||||
countryCodes={[logo.meta["ISO code"]]}
|
countryCodes={[String(logo.meta["ISO code"]).trim().toUpperCase()]}
|
||||||
countryNames={[logo.meta["country"]]}
|
|
||||||
countryScale={true}
|
countryScale={true}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -317,15 +316,15 @@
|
|||||||
|
|
||||||
.preview-container {
|
.preview-container {
|
||||||
flex: 3;
|
flex: 3;
|
||||||
display: flex;
|
/* display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
background-color: var(--color-card);
|
background-color: var(--color-card); */
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
overflow: hidden;
|
/* overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
contain: layout style paint; /* CSS containment */
|
contain: layout style paint; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-container img {
|
.preview-container img {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { afterUpdate, onMount } from "svelte";
|
import { afterUpdate, onMount } from "svelte";
|
||||||
import InlineSvg from "./InlineSvg.svelte";
|
import InlineSvg from "./InlineSvg.svelte";
|
||||||
export let countryCodes = [];
|
export let countryCodes = [];
|
||||||
export let countryNames = [];
|
|
||||||
export let forceCenterKey = 0;
|
export let forceCenterKey = 0;
|
||||||
export let mapPath = "/data/world.svg";
|
export let mapPath = "/data/world.svg";
|
||||||
export let countryScale = false;
|
export let countryScale = false;
|
||||||
@@ -58,174 +57,67 @@
|
|||||||
let svgSeen = false; // whether we've already run the initial highlight for the inlined SVG
|
let svgSeen = false; // whether we've already run the initial highlight for the inlined SVG
|
||||||
|
|
||||||
function highlightCountries() {
|
function highlightCountries() {
|
||||||
// backward-compatible signature: allow passing a force flag
|
|
||||||
const args = Array.from(arguments);
|
const args = Array.from(arguments);
|
||||||
const forceCenter = args && args[0] === true;
|
const forceCenter = args && args[0] === true;
|
||||||
if (!wrapperRef) return;
|
if (!wrapperRef) return;
|
||||||
const svgEl = wrapperRef.querySelector("svg");
|
const svgEl = wrapperRef.querySelector("svg");
|
||||||
if (!svgEl) return;
|
if (!svgEl) return;
|
||||||
// Clear previous highlights robustly. Try to restore any saved inline
|
// Simple cleanup: remove any inline fill/stroke/filter and any
|
||||||
// attributes; if they are missing (e.g. due to re-mounts) also remove
|
// data-geo* helper attributes on all path and g elements. We don't
|
||||||
// the highlight styling if present.
|
// keep or restore previous values; just clear styling before
|
||||||
|
// applying new highlight styles.
|
||||||
try {
|
try {
|
||||||
const candidateSelectors = 'path, g, polygon, rect, circle, ellipse, use';
|
const els = Array.from(svgEl.querySelectorAll('path, g'));
|
||||||
const allEls = Array.from(svgEl.querySelectorAll(candidateSelectors));
|
els.forEach((el) => {
|
||||||
allEls.forEach((el) => {
|
|
||||||
try {
|
try {
|
||||||
const prevFill = el.getAttribute('data-geo-prev-fill');
|
// Clear basic inline styling
|
||||||
const prevStroke = el.getAttribute('data-geo-prev-stroke');
|
el.removeAttribute('fill');
|
||||||
const prevFilter = el.getAttribute('data-geo-prev-filter');
|
el.removeAttribute('stroke');
|
||||||
|
if (el.style) el.style.filter = '';
|
||||||
|
|
||||||
// If we saved explicit previous values, restore them (empty string means remove attr)
|
// Remove any attributes that start with 'data-geo'
|
||||||
if (prevFill !== null) {
|
|
||||||
if (prevFill === '') el.removeAttribute('fill');
|
|
||||||
else el.setAttribute('fill', prevFill);
|
|
||||||
} else {
|
|
||||||
// no saved value: if the element currently has the highlight color, remove it
|
|
||||||
try {
|
|
||||||
const curFill = el.getAttribute && el.getAttribute('fill');
|
|
||||||
if (curFill && curFill.toLowerCase() === '#4f8cff') el.removeAttribute('fill');
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevStroke !== null) {
|
|
||||||
if (prevStroke === '') el.removeAttribute('stroke');
|
|
||||||
else el.setAttribute('stroke', prevStroke);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const curStroke = el.getAttribute && el.getAttribute('stroke');
|
|
||||||
if (curStroke && curStroke.toLowerCase() === '#222') el.removeAttribute('stroke');
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevFilter !== null) {
|
|
||||||
if (prevFilter === '') el.style.filter = '';
|
|
||||||
else el.style.filter = prevFilter;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const cf = el.style && el.style.filter;
|
|
||||||
if (cf && cf.indexOf('4f8cff') !== -1) el.style.filter = '';
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up helper attributes if present
|
|
||||||
try {
|
try {
|
||||||
el.removeAttribute('data-geo-prev-fill');
|
const attrs = Array.from(el.attributes || []);
|
||||||
el.removeAttribute('data-geo-prev-stroke');
|
attrs.forEach((a) => {
|
||||||
el.removeAttribute('data-geo-prev-filter');
|
try {
|
||||||
el.removeAttribute('data-geo-highlight');
|
if (a && typeof a.name === 'string' && a.name.indexOf('data-geo') === 0) {
|
||||||
|
el.removeAttribute(a.name);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
} catch (err) {}
|
} catch (e) {}
|
||||||
});
|
});
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
||||||
// Highlight by country code (id)
|
// Highlight by country ISO using data-iso attribute only (case-insensitive via uppercasing)
|
||||||
let highlighted = [];
|
let highlighted = [];
|
||||||
countryCodes.forEach((code) => {
|
countryCodes.forEach((code) => {
|
||||||
const countryPath = svgEl.querySelector(`#${code}`);
|
try {
|
||||||
if (countryPath) {
|
if (!code) return;
|
||||||
// Save previous inline attributes so we can restore later
|
const iso = String(code).trim().toUpperCase();
|
||||||
const prevFill = countryPath.getAttribute('fill');
|
// Prefer elements that explicitly declare data-iso
|
||||||
const prevStroke = countryPath.getAttribute('stroke');
|
let countryPath = null;
|
||||||
const prevFilter = countryPath.style.filter || '';
|
try {
|
||||||
if (prevFill !== null) countryPath.setAttribute('data-geo-prev-fill', prevFill);
|
countryPath = svgEl.querySelector(`[data-iso="${iso}"]`);
|
||||||
else countryPath.setAttribute('data-geo-prev-fill', '');
|
} catch (err) {
|
||||||
if (prevStroke !== null) countryPath.setAttribute('data-geo-prev-stroke', prevStroke);
|
// Fallback: try lowercase attribute selector
|
||||||
else countryPath.setAttribute('data-geo-prev-stroke', '');
|
|
||||||
countryPath.setAttribute('data-geo-prev-filter', prevFilter);
|
|
||||||
|
|
||||||
countryPath.setAttribute("fill", "#4f8cff");
|
|
||||||
countryPath.setAttribute("stroke", "#222");
|
|
||||||
countryPath.style.filter = "drop-shadow(0 0 4px #4f8cff44)";
|
|
||||||
countryPath.setAttribute('data-geo-highlight', '1');
|
|
||||||
highlighted.push(countryPath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Highlight by country name (try many fallbacks: attributes, class names, <title> children)
|
|
||||||
const names = Array.isArray(countryNames) ? countryNames : countryNames ? [countryNames] : [];
|
|
||||||
if (names.length > 0) {
|
|
||||||
// candidate elements to check for name matches
|
|
||||||
const candidateSelectors = 'path, g, polygon, rect, circle, ellipse, use';
|
|
||||||
const candidates = Array.from(svgEl.querySelectorAll(candidateSelectors));
|
|
||||||
|
|
||||||
names.forEach((nameRaw) => {
|
|
||||||
if (!nameRaw) return;
|
|
||||||
const name = String(nameRaw).trim();
|
|
||||||
if (!name) return;
|
|
||||||
const nameLower = name.toLowerCase();
|
|
||||||
|
|
||||||
const matched = new Set();
|
|
||||||
|
|
||||||
// First pass: strict attribute/title/id matches (case-insensitive)
|
|
||||||
try {
|
try {
|
||||||
const attrsToCheck = ['name', 'data-name', 'id', 'title', 'inkscape:label'];
|
countryPath = svgEl.querySelector(`[data-iso='${iso.toLowerCase()}']`);
|
||||||
candidates.forEach((el) => {
|
} catch (err2) {
|
||||||
for (const a of attrsToCheck) {
|
countryPath = null;
|
||||||
try {
|
|
||||||
const val = el.getAttribute && el.getAttribute(a);
|
|
||||||
if (val && String(val).trim().toLowerCase() === nameLower) {
|
|
||||||
matched.add(el);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (err) {}
|
|
||||||
}
|
|
||||||
// child <title> element (redundant with 'title' attr check but kept safe)
|
|
||||||
try {
|
|
||||||
const titleEl = el.querySelector && el.querySelector('title');
|
|
||||||
if (titleEl && titleEl.textContent && titleEl.textContent.trim().toLowerCase() === nameLower) {
|
|
||||||
matched.add(el);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (err) {}
|
|
||||||
});
|
|
||||||
} catch (err) {}
|
|
||||||
|
|
||||||
// If we didn't find any strict matches, fall back to class/slug heuristics
|
|
||||||
if (matched.size === 0) {
|
|
||||||
try {
|
|
||||||
// Try direct selectors only when safe
|
|
||||||
try {
|
|
||||||
const byNameAttr = svgEl.querySelectorAll(`[name='${name}']`);
|
|
||||||
byNameAttr.forEach((el) => matched.add(el));
|
|
||||||
} catch (err) {}
|
|
||||||
try {
|
|
||||||
const byClassDot = svgEl.querySelectorAll(`.${name.replace(/\s+/g, '.')}`);
|
|
||||||
byClassDot.forEach((el) => matched.add(el));
|
|
||||||
} catch (err) {}
|
|
||||||
|
|
||||||
// class list heuristics: slug (lower, dashes)
|
|
||||||
candidates.forEach((el) => {
|
|
||||||
try {
|
|
||||||
const cls = (el.className && (typeof el.className === 'string' ? el.className : el.className.baseVal)) || '';
|
|
||||||
if (cls) {
|
|
||||||
const parts = cls.split(/\s+/).map((c) => c.trim()).filter(Boolean);
|
|
||||||
const slug = nameLower.replace(/[^a-z0-9]+/g, '-').replace(/^\-+|\-+$/g, '');
|
|
||||||
if (parts.includes(name) || parts.includes(nameLower) || parts.includes(slug)) matched.add(el);
|
|
||||||
}
|
|
||||||
} catch (err) {}
|
|
||||||
});
|
|
||||||
} catch (err) {}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Apply highlighting to matched elements
|
if (countryPath) {
|
||||||
matched.forEach((countryPath) => {
|
|
||||||
const prevFill = countryPath.getAttribute('fill');
|
|
||||||
const prevStroke = countryPath.getAttribute('stroke');
|
|
||||||
const prevFilter = countryPath.style.filter || '';
|
|
||||||
if (prevFill !== null) countryPath.setAttribute('data-geo-prev-fill', prevFill);
|
|
||||||
else countryPath.setAttribute('data-geo-prev-fill', '');
|
|
||||||
if (prevStroke !== null) countryPath.setAttribute('data-geo-prev-stroke', prevStroke);
|
|
||||||
else countryPath.setAttribute('data-geo-prev-stroke', '');
|
|
||||||
countryPath.setAttribute('data-geo-prev-filter', prevFilter);
|
|
||||||
|
|
||||||
countryPath.setAttribute('fill', '#4f8cff');
|
countryPath.setAttribute('fill', '#4f8cff');
|
||||||
countryPath.setAttribute('stroke', '#222');
|
countryPath.setAttribute('stroke', '#222');
|
||||||
countryPath.style.filter = 'drop-shadow(0 0 4px #4f8cff44)';
|
countryPath.style.filter = 'drop-shadow(0 0 4px #4f8cff44)';
|
||||||
countryPath.setAttribute('data-geo-highlight', '1');
|
countryPath.setAttribute('data-geo-highlight', '1');
|
||||||
highlighted.push(countryPath);
|
highlighted.push(countryPath);
|
||||||
});
|
}
|
||||||
});
|
} catch (err) {}
|
||||||
}
|
});
|
||||||
// Smart scale/center if enabled and at least one country is highlighted
|
// Smart scale/center if enabled and at least one country is highlighted
|
||||||
if (countryScale && highlighted.length > 0) {
|
if (countryScale && highlighted.length > 0) {
|
||||||
// Compute bounding box of all highlighted paths
|
// Compute bounding box of all highlighted paths
|
||||||
|
|||||||
@@ -228,7 +228,7 @@
|
|||||||
|
|
||||||
.svg-wrapper :global(svg) {
|
.svg-wrapper :global(svg) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
display: block;
|
display: block;
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
|
|||||||
@@ -144,10 +144,6 @@
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-card.scheduled .play-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coming-soon-text {
|
.coming-soon-text {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
@@ -187,38 +183,6 @@
|
|||||||
background: var(--color-primary-dark);
|
background: var(--color-primary-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.coming-soon {
|
|
||||||
text-align: center;
|
|
||||||
padding: 2rem;
|
|
||||||
background: var(--color-bg-secondary);
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px dashed var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.coming-soon h3 {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upcoming-games {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 2rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upcoming-game {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upcoming-game .icon {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.container {
|
.container {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
@@ -233,10 +197,5 @@
|
|||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upcoming-games {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -50,10 +50,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isFlagOnMap(flag) {
|
function isFlagOnMap(flag) {
|
||||||
if (!mapParsed) return true; // allow through if we couldn't parse map
|
// Prefer ISO comparison against data-iso values. If map couldn't be parsed, allow.
|
||||||
|
if (!mapParsed) return true;
|
||||||
try {
|
try {
|
||||||
const iso = flag?.meta?.["ISO code"] || flag?.meta?.["ISO Code"] || flag?.meta?.["ISO"] || flag?.ISO;
|
const isoRaw = flag?.meta?.["ISO code"] || flag?.meta?.["ISO Code"] || flag?.meta?.["ISO"] || flag?.ISO;
|
||||||
if (iso && availableIso.has(String(iso).trim())) return true;
|
const iso = isoRaw ? String(isoRaw).trim().toUpperCase() : '';
|
||||||
|
if (iso && availableIso.has(iso)) return true;
|
||||||
|
|
||||||
|
// Fallbacks to name/slug checks remain, but only used if no ISO matches.
|
||||||
const name = getCountryName(flag);
|
const name = getCountryName(flag);
|
||||||
const n = normalizeNameForLookup(name);
|
const n = normalizeNameForLookup(name);
|
||||||
if (n && availableNames.has(n)) return true;
|
if (n && availableNames.has(n)) return true;
|
||||||
@@ -206,8 +210,13 @@
|
|||||||
const all = Array.from(doc.querySelectorAll('*'));
|
const all = Array.from(doc.querySelectorAll('*'));
|
||||||
all.forEach((el) => {
|
all.forEach((el) => {
|
||||||
try {
|
try {
|
||||||
|
// Prefer data-iso attribute for ISO lookup. Normalize to uppercase.
|
||||||
|
const dataIso = el.getAttribute && (el.getAttribute('data-iso') || el.getAttribute('data-ISO'));
|
||||||
|
if (dataIso) availableIso.add(String(dataIso).trim().toUpperCase());
|
||||||
|
// Backwards-compatible: also include id if present (older maps)
|
||||||
const id = el.getAttribute && el.getAttribute('id');
|
const id = el.getAttribute && el.getAttribute('id');
|
||||||
if (id) availableIso.add(String(id).trim());
|
if (id) availableIso.add(String(id).trim().toUpperCase());
|
||||||
|
|
||||||
const nameAttr = el.getAttribute && (el.getAttribute('name') || el.getAttribute('data-name') || el.getAttribute('inkscape:label'));
|
const nameAttr = el.getAttribute && (el.getAttribute('name') || el.getAttribute('data-name') || el.getAttribute('inkscape:label'));
|
||||||
if (nameAttr) availableNames.add(String(nameAttr).trim().toLowerCase());
|
if (nameAttr) availableNames.add(String(nameAttr).trim().toLowerCase());
|
||||||
// title child
|
// title child
|
||||||
@@ -570,18 +579,12 @@
|
|||||||
currentQuestion.correct.meta["ISO code"]) ||
|
currentQuestion.correct.meta["ISO code"]) ||
|
||||||
currentQuestion.correct?.ISO ||
|
currentQuestion.correct?.ISO ||
|
||||||
"",
|
"",
|
||||||
].filter(Boolean)
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((c) => (c ? String(c).trim().toUpperCase() : c))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
// Reactive country names array for CountryMap (fallback when no ISO present)
|
|
||||||
$: countryNames = currentQuestion
|
|
||||||
? [
|
|
||||||
currentQuestion.correct?.name ||
|
|
||||||
(currentQuestion.correct?.meta && currentQuestion.correct.meta.country) ||
|
|
||||||
currentQuestion.correct?.meta?.country ||
|
|
||||||
"",
|
|
||||||
].filter(Boolean)
|
|
||||||
: [];
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -660,8 +663,7 @@
|
|||||||
|
|
||||||
<div class="map-display">
|
<div class="map-display">
|
||||||
<CountryMap
|
<CountryMap
|
||||||
{countryCodes}
|
{countryCodes}
|
||||||
{countryNames}
|
|
||||||
countryScale={true}
|
countryScale={true}
|
||||||
scalePadding={90}
|
scalePadding={90}
|
||||||
forceCenterKey={questionKey}
|
forceCenterKey={questionKey}
|
||||||
|
|||||||