Upload 18 files
Browse files- cologne-christmas-markets-map-2025_5/.env.local +1 -0
- cologne-christmas-markets-map-2025_5/.gitignore +24 -0
- cologne-christmas-markets-map-2025_5/App.tsx +58 -0
- cologne-christmas-markets-map-2025_5/README.md +20 -0
- cologne-christmas-markets-map-2025_5/components/ImageCarousel.tsx +79 -0
- cologne-christmas-markets-map-2025_5/components/MapComponent.tsx +141 -0
- cologne-christmas-markets-map-2025_5/components/MarketDetailModal.tsx +81 -0
- cologne-christmas-markets-map-2025_5/components/MarketList.tsx +45 -0
- cologne-christmas-markets-map-2025_5/components/MarketListItem.tsx +37 -0
- cologne-christmas-markets-map-2025_5/components/WeatherWidget.tsx +121 -0
- cologne-christmas-markets-map-2025_5/constants.ts +578 -0
- cologne-christmas-markets-map-2025_5/index.html +236 -0
- cologne-christmas-markets-map-2025_5/index.tsx +16 -0
- cologne-christmas-markets-map-2025_5/metadata.json +7 -0
- cologne-christmas-markets-map-2025_5/package.json +24 -0
- cologne-christmas-markets-map-2025_5/tsconfig.json +29 -0
- cologne-christmas-markets-map-2025_5/types.ts +15 -0
- cologne-christmas-markets-map-2025_5/vite.config.ts +23 -0
cologne-christmas-markets-map-2025_5/.env.local
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
GEMINI_API_KEY=PLACEHOLDER_API_KEY
|
cologne-christmas-markets-map-2025_5/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Logs
|
| 2 |
+
logs
|
| 3 |
+
*.log
|
| 4 |
+
npm-debug.log*
|
| 5 |
+
yarn-debug.log*
|
| 6 |
+
yarn-error.log*
|
| 7 |
+
pnpm-debug.log*
|
| 8 |
+
lerna-debug.log*
|
| 9 |
+
|
| 10 |
+
node_modules
|
| 11 |
+
dist
|
| 12 |
+
dist-ssr
|
| 13 |
+
*.local
|
| 14 |
+
|
| 15 |
+
# Editor directories and files
|
| 16 |
+
.vscode/*
|
| 17 |
+
!.vscode/extensions.json
|
| 18 |
+
.idea
|
| 19 |
+
.DS_Store
|
| 20 |
+
*.suo
|
| 21 |
+
*.ntvs*
|
| 22 |
+
*.njsproj
|
| 23 |
+
*.sln
|
| 24 |
+
*.sw?
|
cologne-christmas-markets-map-2025_5/App.tsx
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useCallback } from 'react';
|
| 2 |
+
import MapComponent from './components/MapComponent';
|
| 3 |
+
import MarketList from './components/MarketList';
|
| 4 |
+
import { MARKETS } from './constants';
|
| 5 |
+
import type { Market } from './types';
|
| 6 |
+
|
| 7 |
+
const Snowfall: React.FC = () => (
|
| 8 |
+
<div className="absolute top-0 left-0 w-full h-full pointer-events-none" aria-hidden="true">
|
| 9 |
+
{[...Array(12)].map((_, i) => (
|
| 10 |
+
<div key={i} className="snowflake">❅</div>
|
| 11 |
+
))}
|
| 12 |
+
</div>
|
| 13 |
+
);
|
| 14 |
+
|
| 15 |
+
const App: React.FC = () => {
|
| 16 |
+
const [selectedMarket, setSelectedMarket] = useState<Market | null>(null);
|
| 17 |
+
const [hoveredMarketId, setHoveredMarketId] = useState<number | null>(null);
|
| 18 |
+
const [mapCenter, setMapCenter] = useState<[number, number]>([50.9375, 6.9603]);
|
| 19 |
+
const [mapZoom, setMapZoom] = useState<number>(13);
|
| 20 |
+
|
| 21 |
+
const handleSelectMarket = useCallback((market: Market | null) => {
|
| 22 |
+
setSelectedMarket(market);
|
| 23 |
+
if (market) {
|
| 24 |
+
setMapCenter([market.lat, market.lon]);
|
| 25 |
+
setMapZoom(15);
|
| 26 |
+
}
|
| 27 |
+
}, []);
|
| 28 |
+
|
| 29 |
+
const handleMarketHover = useCallback((marketId: number | null) => {
|
| 30 |
+
setHoveredMarketId(marketId);
|
| 31 |
+
}, []);
|
| 32 |
+
|
| 33 |
+
return (
|
| 34 |
+
<div className="flex flex-col md:flex-row h-screen bg-stone-900 text-stone-200 relative">
|
| 35 |
+
<Snowfall />
|
| 36 |
+
<MarketList
|
| 37 |
+
markets={MARKETS}
|
| 38 |
+
selectedMarket={selectedMarket}
|
| 39 |
+
hoveredMarketId={hoveredMarketId}
|
| 40 |
+
onSelectMarket={handleSelectMarket}
|
| 41 |
+
onMarketHover={handleMarketHover}
|
| 42 |
+
/>
|
| 43 |
+
<div className="flex-grow h-full w-full relative">
|
| 44 |
+
<MapComponent
|
| 45 |
+
markets={MARKETS}
|
| 46 |
+
selectedMarket={selectedMarket}
|
| 47 |
+
hoveredMarketId={hoveredMarketId}
|
| 48 |
+
onSelectMarket={handleSelectMarket}
|
| 49 |
+
onMarketHover={handleMarketHover}
|
| 50 |
+
mapCenter={mapCenter}
|
| 51 |
+
mapZoom={mapZoom}
|
| 52 |
+
/>
|
| 53 |
+
</div>
|
| 54 |
+
</div>
|
| 55 |
+
);
|
| 56 |
+
};
|
| 57 |
+
|
| 58 |
+
export default App;
|
cologne-christmas-markets-map-2025_5/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div align="center">
|
| 2 |
+
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
| 3 |
+
</div>
|
| 4 |
+
|
| 5 |
+
# Run and deploy your AI Studio app
|
| 6 |
+
|
| 7 |
+
This contains everything you need to run your app locally.
|
| 8 |
+
|
| 9 |
+
View your app in AI Studio: https://ai.studio/apps/drive/1oqoCQFp46GFqSBo-A5v8rRnvagnv70Wc
|
| 10 |
+
|
| 11 |
+
## Run Locally
|
| 12 |
+
|
| 13 |
+
**Prerequisites:** Node.js
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
1. Install dependencies:
|
| 17 |
+
`npm install`
|
| 18 |
+
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
| 19 |
+
3. Run the app:
|
| 20 |
+
`npm run dev`
|
cologne-christmas-markets-map-2025_5/components/ImageCarousel.tsx
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect, useCallback } from 'react';
|
| 2 |
+
|
| 3 |
+
interface ImageCarouselProps {
|
| 4 |
+
images: string[];
|
| 5 |
+
marketName: string;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
const ImageCarousel: React.FC<ImageCarouselProps> = ({ images, marketName }) => {
|
| 9 |
+
const [currentIndex, setCurrentIndex] = useState(0);
|
| 10 |
+
|
| 11 |
+
const goToNext = useCallback(() => {
|
| 12 |
+
setCurrentIndex((prevIndex) => (prevIndex === images.length - 1 ? 0 : prevIndex + 1));
|
| 13 |
+
}, [images.length]);
|
| 14 |
+
|
| 15 |
+
const goToPrevious = () => {
|
| 16 |
+
setCurrentIndex((prevIndex) => (prevIndex === 0 ? images.length - 1 : prevIndex - 1));
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
const goToSlide = (slideIndex: number) => {
|
| 20 |
+
setCurrentIndex(slideIndex);
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
useEffect(() => {
|
| 24 |
+
if (images.length > 1) {
|
| 25 |
+
const timer = setTimeout(goToNext, 4000); // Autoplay every 4 seconds
|
| 26 |
+
return () => clearTimeout(timer);
|
| 27 |
+
}
|
| 28 |
+
}, [currentIndex, images.length, goToNext]);
|
| 29 |
+
|
| 30 |
+
if (!images || images.length === 0) {
|
| 31 |
+
return null;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
return (
|
| 35 |
+
<div className="relative w-full h-32 group">
|
| 36 |
+
<div className="w-full h-full overflow-hidden rounded-t-lg">
|
| 37 |
+
{images.map((image, index) => (
|
| 38 |
+
<img
|
| 39 |
+
key={index}
|
| 40 |
+
src={image}
|
| 41 |
+
alt={`${marketName} - image ${index + 1}`}
|
| 42 |
+
className={`absolute top-0 left-0 w-full h-full object-cover transition-opacity duration-700 ease-in-out ${index === currentIndex ? 'opacity-100' : 'opacity-0'}`}
|
| 43 |
+
/>
|
| 44 |
+
))}
|
| 45 |
+
</div>
|
| 46 |
+
|
| 47 |
+
{images.length > 1 && (
|
| 48 |
+
<>
|
| 49 |
+
<button
|
| 50 |
+
onClick={goToPrevious}
|
| 51 |
+
className="absolute top-1/2 left-1 transform -translate-y-1/2 bg-black/40 text-white p-1 rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
|
| 52 |
+
aria-label="Previous image"
|
| 53 |
+
>
|
| 54 |
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" /></svg>
|
| 55 |
+
</button>
|
| 56 |
+
<button
|
| 57 |
+
onClick={goToNext}
|
| 58 |
+
className="absolute top-1/2 right-1 transform -translate-y-1/2 bg-black/40 text-white p-1 rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
|
| 59 |
+
aria-label="Next image"
|
| 60 |
+
>
|
| 61 |
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /></svg>
|
| 62 |
+
</button>
|
| 63 |
+
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 flex space-x-2">
|
| 64 |
+
{images.map((_, slideIndex) => (
|
| 65 |
+
<button
|
| 66 |
+
key={slideIndex}
|
| 67 |
+
onClick={() => goToSlide(slideIndex)}
|
| 68 |
+
className={`w-2 h-2 rounded-full transition-colors ${currentIndex === slideIndex ? 'bg-white' : 'bg-white/50'}`}
|
| 69 |
+
aria-label={`Go to slide ${slideIndex + 1}`}
|
| 70 |
+
></button>
|
| 71 |
+
))}
|
| 72 |
+
</div>
|
| 73 |
+
</>
|
| 74 |
+
)}
|
| 75 |
+
</div>
|
| 76 |
+
);
|
| 77 |
+
};
|
| 78 |
+
|
| 79 |
+
export default ImageCarousel;
|
cologne-christmas-markets-map-2025_5/components/MapComponent.tsx
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useEffect, useState, useRef } from 'react';
|
| 2 |
+
import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet';
|
| 3 |
+
import L from 'leaflet';
|
| 4 |
+
import type { Market } from '../types';
|
| 5 |
+
import ImageCarousel from './ImageCarousel';
|
| 6 |
+
|
| 7 |
+
interface MapComponentProps {
|
| 8 |
+
markets: Market[];
|
| 9 |
+
selectedMarket: Market | null;
|
| 10 |
+
hoveredMarketId: number | null;
|
| 11 |
+
onSelectMarket: (market: Market) => void;
|
| 12 |
+
onMarketHover: (marketId: number | null) => void;
|
| 13 |
+
mapCenter: [number, number];
|
| 14 |
+
mapZoom: number;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
const categoryIcons: Record<Market['category'], string> = {
|
| 18 |
+
Traditional: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="{fill}" width="{width}" height="{height}" style="filter: drop-shadow(2px 2px 2px rgba(0,0,0,0.5));"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>`,
|
| 19 |
+
Themed: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="{fill}" width="{width}" height="{height}" style="filter: drop-shadow(2px 2px 2px rgba(0,0,0,0.5));"><path d="M22 11h-2V8.34l2-1.73-1-1.73-2 1.73V4h-2v2.66l-1.45-1.25-1 1.73L14.34 8H12v3.66l-1.55-1.34-1 1.73L11.66 12H9.34l2.21-2.55-1-1.73L9 9.34V7H7v2.34l-1.45-1.25-1 1.73L6.34 11H4v2h2.34l-1.73 2 1 1.73 1.73-2V17h2v-2.34l1.45 1.25 1-1.73L9.66 13h2.68l-2.21 2.55 1 1.73L12 15.66V18h2v-2.34l1.45 1.25 1-1.73L14.34 14h2.32l-1.73 2 1 1.73 2-1.73V13h2z"/></svg>`,
|
| 20 |
+
Community: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="{fill}" width="{width}" height="{height}" style="filter: drop-shadow(2px 2px 2px rgba(0,0,0,0.5));"><path d="M12 5.69l5 4.5V18h-2v-6H9v6H7v-7.81l5-4.5M12 3L2 12h3v8h6v-6h2v6h6v-8h3L12 3z"/></svg>`,
|
| 21 |
+
'Short-Term': `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="{fill}" width="{width}" height="{height}" style="filter: drop-shadow(2px 2px 2px rgba(0,0,0,0.5));"><path d="M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V10h14v10zm-5-7h-4v4h4v-4z"/></svg>`,
|
| 22 |
+
};
|
| 23 |
+
|
| 24 |
+
const createIcon = (category: Market['category'], color: string, size: number, className: string = '') => {
|
| 25 |
+
const svgIcon = categoryIcons[category]
|
| 26 |
+
.replace('{fill}', color)
|
| 27 |
+
.replace('{width}', `${size}px`)
|
| 28 |
+
.replace('{height}', `${size}px`);
|
| 29 |
+
|
| 30 |
+
return L.divIcon({
|
| 31 |
+
html: `<div class="${className}">${svgIcon}</div>`,
|
| 32 |
+
className: 'bg-transparent border-0',
|
| 33 |
+
iconSize: [size, size],
|
| 34 |
+
iconAnchor: [size / 2, size / 2],
|
| 35 |
+
});
|
| 36 |
+
};
|
| 37 |
+
|
| 38 |
+
const userLocationIcon = L.divIcon({
|
| 39 |
+
className: 'user-location-pulse',
|
| 40 |
+
iconSize: [20, 20],
|
| 41 |
+
});
|
| 42 |
+
|
| 43 |
+
const ChangeView: React.FC<{ center: [number, number]; zoom: number }> = ({ center, zoom }) => {
|
| 44 |
+
const map = useMap();
|
| 45 |
+
useEffect(() => {
|
| 46 |
+
map.flyTo(center, zoom);
|
| 47 |
+
}, [center, zoom, map]);
|
| 48 |
+
return null;
|
| 49 |
+
};
|
| 50 |
+
|
| 51 |
+
const LocateControl: React.FC = () => {
|
| 52 |
+
const map = useMap();
|
| 53 |
+
const [position, setPosition] = useState<L.LatLng | null>(null);
|
| 54 |
+
|
| 55 |
+
const handleLocate = () => {
|
| 56 |
+
map.locate().on('locationfound', (e) => {
|
| 57 |
+
setPosition(e.latlng);
|
| 58 |
+
map.flyTo(e.latlng, 14);
|
| 59 |
+
}).on('locationerror', () => {
|
| 60 |
+
alert('Could not access your location. Please enable location services in your browser.');
|
| 61 |
+
});
|
| 62 |
+
};
|
| 63 |
+
|
| 64 |
+
return (
|
| 65 |
+
<>
|
| 66 |
+
<div className="absolute top-4 left-4 z-[1000]">
|
| 67 |
+
<button onClick={handleLocate} className="locate-me-btn" aria-label="Find my location">
|
| 68 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 8V2"/><path d="M12 16v6"/><path d="M22 12h-6"/><path d="M8 12H2"/><path d="M12 12a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/></svg>
|
| 69 |
+
</button>
|
| 70 |
+
</div>
|
| 71 |
+
{position && <Marker position={position} icon={userLocationIcon} />}
|
| 72 |
+
</>
|
| 73 |
+
);
|
| 74 |
+
};
|
| 75 |
+
|
| 76 |
+
const MapComponent: React.FC<MapComponentProps> = ({ markets, selectedMarket, hoveredMarketId, onSelectMarket, onMarketHover, mapCenter, mapZoom }) => {
|
| 77 |
+
const markerRefs = useRef<{ [key: number]: L.Marker | null }>({});
|
| 78 |
+
|
| 79 |
+
useEffect(() => {
|
| 80 |
+
if (selectedMarket && markerRefs.current[selectedMarket.id]) {
|
| 81 |
+
setTimeout(() => {
|
| 82 |
+
markerRefs.current[selectedMarket.id]?.openPopup();
|
| 83 |
+
}, 300);
|
| 84 |
+
}
|
| 85 |
+
}, [selectedMarket]);
|
| 86 |
+
|
| 87 |
+
return (
|
| 88 |
+
<MapContainer center={mapCenter} zoom={mapZoom} scrollWheelZoom={true} className="h-full w-full">
|
| 89 |
+
<ChangeView center={mapCenter} zoom={mapZoom} />
|
| 90 |
+
<LocateControl />
|
| 91 |
+
<TileLayer
|
| 92 |
+
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
|
| 93 |
+
url="https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png"
|
| 94 |
+
/>
|
| 95 |
+
{markets.map((market) => {
|
| 96 |
+
const isSelected = selectedMarket?.id === market.id;
|
| 97 |
+
const isHovered = hoveredMarketId === market.id;
|
| 98 |
+
let icon;
|
| 99 |
+
if (isSelected) {
|
| 100 |
+
icon = createIcon(market.category, '#facc15', 50, 'selected-marker-icon');
|
| 101 |
+
} else if (isHovered) {
|
| 102 |
+
icon = createIcon(market.category, '#f43f5e', 44, 'hovered-marker-icon');
|
| 103 |
+
} else {
|
| 104 |
+
icon = createIcon(market.category, '#e11d48', 38);
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
return (
|
| 108 |
+
<Marker
|
| 109 |
+
key={market.id}
|
| 110 |
+
position={[market.lat, market.lon]}
|
| 111 |
+
icon={icon}
|
| 112 |
+
eventHandlers={{
|
| 113 |
+
click: () => onSelectMarket(market),
|
| 114 |
+
mouseover: () => onMarketHover(market.id),
|
| 115 |
+
mouseout: () => onMarketHover(null),
|
| 116 |
+
}}
|
| 117 |
+
ref={(el) => (markerRefs.current[market.id] = el)}
|
| 118 |
+
>
|
| 119 |
+
<Popup>
|
| 120 |
+
<div className="w-full text-stone-200 font-sans">
|
| 121 |
+
<ImageCarousel images={market.imageUrls} marketName={market.name} />
|
| 122 |
+
<div className="p-4">
|
| 123 |
+
<h3 className="text-xl font-bold mb-2 text-amber-400" style={{ fontFamily: "'Cinzel', serif" }}>
|
| 124 |
+
{market.name}
|
| 125 |
+
</h3>
|
| 126 |
+
<p className="text-sm text-stone-300 mb-4 text-left">{market.description}</p>
|
| 127 |
+
<div className="border-t border-stone-600 pt-3 text-left">
|
| 128 |
+
<h4 className="font-semibold text-stone-100 text-base mb-1">Öffnungszeiten</h4>
|
| 129 |
+
<p className="text-stone-300 text-sm">{market.openingHours}</p>
|
| 130 |
+
</div>
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
</Popup>
|
| 134 |
+
</Marker>
|
| 135 |
+
);
|
| 136 |
+
})}
|
| 137 |
+
</MapContainer>
|
| 138 |
+
);
|
| 139 |
+
};
|
| 140 |
+
|
| 141 |
+
export default MapComponent;
|
cologne-christmas-markets-map-2025_5/components/MarketDetailModal.tsx
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import type { Market } from '../types';
|
| 3 |
+
|
| 4 |
+
interface MarketDetailModalProps {
|
| 5 |
+
market: Market;
|
| 6 |
+
onClose: () => void;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
const MarketDetailModal: React.FC<MarketDetailModalProps> = ({ market, onClose }) => {
|
| 10 |
+
if (!market) return null;
|
| 11 |
+
|
| 12 |
+
return (
|
| 13 |
+
<div
|
| 14 |
+
className="modal-backdrop fixed inset-0 z-[2000] flex items-center justify-center bg-black/70"
|
| 15 |
+
onClick={onClose}
|
| 16 |
+
>
|
| 17 |
+
<div
|
| 18 |
+
className="modal-content bg-stone-800 rounded-lg shadow-2xl w-11/12 md:w-2/3 lg:w-1/2 max-w-4xl max-h-[90vh] flex flex-col border-2 border-stone-700 overflow-hidden"
|
| 19 |
+
onClick={(e) => e.stopPropagation()} // Prevent clicks inside the modal from closing it
|
| 20 |
+
>
|
| 21 |
+
<div className="relative">
|
| 22 |
+
<img src={market.imageUrls[0]} alt={market.name} className="w-full h-48 md:h-64 object-cover" />
|
| 23 |
+
<div className="absolute inset-0 bg-gradient-to-t from-stone-800 to-transparent"></div>
|
| 24 |
+
<button
|
| 25 |
+
onClick={onClose}
|
| 26 |
+
className="absolute top-2 right-2 text-white bg-black/50 rounded-full p-2 hover:bg-black/80 transition-colors"
|
| 27 |
+
aria-label="Close"
|
| 28 |
+
>
|
| 29 |
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 30 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
| 31 |
+
</svg>
|
| 32 |
+
</button>
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
<div className="p-6 md:p-8 overflow-y-auto">
|
| 36 |
+
<h2 className="text-3xl font-bold text-amber-400 mb-4">{market.name}</h2>
|
| 37 |
+
<p className="text-stone-300 leading-relaxed mb-6">{market.description}</p>
|
| 38 |
+
|
| 39 |
+
<div className="border-t border-stone-700 pt-4 space-y-3 text-stone-300">
|
| 40 |
+
<div className="flex items-start">
|
| 41 |
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 mr-3 text-amber-400 flex-shrink-0" viewBox="0 0 24 24" fill="currentColor"><path d="M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V10h14v10zM9 14H7v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2z"/></svg>
|
| 42 |
+
<div>
|
| 43 |
+
<h4 className="font-semibold text-stone-100">Zeitraum</h4>
|
| 44 |
+
<p>{market.start} - {market.end}</p>
|
| 45 |
+
</div>
|
| 46 |
+
</div>
|
| 47 |
+
<div className="flex items-start">
|
| 48 |
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 mr-3 text-amber-400 flex-shrink-0" viewBox="0 0 24 24" fill="currentColor"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/><path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"/></svg>
|
| 49 |
+
<div>
|
| 50 |
+
<h4 className="font-semibold text-stone-100">Öffnungszeiten</h4>
|
| 51 |
+
<p>{market.openingHours}</p>
|
| 52 |
+
</div>
|
| 53 |
+
</div>
|
| 54 |
+
<div className="flex items-start">
|
| 55 |
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 mr-3 text-amber-400 flex-shrink-0" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 0 1 0-5 2.5 2.5 0 0 1 0 5z"/></svg>
|
| 56 |
+
<div>
|
| 57 |
+
<h4 className="font-semibold text-stone-100">Ort</h4>
|
| 58 |
+
<p>{market.ort}</p>
|
| 59 |
+
</div>
|
| 60 |
+
</div>
|
| 61 |
+
</div>
|
| 62 |
+
{market.website && (
|
| 63 |
+
<div className="mt-8 text-center">
|
| 64 |
+
<a
|
| 65 |
+
href={market.website}
|
| 66 |
+
target="_blank"
|
| 67 |
+
rel="noopener noreferrer"
|
| 68 |
+
className="inline-flex items-center justify-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-stone-900 bg-amber-400 hover:bg-amber-500 transition-colors shadow-lg hover:shadow-amber-400/30"
|
| 69 |
+
>
|
| 70 |
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg>
|
| 71 |
+
Zur Webseite
|
| 72 |
+
</a>
|
| 73 |
+
</div>
|
| 74 |
+
)}
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
);
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
export default MarketDetailModal;
|
cologne-christmas-markets-map-2025_5/components/MarketList.tsx
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import type { Market } from '../types';
|
| 3 |
+
import MarketListItem from './MarketListItem';
|
| 4 |
+
import WeatherWidget from './WeatherWidget';
|
| 5 |
+
|
| 6 |
+
interface MarketListProps {
|
| 7 |
+
markets: Market[];
|
| 8 |
+
selectedMarket: Market | null;
|
| 9 |
+
hoveredMarketId: number | null;
|
| 10 |
+
onSelectMarket: (market: Market | null) => void;
|
| 11 |
+
onMarketHover: (marketId: number | null) => void;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
const MarketList: React.FC<MarketListProps> = ({ markets, selectedMarket, hoveredMarketId, onSelectMarket, onMarketHover }) => {
|
| 15 |
+
return (
|
| 16 |
+
<aside className="w-full md:w-96 lg:w-1/3 xl:w-1/4 bg-stone-800 shadow-lg flex flex-col h-1/3 md:h-full border-r-2 border-black/30">
|
| 17 |
+
<header className="p-4 bg-black/20 border-b border-black/30 shadow-md">
|
| 18 |
+
<h1 className="text-2xl font-bold text-stone-100 flex items-center">
|
| 19 |
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 mr-3 text-amber-400" viewBox="0 0 24 24" fill="currentColor">
|
| 20 |
+
<path d="M12 2.5l2.42 5.38 5.58.81-4.04 3.93.95 5.56L12 15.4l-5.91 3.78.95-5.56-4.04-3.93 5.58-.81L12 2.5z M12 5.8l-1.61 3.58-3.96.58 2.86 2.79-.68 3.94L12 14.5l3.35 2.19-.68-3.94 2.86-2.79-3.96-.58L12 5.8z" />
|
| 21 |
+
</svg>
|
| 22 |
+
Kölner Weihnachtsmärkte 2025
|
| 23 |
+
</h1>
|
| 24 |
+
<p className="text-base text-stone-400 mt-2">Wählen Sie einen Markt aus, um ihn auf der Karte zu sehen.</p>
|
| 25 |
+
</header>
|
| 26 |
+
<div className="overflow-y-auto flex-grow">
|
| 27 |
+
{markets.map((market) => (
|
| 28 |
+
<MarketListItem
|
| 29 |
+
key={market.id}
|
| 30 |
+
market={market}
|
| 31 |
+
isSelected={selectedMarket?.id === market.id}
|
| 32 |
+
isHovered={hoveredMarketId === market.id}
|
| 33 |
+
onSelect={() => onSelectMarket(market)}
|
| 34 |
+
onHover={onMarketHover}
|
| 35 |
+
/>
|
| 36 |
+
))}
|
| 37 |
+
</div>
|
| 38 |
+
<footer className="mt-auto bg-black/20 border-t border-black/30">
|
| 39 |
+
<WeatherWidget />
|
| 40 |
+
</footer>
|
| 41 |
+
</aside>
|
| 42 |
+
);
|
| 43 |
+
};
|
| 44 |
+
|
| 45 |
+
export default MarketList;
|
cologne-christmas-markets-map-2025_5/components/MarketListItem.tsx
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import type { Market } from '../types';
|
| 3 |
+
|
| 4 |
+
interface MarketListItemProps {
|
| 5 |
+
market: Market;
|
| 6 |
+
isSelected: boolean;
|
| 7 |
+
isHovered: boolean;
|
| 8 |
+
onSelect: () => void;
|
| 9 |
+
onHover: (marketId: number | null) => void;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
const MarketListItem: React.FC<MarketListItemProps> = ({ market, isSelected, isHovered, onSelect, onHover }) => {
|
| 13 |
+
const itemClasses = `
|
| 14 |
+
p-4 border-b border-black/20 cursor-pointer transition-all duration-200 ease-in-out
|
| 15 |
+
${isSelected ? 'bg-amber-900/40 border-l-4 border-amber-400' : isHovered ? 'bg-stone-700/70 border-l-4 border-stone-600' : 'hover:bg-stone-700/50 border-l-4 border-transparent'}
|
| 16 |
+
`;
|
| 17 |
+
|
| 18 |
+
return (
|
| 19 |
+
<div
|
| 20 |
+
className={itemClasses}
|
| 21 |
+
onClick={onSelect}
|
| 22 |
+
onMouseEnter={() => onHover(market.id)}
|
| 23 |
+
onMouseLeave={() => onHover(null)}
|
| 24 |
+
>
|
| 25 |
+
<h3 className={`font-semibold text-lg ${isSelected ? 'text-amber-300' : 'text-stone-200'}`}>
|
| 26 |
+
{market.name}
|
| 27 |
+
</h3>
|
| 28 |
+
<div className="text-sm text-stone-400 mt-1">
|
| 29 |
+
<p>
|
| 30 |
+
<span className="font-medium text-stone-300">Zeitraum:</span> {market.start} - {market.end}
|
| 31 |
+
</p>
|
| 32 |
+
</div>
|
| 33 |
+
</div>
|
| 34 |
+
);
|
| 35 |
+
};
|
| 36 |
+
|
| 37 |
+
export default MarketListItem;
|
cologne-christmas-markets-map-2025_5/components/WeatherWidget.tsx
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect } from 'react';
|
| 2 |
+
|
| 3 |
+
// Simplified weather icons as React components
|
| 4 |
+
const SunIcon = () => <svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>;
|
| 5 |
+
const MoonIcon = () => <svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>;
|
| 6 |
+
const CloudIcon = () => <svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path></svg>;
|
| 7 |
+
const CloudRainIcon = () => <svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="16" y1="13" x2="16" y2="21"></line><line x1="8" y1="13" x2="8" y2="21"></line><line x1="12" y1="15" x2="12" y2="23"></line><path d="M20 16.58A5 5 0 0 0 18 7h-1.26A8 8 0 1 0 4 15.25"></path></svg>;
|
| 8 |
+
const CloudSnowIcon = () => <svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 17.58A5 5 0 0 0 18 8h-1.26A8 8 0 1 0 4 16.25"></path><line x1="8" y1="16" x2="8" y2="16"></line><line x1="8" y1="20" x2="8" y2="20"></line><line x1="12" y1="18" x2="12" y2="18"></line><line x1="12" y1="22" x2="12" y2="22"></line><line x1="16" y1="16" x2="16" y2="16"></line><line x1="16" y1="20" x2="16" y2="20"></line></svg>;
|
| 9 |
+
const CloudFogIcon = () => <svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"/><path d="M16 17H7"/><path d="M17 21H9"/></svg>;
|
| 10 |
+
|
| 11 |
+
interface WeatherData {
|
| 12 |
+
temperature: number;
|
| 13 |
+
weatherCode: number;
|
| 14 |
+
isDay: number;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
const getWeatherInfo = (code: number, isDay: number): { icon: React.ReactNode; description: string } => {
|
| 18 |
+
switch (code) {
|
| 19 |
+
case 0:
|
| 20 |
+
return { icon: isDay ? <SunIcon /> : <MoonIcon />, description: 'Clear sky' };
|
| 21 |
+
case 1:
|
| 22 |
+
case 2:
|
| 23 |
+
case 3:
|
| 24 |
+
return { icon: <CloudIcon />, description: 'Partly cloudy' };
|
| 25 |
+
case 45:
|
| 26 |
+
case 48:
|
| 27 |
+
return { icon: <CloudFogIcon />, description: 'Fog' };
|
| 28 |
+
case 51:
|
| 29 |
+
case 53:
|
| 30 |
+
case 55:
|
| 31 |
+
return { icon: <CloudRainIcon />, description: 'Drizzle' };
|
| 32 |
+
case 61:
|
| 33 |
+
case 63:
|
| 34 |
+
case 65:
|
| 35 |
+
return { icon: <CloudRainIcon />, description: 'Rain' };
|
| 36 |
+
case 71:
|
| 37 |
+
case 73:
|
| 38 |
+
case 75:
|
| 39 |
+
case 77:
|
| 40 |
+
return { icon: <CloudSnowIcon />, description: 'Snow fall' };
|
| 41 |
+
case 80:
|
| 42 |
+
case 81:
|
| 43 |
+
case 82:
|
| 44 |
+
return { icon: <CloudRainIcon />, description: 'Rain showers' };
|
| 45 |
+
case 85:
|
| 46 |
+
case 86:
|
| 47 |
+
return { icon: <CloudSnowIcon />, description: 'Snow showers' };
|
| 48 |
+
case 95:
|
| 49 |
+
case 96:
|
| 50 |
+
case 99:
|
| 51 |
+
return { icon: <CloudRainIcon />, description: 'Thunderstorm' };
|
| 52 |
+
default:
|
| 53 |
+
return { icon: <CloudIcon />, description: 'Cloudy' };
|
| 54 |
+
}
|
| 55 |
+
};
|
| 56 |
+
|
| 57 |
+
const WeatherWidget: React.FC = () => {
|
| 58 |
+
const [weather, setWeather] = useState<WeatherData | null>(null);
|
| 59 |
+
const [loading, setLoading] = useState(true);
|
| 60 |
+
const [error, setError] = useState<string | null>(null);
|
| 61 |
+
|
| 62 |
+
useEffect(() => {
|
| 63 |
+
const fetchWeather = async () => {
|
| 64 |
+
try {
|
| 65 |
+
const response = await fetch('https://api.open-meteo.com/v1/forecast?latitude=50.94&longitude=6.96¤t=temperature_2m,is_day,weather_code');
|
| 66 |
+
if (!response.ok) {
|
| 67 |
+
throw new Error('Failed to fetch weather data');
|
| 68 |
+
}
|
| 69 |
+
const data = await response.json();
|
| 70 |
+
setWeather({
|
| 71 |
+
temperature: Math.round(data.current.temperature_2m),
|
| 72 |
+
weatherCode: data.current.weather_code,
|
| 73 |
+
isDay: data.current.is_day,
|
| 74 |
+
});
|
| 75 |
+
} catch (err) {
|
| 76 |
+
setError(err instanceof Error ? err.message : 'An unknown error occurred');
|
| 77 |
+
} finally {
|
| 78 |
+
setLoading(false);
|
| 79 |
+
}
|
| 80 |
+
};
|
| 81 |
+
|
| 82 |
+
fetchWeather();
|
| 83 |
+
}, []);
|
| 84 |
+
|
| 85 |
+
if (loading) {
|
| 86 |
+
return (
|
| 87 |
+
<div className="p-4 text-center text-stone-400">
|
| 88 |
+
Loading weather...
|
| 89 |
+
</div>
|
| 90 |
+
);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
if (error || !weather) {
|
| 94 |
+
return (
|
| 95 |
+
<div className="p-4 text-center text-rose-400">
|
| 96 |
+
Could not load weather.
|
| 97 |
+
</div>
|
| 98 |
+
);
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
const { icon, description } = getWeatherInfo(weather.weatherCode, weather.isDay);
|
| 102 |
+
|
| 103 |
+
return (
|
| 104 |
+
<div className="p-4 flex items-center justify-between text-stone-300">
|
| 105 |
+
<div className="flex items-center">
|
| 106 |
+
<div className="text-amber-400 mr-4">
|
| 107 |
+
{icon}
|
| 108 |
+
</div>
|
| 109 |
+
<div>
|
| 110 |
+
<p className="font-bold text-lg text-stone-200">Cologne</p>
|
| 111 |
+
<p className="text-sm">{description}</p>
|
| 112 |
+
</div>
|
| 113 |
+
</div>
|
| 114 |
+
<div className="text-3xl font-bold font-['Cinzel'] text-white">
|
| 115 |
+
{weather.temperature}°C
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
);
|
| 119 |
+
};
|
| 120 |
+
|
| 121 |
+
export default WeatherWidget;
|
cologne-christmas-markets-map-2025_5/constants.ts
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Market } from './types';
|
| 2 |
+
|
| 3 |
+
export const MARKETS: Market[] = [
|
| 4 |
+
{
|
| 5 |
+
id: 1,
|
| 6 |
+
name: "Weihnachtsmarkt am Kölner Dom",
|
| 7 |
+
lat: 50.941278,
|
| 8 |
+
lon: 6.958281,
|
| 9 |
+
start: "17.11.2025",
|
| 10 |
+
end: "23.12.2025",
|
| 11 |
+
ort: "Roncalliplatz, 50667 Köln",
|
| 12 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/08/weihnachtsmarkt-am-koelner-dom-imago-futureimage-95332928-1.jpg"],
|
| 13 |
+
description: "Großer Markt mit Lichterzelt und hohem Weihnachtsbaum direkt am Dom.",
|
| 14 |
+
openingHours: "So-Mi 11:00-21:00, Do-Fr 11:00-22:00, Sa 10:00-22:00, Totensonntag geschlossen",
|
| 15 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmarkt-am-koelner-dom/",
|
| 16 |
+
category: 'Traditional'
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
id: 2,
|
| 20 |
+
name: "Markt der Engel auf dem Neumarkt",
|
| 21 |
+
lat: 50.936002,
|
| 22 |
+
lon: 6.947546,
|
| 23 |
+
start: "17.11.2025",
|
| 24 |
+
end: "23.12.2025",
|
| 25 |
+
ort: "Neumarkt, 50667 Köln",
|
| 26 |
+
imageUrls: ["https://koeln.mitvergnuegen.com/resizer/images/7b227769647468223a3934302c22686569676874223a3532397d/wp-content/uploads/sites/4/2020/12/markt-der-engel-weihnachtsmarkt-neumarkt-koln.jpg.webp"],
|
| 27 |
+
description: "Stimmungsvoller Markt mit vielen Sternenlichtern und Engel-Figuren.",
|
| 28 |
+
openingHours: "So-Do 11:00-21:00, Fr-Sa 11:00-22:00, Totensonntag geschlossen",
|
| 29 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmarkt-neumarkt/",
|
| 30 |
+
category: 'Traditional'
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
id: 3,
|
| 34 |
+
name: "Heinzels Wintermärchen Altstadt",
|
| 35 |
+
lat: 50.9394,
|
| 36 |
+
lon: 6.9599,
|
| 37 |
+
start: "24.11.2025",
|
| 38 |
+
end: "04.01.2026",
|
| 39 |
+
ort: "Alter Markt und Heumarkt, 50667 Köln",
|
| 40 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/08/weihnachtsmarkt-koelner-altstadt-jochen-tack-imago95344912-1-800x470.jpg"],
|
| 41 |
+
description: "Großer Altstadtmarkt mit Eislaufbahn und vielen Buden am Alter Markt und Heumarkt.",
|
| 42 |
+
openingHours: "Täglich 11:00-22:00, ab 26.12 11:00-21:00, 24. und 25.12 geschlossen",
|
| 43 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmarkt-altstadt/",
|
| 44 |
+
category: 'Traditional'
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
id: 4,
|
| 48 |
+
name: "Nikolausdorf Rudolfplatz",
|
| 49 |
+
lat: 50.9369,
|
| 50 |
+
lon: 6.9378,
|
| 51 |
+
start: "17.11.2025",
|
| 52 |
+
end: "23.12.2025",
|
| 53 |
+
ort: "Rudolfplatz, 50674 Köln",
|
| 54 |
+
imageUrls: ["https://nikolausdorf.com/templates/yootheme/cache/5d/footer_img_03b-5dbd2f20.webp"],
|
| 55 |
+
description: "Familienfreundlicher Markt als Nikolausdorf vor der Hahnentorburg.",
|
| 56 |
+
openingHours: "So-Do 11:00-21:00, Fr-Sa 11:00-22:00, Glühweinstände je 1 Stunde länger, Totensonntag geschlossen",
|
| 57 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmarkt-rudolfplatz/",
|
| 58 |
+
category: 'Themed'
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
id: 5,
|
| 62 |
+
name: "Hafen-Weihnachtsmarkt am Schokoladenmuseum",
|
| 63 |
+
lat: 50.9322,
|
| 64 |
+
lon: 6.9638,
|
| 65 |
+
start: "14.11.2025",
|
| 66 |
+
end: "23.12.2025",
|
| 67 |
+
ort: "Am Schokoladenmuseum, 50678 Köln",
|
| 68 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/08/hafenweihnachtsmarkt-imago-zoonar-0170166521.jpg"],
|
| 69 |
+
description: "Maritimer Markt im Rheinauhafen mit weißen Pagodenzelten und Riesenrad.",
|
| 70 |
+
openingHours: "Täglich 11:00-21:00, Fr-Sa 11:00-22:00, Volkstrauertag ab 13:00, Totensonntag ab 18:00",
|
| 71 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/hafen-weihnachtsmarkt/",
|
| 72 |
+
category: 'Themed'
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
id: 6,
|
| 76 |
+
name: "Weihnachtsmarkt im Stadtgarten",
|
| 77 |
+
lat: 50.9435,
|
| 78 |
+
lon: 6.9380,
|
| 79 |
+
start: "14.11.2025",
|
| 80 |
+
end: "23.12.2025",
|
| 81 |
+
ort: "Venloer Str. 40, 50672 Köln",
|
| 82 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2024/11/Weihnachtsmarkt-Stadtgarten06-Vincent-Alex-2000-800x470.jpg"],
|
| 83 |
+
description: "Beliebter Veedels-Markt mit vielen Design- und Handwerksständen unter Bäumen.",
|
| 84 |
+
openingHours: "Mo-Fr 16:00-21:30, Sa-So 12:00-21:30, Totensonntag ab 18:00",
|
| 85 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmarkt-stadtgarten/",
|
| 86 |
+
category: 'Themed'
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
id: 7,
|
| 90 |
+
name: "Veedelsadvent auf dem Chlodwigplatz",
|
| 91 |
+
lat: 50.9232,
|
| 92 |
+
lon: 6.9560,
|
| 93 |
+
start: "20.11.2025",
|
| 94 |
+
end: "23.12.2025",
|
| 95 |
+
ort: "Chlodwigplatz, 50678 Köln",
|
| 96 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/clodwigplatz_weihnachtsmarkt_2016-27-von-37-800x470.jpg"],
|
| 97 |
+
description: "Kleiner, nachbarschaftlicher Veedels-Markt mit lokalen Anbietern.",
|
| 98 |
+
openingHours: "Täglich 12:00-22:00, Fr-Sa bis 23:00, Totensonntag geschlossen",
|
| 99 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmarkt-chlodwigplatz/",
|
| 100 |
+
category: 'Community'
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
id: 8,
|
| 104 |
+
name: "Heavenue Cologne",
|
| 105 |
+
lat: 50.9406,
|
| 106 |
+
lon: 6.9421,
|
| 107 |
+
start: "17.11.2025",
|
| 108 |
+
end: "23.12.2025",
|
| 109 |
+
ort: "Friesenplatz, 50672 Köln",
|
| 110 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/Weihnachtsmarkt-AdobeStock_128195075-800x470.jpg"],
|
| 111 |
+
description: "Bunter LGBTQ+ Weihnachtsmarkt mit Shows, Musik und Food am Friesenplatz.",
|
| 112 |
+
openingHours: "Mo-Do 12:00-22:00, Fr 12:00-23:00, Sa 11:00-23:00, So 11:00-22:00, Totensonntag geschlossen",
|
| 113 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/heavenue-cologne/",
|
| 114 |
+
category: 'Themed'
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
id: 9,
|
| 118 |
+
name: "Winterzauber Eigelstein",
|
| 119 |
+
lat: 50.9479,
|
| 120 |
+
lon: 6.9563,
|
| 121 |
+
start: "03.12.2025",
|
| 122 |
+
end: "07.12.2025",
|
| 123 |
+
ort: "Eigelsteintorburg, 50668 Köln",
|
| 124 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 125 |
+
description: "Kleiner Markt an der Eigelsteintorburg mit Bühnenprogramm und Gastronomie.",
|
| 126 |
+
openingHours: "Mi-Fr 15:00-22:00, Sa 14:00-22:00, So 14:00-21:00",
|
| 127 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/winterzauber-eigelstein/",
|
| 128 |
+
category: 'Short-Term'
|
| 129 |
+
},
|
| 130 |
+
{
|
| 131 |
+
id: 10,
|
| 132 |
+
name: "Der kleinste Weihnachtsmarkt der Stadt",
|
| 133 |
+
lat: 50.9238,
|
| 134 |
+
lon: 6.9455,
|
| 135 |
+
start: "19.11.2025",
|
| 136 |
+
end: "20.12.2025",
|
| 137 |
+
ort: "Biergarten im Volksgarten, Volksgartenstr. 27, 50677 Köln",
|
| 138 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/Weihnachtsmarkt-AdobeStock_128195075-800x470.jpg"],
|
| 139 |
+
description: "Gemütlicher Benefiz-Markt im Biergarten Volksgarten, Erlös für soziale Projekte.",
|
| 140 |
+
openingHours: "Mo-Fr 17:00-22:00, Sa 15:00-22:00, So 15:00-20:00",
|
| 141 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/kleinster-weihnachtsmarkt-der-stadt/",
|
| 142 |
+
category: 'Community'
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
id: 11,
|
| 146 |
+
name: "Weihnachtsmarkt an St. Aposteln",
|
| 147 |
+
lat: 50.9372,
|
| 148 |
+
lon: 6.9443,
|
| 149 |
+
start: "17.11.2025",
|
| 150 |
+
end: "23.12.2025",
|
| 151 |
+
ort: "Apostelnkloster, 50672 Köln",
|
| 152 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 153 |
+
description: "Kirchnaher Markt mit Buden rund um St. Aposteln nahe Neumarkt.",
|
| 154 |
+
openingHours: "täglich ca. 11:00-22:00, freitags & samstags teils länger laut Aushang vor Ort",
|
| 155 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmarkt-st-aposteln/",
|
| 156 |
+
category: 'Traditional'
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
id: 12,
|
| 160 |
+
name: "Weihnachtsbasar Josef Esser Platz",
|
| 161 |
+
lat: 50.9634,
|
| 162 |
+
lon: 6.8943,
|
| 163 |
+
start: "29.11.2025",
|
| 164 |
+
end: "30.11.2025",
|
| 165 |
+
ort: "Josef Esser Platz, 50827 Köln",
|
| 166 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 167 |
+
description: "Nachbarschaftlicher Weihnachtsbasar mit Ständen auf dem Platz in Bickendorf.",
|
| 168 |
+
openingHours: "laut Veranstalter, meist nachmittags bis abends",
|
| 169 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 170 |
+
category: 'Short-Term'
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
id: 13,
|
| 174 |
+
name: "Dellbrücker Weihnachtsmarkt",
|
| 175 |
+
lat: 50.9882,
|
| 176 |
+
lon: 7.0865,
|
| 177 |
+
start: "29.11.2025",
|
| 178 |
+
end: "29.11.2025",
|
| 179 |
+
ort: "Beim SV Adler Dellbrück, 51069 Köln",
|
| 180 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 181 |
+
description: "Klassischer Veedel Markt beim SV Adler Dellbrück.",
|
| 182 |
+
openingHours: "ab 15:00 Uhr bis Abend",
|
| 183 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 184 |
+
category: 'Short-Term'
|
| 185 |
+
},
|
| 186 |
+
{
|
| 187 |
+
id: 14,
|
| 188 |
+
name: "Weihnachtsmarkt im Waldbad Dünnwald",
|
| 189 |
+
lat: 51.0028,
|
| 190 |
+
lon: 7.0425,
|
| 191 |
+
start: "13.12.2025",
|
| 192 |
+
end: "14.12.2025",
|
| 193 |
+
ort: "Waldbad Dünnwald, Peter Bergner Weg 2, 51069 Köln",
|
| 194 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 195 |
+
description: "Atmosphärischer Markt im Waldbad mit Waldkulisse.",
|
| 196 |
+
openingHours: "Sa 14:00-20:00, So 11:00-18:00",
|
| 197 |
+
website: "https://waldbad-camping.de",
|
| 198 |
+
category: 'Short-Term'
|
| 199 |
+
},
|
| 200 |
+
{
|
| 201 |
+
id: 15,
|
| 202 |
+
name: "Dünnwalder Adventsmarkt",
|
| 203 |
+
lat: 50.9995,
|
| 204 |
+
lon: 7.0336,
|
| 205 |
+
start: "30.11.2025",
|
| 206 |
+
end: "30.11.2025",
|
| 207 |
+
ort: "Kirchplatz vor St. Hermann Joseph, 51069 Köln",
|
| 208 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 209 |
+
description: "Kleiner Adventsmarkt vor der Kirche St. Hermann Joseph.",
|
| 210 |
+
openingHours: "12:00-18:00",
|
| 211 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 212 |
+
category: 'Short-Term'
|
| 213 |
+
},
|
| 214 |
+
{
|
| 215 |
+
id: 16,
|
| 216 |
+
name: "Weihnachtsmarkt im Herbrands",
|
| 217 |
+
lat: 50.9497,
|
| 218 |
+
lon: 6.9211,
|
| 219 |
+
start: "21.11.2025",
|
| 220 |
+
end: "23.12.2025",
|
| 221 |
+
ort: "Herbrandstr. 21, 50825 Köln",
|
| 222 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 223 |
+
description: "Indoor und Hof Markt mit Gastro und Programm im Herbrands in Ehrenfeld.",
|
| 224 |
+
openingHours: "Öffnungszeiten nach Spielplan, meist nachmittags bis spät abends",
|
| 225 |
+
website: "https://www.herbrands.de",
|
| 226 |
+
category: 'Themed'
|
| 227 |
+
},
|
| 228 |
+
{
|
| 229 |
+
id: 17,
|
| 230 |
+
name: "Weihnachtsmarkt bei Bumann und Sohn",
|
| 231 |
+
lat: 50.9482,
|
| 232 |
+
lon: 6.9205,
|
| 233 |
+
start: "13.11.2025",
|
| 234 |
+
end: "23.12.2025",
|
| 235 |
+
ort: "Bartholomäus-Schink-Str. 2, 50825 Köln",
|
| 236 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 237 |
+
description: "Alternativer Markt im Club Bumann und Sohn in Ehrenfeld.",
|
| 238 |
+
openingHours: "überwiegend ab Nachmittags/Abend laut Programm",
|
| 239 |
+
website: "https://bumannundsohn.de",
|
| 240 |
+
category: 'Themed'
|
| 241 |
+
},
|
| 242 |
+
{
|
| 243 |
+
id: 18,
|
| 244 |
+
name: "Wintermarkt im CBE",
|
| 245 |
+
lat: 50.9494,
|
| 246 |
+
lon: 6.9200,
|
| 247 |
+
start: "16.11.2025",
|
| 248 |
+
end: "23.12.2025",
|
| 249 |
+
ort: "Venloer Str. 429, 50825 Köln",
|
| 250 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 251 |
+
description: "Sozialer Wintermarkt im Bürgerzentrum Ehrenfeld.",
|
| 252 |
+
openingHours: "laut Veranstalter, meist nachmittags",
|
| 253 |
+
website: "https://www.cbe.koeln",
|
| 254 |
+
category: 'Community'
|
| 255 |
+
},
|
| 256 |
+
{
|
| 257 |
+
id: 19,
|
| 258 |
+
name: "Garagen Glüh'n Hofweihnachtsmarkt Körnerstrasse",
|
| 259 |
+
lat: 50.9507,
|
| 260 |
+
lon: 6.9281,
|
| 261 |
+
start: "06.12.2025",
|
| 262 |
+
end: "07.12.2025",
|
| 263 |
+
ort: "Körnerstr., 50823 Köln",
|
| 264 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 265 |
+
description: "Kreativer Hofmarkt mit vielen Garagenständen in der Körnerstraße in Ehrenfeld.",
|
| 266 |
+
openingHours: "jeweils tagsüber bis abends",
|
| 267 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 268 |
+
category: 'Short-Term'
|
| 269 |
+
},
|
| 270 |
+
{
|
| 271 |
+
id: 20,
|
| 272 |
+
name: "Advent am Geisselmarkt",
|
| 273 |
+
lat: 50.9491,
|
| 274 |
+
lon: 6.9299,
|
| 275 |
+
start: "23.11.2025",
|
| 276 |
+
end: "23.11.2025",
|
| 277 |
+
ort: "Geisselmarkt, 50823 Köln",
|
| 278 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 279 |
+
description: "Kleiner Adventstreff auf dem Geisselmarkt in Ehrenfeld.",
|
| 280 |
+
openingHours: "11:00-17:00",
|
| 281 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 282 |
+
category: 'Short-Term'
|
| 283 |
+
},
|
| 284 |
+
{
|
| 285 |
+
id: 21,
|
| 286 |
+
name: "Traditioneller Weihnachtsmarkt Esch",
|
| 287 |
+
lat: 51.0185,
|
| 288 |
+
lon: 6.8340,
|
| 289 |
+
start: "29.11.2025",
|
| 290 |
+
end: "30.11.2025",
|
| 291 |
+
ort: "Alter Dorfkern, Kirchgasse, 50765 Köln",
|
| 292 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 293 |
+
description: "Dorfkern Markt mit Buden in der Kirchgasse.",
|
| 294 |
+
openingHours: "laut Veranstalter",
|
| 295 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 296 |
+
category: 'Short-Term'
|
| 297 |
+
},
|
| 298 |
+
{
|
| 299 |
+
id: 22,
|
| 300 |
+
name: "Weihnachtsmarkt Reitstall Birkenhof",
|
| 301 |
+
lat: 50.9922,
|
| 302 |
+
lon: 7.0583,
|
| 303 |
+
start: "14.11.2025",
|
| 304 |
+
end: "21.12.2025",
|
| 305 |
+
ort: "Reitstall Birkenhof, 51069 Köln",
|
| 306 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 307 |
+
description: "Reiterhof Weihnachtsmarkt mit Buden und Aktionen in Höhenhaus.",
|
| 308 |
+
openingHours: "Fr-So 15:00-22:00",
|
| 309 |
+
website: "https://www.instagram.com/birkenhof",
|
| 310 |
+
category: 'Themed'
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
id: 23,
|
| 314 |
+
name: "Veganer Weihnachtsmarkt Kalk",
|
| 315 |
+
lat: 50.9404,
|
| 316 |
+
lon: 6.9999,
|
| 317 |
+
start: "05.12.2025",
|
| 318 |
+
end: "21.12.2025",
|
| 319 |
+
ort: "Evangelische Kirche, Steinmetzstr. 57, 51103 Köln",
|
| 320 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 321 |
+
description: "Veganer Markt an drei Adventswochenenden an der ev. Kirche.",
|
| 322 |
+
openingHours: "Fr-So an den Wochenenden, ca. 15:00-21:00",
|
| 323 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 324 |
+
category: 'Themed'
|
| 325 |
+
},
|
| 326 |
+
{
|
| 327 |
+
id: 24,
|
| 328 |
+
name: "Lindenthaler Adventstreff Karl-Schwering-Platz",
|
| 329 |
+
lat: 50.9279,
|
| 330 |
+
lon: 6.9157,
|
| 331 |
+
start: "20.11.2025",
|
| 332 |
+
end: "23.12.2025",
|
| 333 |
+
ort: "Karl-Schwering-Platz, 50931 Köln",
|
| 334 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 335 |
+
description: "Kleiner Adventsmarkt im Lindenthaler Zentrum.",
|
| 336 |
+
openingHours: "täglich 12:00-22:00, Totensonntag ab 18:00",
|
| 337 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 338 |
+
category: 'Community'
|
| 339 |
+
},
|
| 340 |
+
{
|
| 341 |
+
id: 25,
|
| 342 |
+
name: "Winterzauber am See Stadtwald",
|
| 343 |
+
lat: 50.9246,
|
| 344 |
+
lon: 6.9038,
|
| 345 |
+
start: "21.11.2025",
|
| 346 |
+
end: "28.12.2025",
|
| 347 |
+
ort: "Leonardo Royal Hotel, Dürener Str. 287, 50935 Köln",
|
| 348 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 349 |
+
description: "Winterzauber Terrasse am See beim Leonardo Royal Hotel.",
|
| 350 |
+
openingHours: "laut Hotel, meist nachmittags/abends",
|
| 351 |
+
website: "https://www.leonardo-hotels.de",
|
| 352 |
+
category: 'Themed'
|
| 353 |
+
},
|
| 354 |
+
{
|
| 355 |
+
id: 26,
|
| 356 |
+
name: "Lindweiler Weihnachtsmarkt",
|
| 357 |
+
lat: 51.0069,
|
| 358 |
+
lon: 6.9056,
|
| 359 |
+
start: "12.12.2025",
|
| 360 |
+
end: "14.12.2025",
|
| 361 |
+
ort: "Marienberger Weg 19, 50767 Köln",
|
| 362 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 363 |
+
description: "Veedel Weihnachtsmarkt am Marienberger Weg.",
|
| 364 |
+
openingHours: "laut Bürgerverein, meist nachmittags",
|
| 365 |
+
website: "https://www.bv-lindweiler.de",
|
| 366 |
+
category: 'Short-Term'
|
| 367 |
+
},
|
| 368 |
+
{
|
| 369 |
+
id: 27,
|
| 370 |
+
name: "Kleinster Weihnachtsmarkt Merheim",
|
| 371 |
+
lat: 50.9469,
|
| 372 |
+
lon: 7.0543,
|
| 373 |
+
start: "06.12.2025",
|
| 374 |
+
end: "06.12.2025",
|
| 375 |
+
ort: "Bauspielplatz, Walnussweg, 51109 Köln",
|
| 376 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 377 |
+
description: "Kleiner Markt auf dem Bauspielplatz Walnussweg.",
|
| 378 |
+
openingHours: "13:30-17:30",
|
| 379 |
+
website: "https://bauspielplatz-merheim.de",
|
| 380 |
+
category: 'Short-Term'
|
| 381 |
+
},
|
| 382 |
+
{
|
| 383 |
+
id: 28,
|
| 384 |
+
name: "Max und Moritz Markt Lenauplatz",
|
| 385 |
+
lat: 50.9559,
|
| 386 |
+
lon: 6.9231,
|
| 387 |
+
start: "28.11.2025",
|
| 388 |
+
end: "23.12.2025",
|
| 389 |
+
ort: "Lenauplatz, 50825 Köln",
|
| 390 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 391 |
+
description: "Veedel Weihnachtsmarkt mit Buden und Gastro in Neuehrenfeld.",
|
| 392 |
+
openingHours: "Mo-Do 16:30-21:30, Fr-So ab 14:00",
|
| 393 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 394 |
+
category: 'Community'
|
| 395 |
+
},
|
| 396 |
+
{
|
| 397 |
+
id: 29,
|
| 398 |
+
name: "Weihnachtsmarkt Lutherkirche",
|
| 399 |
+
lat: 50.9620,
|
| 400 |
+
lon: 6.9467,
|
| 401 |
+
start: "13.12.2025",
|
| 402 |
+
end: "14.12.2025",
|
| 403 |
+
ort: "Lutherkirche Nippes, 50733 Köln",
|
| 404 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 405 |
+
description: "Kirchnaher Markt an der Lutherkirche in Nippes.",
|
| 406 |
+
openingHours: "12:00-18:00",
|
| 407 |
+
website: "https://www.lutherkirche-nippes.de",
|
| 408 |
+
category: 'Short-Term'
|
| 409 |
+
},
|
| 410 |
+
{
|
| 411 |
+
id: 30,
|
| 412 |
+
name: "Ostheimer Weihnachtsmarkt Sparkassen Vorplatz",
|
| 413 |
+
lat: 50.9385,
|
| 414 |
+
lon: 7.0438,
|
| 415 |
+
start: "07.12.2025",
|
| 416 |
+
end: "07.12.2025",
|
| 417 |
+
ort: "Vorplatz ehem. Sparkasse, 51109 Köln",
|
| 418 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 419 |
+
description: "Kleiner Markt auf dem Vorplatz der ehemaligen Sparkasse.",
|
| 420 |
+
openingHours: "12:00-18:00",
|
| 421 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 422 |
+
category: 'Short-Term'
|
| 423 |
+
},
|
| 424 |
+
{
|
| 425 |
+
id: 31,
|
| 426 |
+
name: "Pescher Christkindlmarkt",
|
| 427 |
+
lat: 51.0076,
|
| 428 |
+
lon: 6.8550,
|
| 429 |
+
start: "07.12.2025",
|
| 430 |
+
end: "07.12.2025",
|
| 431 |
+
ort: "Pesch, 50767 Köln",
|
| 432 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 433 |
+
description: "Traditioneller Christkindlmarkt in Pesch.",
|
| 434 |
+
openingHours: "11:00-18:00",
|
| 435 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 436 |
+
category: 'Short-Term'
|
| 437 |
+
},
|
| 438 |
+
{
|
| 439 |
+
id: 32,
|
| 440 |
+
name: "Waldweihnacht Gut Leidenhausen",
|
| 441 |
+
lat: 50.8879,
|
| 442 |
+
lon: 7.0825,
|
| 443 |
+
start: "29.11.2025",
|
| 444 |
+
end: "30.11.2025",
|
| 445 |
+
ort: "Gut Leidenhausen, 51147 Köln",
|
| 446 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 447 |
+
description: "Stimmungsvoller Markt im Wald rund um Gut Leidenhausen in Porz.",
|
| 448 |
+
openingHours: "11:00-20:00",
|
| 449 |
+
website: "https://www.gut-leidenhausen.de",
|
| 450 |
+
category: 'Short-Term'
|
| 451 |
+
},
|
| 452 |
+
{
|
| 453 |
+
id: 33,
|
| 454 |
+
name: "Mittelalterlicher Weihnachtsmarkt Alexianer",
|
| 455 |
+
lat: 50.8753,
|
| 456 |
+
lon: 7.0435,
|
| 457 |
+
start: "21.11.2025",
|
| 458 |
+
end: "22.11.2025",
|
| 459 |
+
ort: "Alexianer, Kölner Str. 64, 51149 Köln",
|
| 460 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 461 |
+
description: "Mittelalter-Markt der Alexianer mit Programm in Porz.",
|
| 462 |
+
openingHours: "Fr 07:00-20:00, Sa 10:00-19:00",
|
| 463 |
+
website: "https://www.alexianer.de",
|
| 464 |
+
category: 'Short-Term'
|
| 465 |
+
},
|
| 466 |
+
{
|
| 467 |
+
id: 34,
|
| 468 |
+
name: "Porzer Weihnachtsmarkt City Center",
|
| 469 |
+
lat: 50.8879,
|
| 470 |
+
lon: 7.0427,
|
| 471 |
+
start: "12.12.2025",
|
| 472 |
+
end: "14.12.2025",
|
| 473 |
+
ort: "City Center Porz, 51143 Köln",
|
| 474 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 475 |
+
description: "Stadtteil-Markt mit Wunschbaum-Aktion im City Center.",
|
| 476 |
+
openingHours: "13:00-18:00",
|
| 477 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 478 |
+
category: 'Short-Term'
|
| 479 |
+
},
|
| 480 |
+
{
|
| 481 |
+
id: 35,
|
| 482 |
+
name: "Adventsmarkt Wahner Wibbelstetze",
|
| 483 |
+
lat: 50.8770,
|
| 484 |
+
lon: 7.0801,
|
| 485 |
+
start: "29.11.2025",
|
| 486 |
+
end: "30.11.2025",
|
| 487 |
+
ort: "Marktplatz Wahn, 51147 Köln",
|
| 488 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 489 |
+
description: "Adventsmarkt des Karnevalsvereins Wahner Wibbelstetze.",
|
| 490 |
+
openingHours: "laut Verein",
|
| 491 |
+
website: "https://wahner-wibbelstetze.de",
|
| 492 |
+
category: 'Short-Term'
|
| 493 |
+
},
|
| 494 |
+
{
|
| 495 |
+
id: 36,
|
| 496 |
+
name: "Weihnachtsmarkt Rath Heumar",
|
| 497 |
+
lat: 50.9332,
|
| 498 |
+
lon: 7.0722,
|
| 499 |
+
start: "05.12.2025",
|
| 500 |
+
end: "07.12.2025",
|
| 501 |
+
ort: "Kurt-Henn-Platz, 51107 Köln",
|
| 502 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 503 |
+
description: "Veedel Markt auf dem Kurt-Henn-Platz.",
|
| 504 |
+
openingHours: "laut Veranstalter",
|
| 505 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 506 |
+
category: 'Short-Term'
|
| 507 |
+
},
|
| 508 |
+
{
|
| 509 |
+
id: 37,
|
| 510 |
+
name: "Winterzauber Maternusplatz",
|
| 511 |
+
lat: 50.8953,
|
| 512 |
+
lon: 6.9740,
|
| 513 |
+
start: "05.12.2025",
|
| 514 |
+
end: "07.12.2025",
|
| 515 |
+
ort: "Maternusplatz, 50996 Köln",
|
| 516 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 517 |
+
description: "Kleiner Wintermarkt auf dem Maternusplatz in Rodenkirchen.",
|
| 518 |
+
openingHours: "laut Veranstalter, meist nachmittags/abends",
|
| 519 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 520 |
+
category: 'Short-Term'
|
| 521 |
+
},
|
| 522 |
+
{
|
| 523 |
+
id: 38,
|
| 524 |
+
name: "Sülzer Weihnachtsdorf",
|
| 525 |
+
lat: 50.9192,
|
| 526 |
+
lon: 6.9189,
|
| 527 |
+
start: "25.11.2025",
|
| 528 |
+
end: "23.12.2025",
|
| 529 |
+
ort: "Elisabeth-von-Mumm-Platz, 50937 Köln",
|
| 530 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 531 |
+
description: "Bekannter Veedel Markt auf dem Elisabeth-von-Mumm-Platz.",
|
| 532 |
+
openingHours: "Mo-Fr 16:00-22:00, Sa-So 12:00-22:00",
|
| 533 |
+
website: "https://www.suelzer-weihnachtsdorf.de",
|
| 534 |
+
category: 'Community'
|
| 535 |
+
},
|
| 536 |
+
{
|
| 537 |
+
id: 39,
|
| 538 |
+
name: "Christmas G'art'en Wachsfabrik",
|
| 539 |
+
lat: 50.8703,
|
| 540 |
+
lon: 6.9959,
|
| 541 |
+
start: "13.12.2025",
|
| 542 |
+
end: "14.12.2025",
|
| 543 |
+
ort: "Wachsfabrik, Industriestr. 170, 50999 Köln",
|
| 544 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 545 |
+
description: "Kreativ-Markt in der Wachsfabrik mit Kunst und Design in Sürth.",
|
| 546 |
+
openingHours: "laut Veranstalter",
|
| 547 |
+
website: "https://www.wachsfabrik.de",
|
| 548 |
+
category: 'Short-Term'
|
| 549 |
+
},
|
| 550 |
+
{
|
| 551 |
+
id: 40,
|
| 552 |
+
name: "Sürther Weihnachtsmarkt",
|
| 553 |
+
lat: 50.8745,
|
| 554 |
+
lon: 6.9995,
|
| 555 |
+
start: "05.12.2025",
|
| 556 |
+
end: "07.12.2025",
|
| 557 |
+
ort: "Marktplatz Sürth, 50999 Köln",
|
| 558 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 559 |
+
description: "Kleiner Markt auf dem Sürther Marktplatz.",
|
| 560 |
+
openingHours: "laut Veranstalter",
|
| 561 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 562 |
+
category: 'Short-Term'
|
| 563 |
+
},
|
| 564 |
+
{
|
| 565 |
+
id: 41,
|
| 566 |
+
name: "Zollstocker Adventsmarkt",
|
| 567 |
+
lat: 50.9069,
|
| 568 |
+
lon: 6.9365,
|
| 569 |
+
start: "06.12.2025",
|
| 570 |
+
end: "07.12.2025",
|
| 571 |
+
ort: "Rund um St. Pius, 50969 Köln",
|
| 572 |
+
imageUrls: ["https://www.koeln.de/wp-content/uploads/2023/10/weihnachtsmakt-koeln-altstadt-2019-sw-0029.jpg"],
|
| 573 |
+
description: "Adventsmarkt rund um die Kirche St. Pius in Zollstock.",
|
| 574 |
+
openingHours: "Sa 14:00-20:00, So 12:00-18:00",
|
| 575 |
+
website: "https://www.koeln.de/weihnachten/weihnachtsmaerkte-koeln/weihnachtsmaerkte-koelner-stadtteile/",
|
| 576 |
+
category: 'Short-Term'
|
| 577 |
+
}
|
| 578 |
+
];
|
cologne-christmas-markets-map-2025_5/index.html
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>Cologne Christmas Markets Map 2025</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@700&family=EB+Garamond&display=swap" rel="stylesheet">
|
| 11 |
+
<link
|
| 12 |
+
rel="stylesheet"
|
| 13 |
+
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
| 14 |
+
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
| 15 |
+
crossorigin=""
|
| 16 |
+
/>
|
| 17 |
+
<style>
|
| 18 |
+
html, body, #root {
|
| 19 |
+
height: 100%;
|
| 20 |
+
width: 100%;
|
| 21 |
+
margin: 0;
|
| 22 |
+
padding: 0;
|
| 23 |
+
overflow: hidden;
|
| 24 |
+
font-family: 'EB Garamond', serif;
|
| 25 |
+
}
|
| 26 |
+
h1, h2, h3 {
|
| 27 |
+
font-family: 'Cinzel', serif;
|
| 28 |
+
}
|
| 29 |
+
.leaflet-container {
|
| 30 |
+
height: 100%;
|
| 31 |
+
width: 100%;
|
| 32 |
+
background-color: #f3f4f6; /* gray-100 */
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
/* Festive Popup Styling */
|
| 36 |
+
.leaflet-popup-content-wrapper {
|
| 37 |
+
background-color: #1c1917; /* stone-900 */
|
| 38 |
+
background-image: url('data:image/svg+xml,%3Csvg width="60" height="60" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M12 2L12 22M2 12L22 12M5.636 5.636L18.364 18.364M5.636 18.364L18.364 5.636M12 6L14.121 8.121M12 6L9.879 8.121M12 18L14.121 15.879M12 18L9.879 15.879M18.364 12L15.879 9.879M18.364 12L15.879 14.121M5.636 12L8.121 9.879M5.636 12L8.121 14.121" stroke="rgba(250, 204, 21, 0.08)" stroke-width="1" fill="none"/%3E%3C/svg%3E');
|
| 39 |
+
color: #d6d3d1; /* stone-300 */
|
| 40 |
+
border-radius: 8px;
|
| 41 |
+
border: 1px solid #f59e0b; /* amber-500 */
|
| 42 |
+
box-shadow: 0 0 15px rgba(245, 158, 11, 0.4), 0 4px 10px rgba(0,0,0,0.6);
|
| 43 |
+
padding: 0;
|
| 44 |
+
transform-origin: bottom center;
|
| 45 |
+
animation: popup-open 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
|
| 46 |
+
}
|
| 47 |
+
.leaflet-popup-content {
|
| 48 |
+
margin: 0;
|
| 49 |
+
width: 288px !important;
|
| 50 |
+
line-height: 1.6;
|
| 51 |
+
}
|
| 52 |
+
.leaflet-popup-content > div {
|
| 53 |
+
animation: content-fade-in 0.5s ease-out 0.2s forwards;
|
| 54 |
+
opacity: 0;
|
| 55 |
+
}
|
| 56 |
+
.leaflet-popup-tip {
|
| 57 |
+
background: #1c1917;
|
| 58 |
+
}
|
| 59 |
+
.leaflet-popup-close-button {
|
| 60 |
+
color: #facc15 !important; /* amber-400 */
|
| 61 |
+
padding: 4px 4px 0 0 !important;
|
| 62 |
+
transition: all 0.2s ease;
|
| 63 |
+
}
|
| 64 |
+
.leaflet-popup-close-button:hover {
|
| 65 |
+
color: #f59e0b !important; /* amber-500 */
|
| 66 |
+
transform: scale(1.2);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
@keyframes popup-open {
|
| 70 |
+
from {
|
| 71 |
+
opacity: 0;
|
| 72 |
+
transform: scale(0.8);
|
| 73 |
+
}
|
| 74 |
+
to {
|
| 75 |
+
opacity: 1;
|
| 76 |
+
transform: scale(1);
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
@keyframes content-fade-in {
|
| 80 |
+
from {
|
| 81 |
+
opacity: 0;
|
| 82 |
+
transform: translateY(10px);
|
| 83 |
+
}
|
| 84 |
+
to {
|
| 85 |
+
opacity: 1;
|
| 86 |
+
transform: translateY(0);
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.locate-me-btn {
|
| 91 |
+
background-color: #292524; /* stone-800 */
|
| 92 |
+
border: 2px solid #facc15; /* amber-400 */
|
| 93 |
+
color: #facc15;
|
| 94 |
+
font-size: 1.5rem;
|
| 95 |
+
line-height: 1;
|
| 96 |
+
border-radius: 8px;
|
| 97 |
+
width: 40px;
|
| 98 |
+
height: 40px;
|
| 99 |
+
display: flex;
|
| 100 |
+
align-items: center;
|
| 101 |
+
justify-content: center;
|
| 102 |
+
cursor: pointer;
|
| 103 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.7);
|
| 104 |
+
transition: background-color 0.2s ease-in-out;
|
| 105 |
+
}
|
| 106 |
+
.locate-me-btn:hover {
|
| 107 |
+
background-color: #44403c; /* stone-700 */
|
| 108 |
+
}
|
| 109 |
+
.user-location-pulse {
|
| 110 |
+
background-color: #38bdf8; /* sky-400 */
|
| 111 |
+
border: 2px solid white;
|
| 112 |
+
border-radius: 50%;
|
| 113 |
+
box-shadow: 0 0 0 0 rgba(56, 189, 248, 1);
|
| 114 |
+
transform: scale(1);
|
| 115 |
+
animation: pulse 2s infinite;
|
| 116 |
+
}
|
| 117 |
+
@keyframes pulse {
|
| 118 |
+
0% {
|
| 119 |
+
transform: scale(0.95);
|
| 120 |
+
box-shadow: 0 0 0 0 rgba(56, 189, 248, 0.7);
|
| 121 |
+
}
|
| 122 |
+
70% {
|
| 123 |
+
transform: scale(1);
|
| 124 |
+
box-shadow: 0 0 0 12px rgba(56, 189, 248, 0);
|
| 125 |
+
}
|
| 126 |
+
100% {
|
| 127 |
+
transform: scale(0.95);
|
| 128 |
+
box-shadow: 0 0 0 0 rgba(56, 189, 248, 0);
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
.modal-backdrop {
|
| 132 |
+
animation: fadeIn 0.3s ease-out forwards;
|
| 133 |
+
}
|
| 134 |
+
.modal-content {
|
| 135 |
+
animation: fadeIn 0.3s ease-out forwards, slideUp 0.4s ease-out forwards;
|
| 136 |
+
}
|
| 137 |
+
@keyframes fadeIn {
|
| 138 |
+
from { opacity: 0; }
|
| 139 |
+
to { opacity: 1; }
|
| 140 |
+
}
|
| 141 |
+
@keyframes slideUp {
|
| 142 |
+
from { transform: translateY(20px); }
|
| 143 |
+
to { transform: translateY(0); }
|
| 144 |
+
}
|
| 145 |
+
.selected-marker-icon {
|
| 146 |
+
animation: pulseGold 1.5s infinite ease-in-out;
|
| 147 |
+
}
|
| 148 |
+
@keyframes pulseGold {
|
| 149 |
+
0% {
|
| 150 |
+
filter: drop-shadow(0 0 2px #fde047);
|
| 151 |
+
transform: scale(1) rotate(0deg);
|
| 152 |
+
}
|
| 153 |
+
50% {
|
| 154 |
+
filter: drop-shadow(0 0 12px #fde047);
|
| 155 |
+
transform: scale(1.1) rotate(5deg);
|
| 156 |
+
}
|
| 157 |
+
100% {
|
| 158 |
+
filter: drop-shadow(0 0 2px #fde047);
|
| 159 |
+
transform: scale(1) rotate(0deg);
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
.hovered-marker-icon {
|
| 163 |
+
animation: festiveBob 2s infinite ease-in-out;
|
| 164 |
+
}
|
| 165 |
+
@keyframes festiveBob {
|
| 166 |
+
0%, 100% {
|
| 167 |
+
transform: translateY(0);
|
| 168 |
+
filter: drop-shadow(0 0 3px #f43f5e);
|
| 169 |
+
}
|
| 170 |
+
50% {
|
| 171 |
+
transform: translateY(-3px);
|
| 172 |
+
filter: drop-shadow(0 0 8px #f43f5e);
|
| 173 |
+
}
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
/* Snowfall Animation */
|
| 177 |
+
.snowflake {
|
| 178 |
+
color: #fff;
|
| 179 |
+
font-size: 1em;
|
| 180 |
+
font-family: Arial, sans-serif;
|
| 181 |
+
text-shadow: 0 0 5px #000;
|
| 182 |
+
position: absolute;
|
| 183 |
+
top: -10%;
|
| 184 |
+
z-index: 9999;
|
| 185 |
+
-webkit-user-select: none;
|
| 186 |
+
-moz-user-select: none;
|
| 187 |
+
-ms-user-select: none;
|
| 188 |
+
user-select: none;
|
| 189 |
+
cursor: default;
|
| 190 |
+
animation-name: snowflakes-fall, snowflakes-shake;
|
| 191 |
+
animation-duration: 10s, 3s;
|
| 192 |
+
animation-timing-function: linear, ease-in-out;
|
| 193 |
+
animation-iteration-count: infinite, infinite;
|
| 194 |
+
animation-play-state: running, running;
|
| 195 |
+
}
|
| 196 |
+
.snowflake:nth-of-type(0) { left: 1%; animation-delay: 0s, 0s; }
|
| 197 |
+
.snowflake:nth-of-type(1) { left: 10%; animation-delay: 1s, 1s; }
|
| 198 |
+
.snowflake:nth-of-type(2) { left: 20%; animation-delay: 6s, 0.5s; }
|
| 199 |
+
.snowflake:nth-of-type(3) { left: 30%; animation-delay: 4s, 2s; }
|
| 200 |
+
.snowflake:nth-of-type(4) { left: 40%; animation-delay: 2s, 2s; }
|
| 201 |
+
.snowflake:nth-of-type(5) { left: 50%; animation-delay: 8s, 3s; }
|
| 202 |
+
.snowflake:nth-of-type(6) { left: 60%; animation-delay: 6s, 2s; }
|
| 203 |
+
.snowflake:nth-of-type(7) { left: 70%; animation-delay: 2.5s, 1s; }
|
| 204 |
+
.snowflake:nth-of-type(8) { left: 80%; animation-delay: 1s, 0s; }
|
| 205 |
+
.snowflake:nth-of-type(9) { left: 90%; animation-delay: 3s, 1.5s; }
|
| 206 |
+
.snowflake:nth-of-type(10) { left: 25%; animation-delay: 2s, 0s; }
|
| 207 |
+
.snowflake:nth-of-type(11) { left: 65%; animation-delay: 4s, 2.5s; }
|
| 208 |
+
|
| 209 |
+
@keyframes snowflakes-fall {
|
| 210 |
+
0% { top: -10%; }
|
| 211 |
+
100% { top: 100%; }
|
| 212 |
+
}
|
| 213 |
+
@keyframes snowflakes-shake {
|
| 214 |
+
0%, 100% { transform: translateX(0); }
|
| 215 |
+
50% { transform: translateX(80px); }
|
| 216 |
+
}
|
| 217 |
+
</style>
|
| 218 |
+
<script type="importmap">
|
| 219 |
+
{
|
| 220 |
+
"imports": {
|
| 221 |
+
"react-dom/": "https://aistudiocdn.com/react-dom@^19.2.0/",
|
| 222 |
+
"@react-leaflet/core": "https://aistudiocdn.com/@react-leaflet/core@^5.0.0",
|
| 223 |
+
"react-leaflet": "https://aistudiocdn.com/react-leaflet@^5.0.0",
|
| 224 |
+
"leaflet": "https://aistudiocdn.com/leaflet@^1.9.4",
|
| 225 |
+
"react": "https://aistudiocdn.com/react@^19.2.0",
|
| 226 |
+
"react/": "https://aistudiocdn.com/react@^19.2.0/"
|
| 227 |
+
}
|
| 228 |
+
}
|
| 229 |
+
</script>
|
| 230 |
+
<link rel="stylesheet" href="/index.css">
|
| 231 |
+
</head>
|
| 232 |
+
<body>
|
| 233 |
+
<div id="root"></div>
|
| 234 |
+
<script type="module" src="/index.tsx"></script>
|
| 235 |
+
</body>
|
| 236 |
+
</html>
|
cologne-christmas-markets-map-2025_5/index.tsx
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import React from 'react';
|
| 3 |
+
import ReactDOM from 'react-dom/client';
|
| 4 |
+
import App from './App';
|
| 5 |
+
|
| 6 |
+
const rootElement = document.getElementById('root');
|
| 7 |
+
if (!rootElement) {
|
| 8 |
+
throw new Error("Could not find root element to mount to");
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
const root = ReactDOM.createRoot(rootElement);
|
| 12 |
+
root.render(
|
| 13 |
+
<React.StrictMode>
|
| 14 |
+
<App />
|
| 15 |
+
</React.StrictMode>
|
| 16 |
+
);
|
cologne-christmas-markets-map-2025_5/metadata.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Cologne Christmas Markets Map 2025",
|
| 3 |
+
"description": "An interactive map showcasing the Christmas markets in Cologne for 2025. Explore market locations, opening times, and details with a modern and festive design.",
|
| 4 |
+
"requestFramePermissions": [
|
| 5 |
+
"geolocation"
|
| 6 |
+
]
|
| 7 |
+
}
|
cologne-christmas-markets-map-2025_5/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "cologne-christmas-markets-map-2025",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"preview": "vite preview"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"react-dom": "^19.2.0",
|
| 13 |
+
"@react-leaflet/core": "^5.0.0",
|
| 14 |
+
"react-leaflet": "^5.0.0",
|
| 15 |
+
"leaflet": "^1.9.4",
|
| 16 |
+
"react": "^19.2.0"
|
| 17 |
+
},
|
| 18 |
+
"devDependencies": {
|
| 19 |
+
"@types/node": "^22.14.0",
|
| 20 |
+
"@vitejs/plugin-react": "^5.0.0",
|
| 21 |
+
"typescript": "~5.8.2",
|
| 22 |
+
"vite": "^6.2.0"
|
| 23 |
+
}
|
| 24 |
+
}
|
cologne-christmas-markets-map-2025_5/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2022",
|
| 4 |
+
"experimentalDecorators": true,
|
| 5 |
+
"useDefineForClassFields": false,
|
| 6 |
+
"module": "ESNext",
|
| 7 |
+
"lib": [
|
| 8 |
+
"ES2022",
|
| 9 |
+
"DOM",
|
| 10 |
+
"DOM.Iterable"
|
| 11 |
+
],
|
| 12 |
+
"skipLibCheck": true,
|
| 13 |
+
"types": [
|
| 14 |
+
"node"
|
| 15 |
+
],
|
| 16 |
+
"moduleResolution": "bundler",
|
| 17 |
+
"isolatedModules": true,
|
| 18 |
+
"moduleDetection": "force",
|
| 19 |
+
"allowJs": true,
|
| 20 |
+
"jsx": "react-jsx",
|
| 21 |
+
"paths": {
|
| 22 |
+
"@/*": [
|
| 23 |
+
"./*"
|
| 24 |
+
]
|
| 25 |
+
},
|
| 26 |
+
"allowImportingTsExtensions": true,
|
| 27 |
+
"noEmit": true
|
| 28 |
+
}
|
| 29 |
+
}
|
cologne-christmas-markets-map-2025_5/types.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
export interface Market {
|
| 3 |
+
id: number;
|
| 4 |
+
name: string;
|
| 5 |
+
lat: number;
|
| 6 |
+
lon: number;
|
| 7 |
+
start: string;
|
| 8 |
+
end:string;
|
| 9 |
+
ort: string;
|
| 10 |
+
imageUrls: string[];
|
| 11 |
+
description: string;
|
| 12 |
+
openingHours: string;
|
| 13 |
+
website?: string;
|
| 14 |
+
category: 'Traditional' | 'Themed' | 'Community' | 'Short-Term';
|
| 15 |
+
}
|
cologne-christmas-markets-map-2025_5/vite.config.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import path from 'path';
|
| 2 |
+
import { defineConfig, loadEnv } from 'vite';
|
| 3 |
+
import react from '@vitejs/plugin-react';
|
| 4 |
+
|
| 5 |
+
export default defineConfig(({ mode }) => {
|
| 6 |
+
const env = loadEnv(mode, '.', '');
|
| 7 |
+
return {
|
| 8 |
+
server: {
|
| 9 |
+
port: 3000,
|
| 10 |
+
host: '0.0.0.0',
|
| 11 |
+
},
|
| 12 |
+
plugins: [react()],
|
| 13 |
+
define: {
|
| 14 |
+
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
| 15 |
+
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
| 16 |
+
},
|
| 17 |
+
resolve: {
|
| 18 |
+
alias: {
|
| 19 |
+
'@': path.resolve(__dirname, '.'),
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
};
|
| 23 |
+
});
|