commit 7db42fd7843e8d8f90915ced1695b9d1ef45bcef Author: Cosmo Date: Sat Mar 21 04:59:39 2026 +0000 Initial commit: Japan PWA guide diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..969a3da --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules +dist +.git +*.md +.env* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7a3bc86 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# Build stage +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy source +COPY . . + +# Build +RUN npm run build + +# Production stage +FROM nginx:alpine + +# Copy built files +COPY --from=builder /app/dist /usr/share/nginx/html + +# Copy nginx config +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Expose port +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..23d1586 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# 🗾 Japan Trip Companion + +PWA-приложение для поездки в Японию 3-18 марта 2026. + +## Функции + +- 🗺️ **Карта** — все точки на интерактивной карте (Leaflet/OSM) +- 📅 **План дня** — маршрут на каждый из 16 дней +- 📍 **Места** — 45+ достопримечательностей с поиском и фильтрами +- 🍜 **Еда** — рестораны и рынки +- 📍 **GPS** — текущая позиция и "что рядом" +- 📱 **Офлайн** — PWA с кэшированием карт + +## Технологии + +- React 18 + TypeScript + Vite +- Tailwind CSS +- Leaflet + OpenStreetMap +- vite-plugin-pwa + +## Деплой + +```bash +docker compose build +docker compose up -d +``` + +## Nginx Proxy Manager + +- **Domain:** japan.digital-home.site +- **Forward Host:** japan-app (или 172.18.0.16) +- **Forward Port:** 80 +- **SSL:** Let's Encrypt + +## Города + +- 🗼 Токио (дни 1-4) +- 🗻 Хаконэ (дни 5-6) +- ⛩️ Киото (дни 7-9, 11) +- 🦌 Нара (день 10) +- 🏯 Осака (дни 12-14) +- 🕊️ Хиросима (дни 15-16) diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..76b78bf --- /dev/null +++ b/deploy.sh @@ -0,0 +1,6 @@ +#!/bin/bash +cd /opt/digital-home/japan-app +docker compose down +docker compose build --no-cache +docker compose up -d +docker compose logs -f diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ac78a32 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +services: + japan-app: + build: . + container_name: japan-app + restart: unless-stopped + networks: + - services_proxy + labels: + - "com.centurylinklabs.watchtower.enable=false" + +networks: + services_proxy: + external: true diff --git a/index.html b/index.html new file mode 100644 index 0000000..2b5108b --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + 🗾 Japan Trip 2026 + + +
+ + + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..776b167 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,30 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript application/json; + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Service worker - no cache + location /sw.js { + add_header Cache-Control "no-cache"; + expires off; + } + + # SPA fallback + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1545e7b --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "japan-trip-companion", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.22.0", + "leaflet": "^1.9.4", + "react-leaflet": "^4.2.1" + }, + "devDependencies": { + "@types/react": "^18.2.55", + "@types/react-dom": "^18.2.19", + "@types/leaflet": "^1.9.8", + "@vitejs/plugin-react": "^4.2.1", + "typescript": "^5.3.3", + "vite": "^5.1.0", + "vite-plugin-pwa": "^0.19.2", + "autoprefixer": "^10.4.17", + "postcss": "^8.4.35", + "tailwindcss": "^3.4.1" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/icon-192.png b/public/icon-192.png new file mode 100644 index 0000000..95d4b24 Binary files /dev/null and b/public/icon-192.png differ diff --git a/public/icon-192.svg b/public/icon-192.svg new file mode 100644 index 0000000..ad4c232 --- /dev/null +++ b/public/icon-192.svg @@ -0,0 +1,5 @@ + + + + 🗾 + diff --git a/public/icon-512.png b/public/icon-512.png new file mode 100644 index 0000000..95d4b24 Binary files /dev/null and b/public/icon-512.png differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..c2a49f4 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..3c3b60b --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,87 @@ +import { useState, useCallback } from 'react'; +import { TabType, Place } from './types'; +import { Navigation } from './components/Navigation'; +import { PlaceDetails } from './components/PlaceDetails'; +import { MapPage } from './pages/MapPage'; +import { PlanPage } from './pages/PlanPage'; +import { PlacesPage } from './pages/PlacesPage'; +import { FoodPage } from './pages/FoodPage'; +import { useGeolocation } from './hooks/useGeolocation'; +import { usePlaces } from './hooks/usePlaces'; + +function App() { + const [activeTab, setActiveTab] = useState('plan'); + const [selectedPlace, setSelectedPlace] = useState(null); + const [detailsPlace, setDetailsPlace] = useState(null); + const [showRouteForDay, setShowRouteForDay] = useState(null); + + const { position } = useGeolocation(); + const { places } = usePlaces(position); + + const handleSelectPlace = useCallback((place: Place) => { + setDetailsPlace(place); + }, []); + + const handleNavigateToPlace = useCallback((place: Place) => { + setSelectedPlace(place); + setDetailsPlace(null); + setActiveTab('map'); + }, []); + + const handleShowDayRoute = useCallback((day: number) => { + setShowRouteForDay(day); + setActiveTab('map'); + }, []); + + const handleCloseDetails = useCallback(() => { + setDetailsPlace(null); + }, []); + + return ( +
+
+ {activeTab === 'map' && ( + setShowRouteForDay(null)} + /> + )} + {activeTab === 'plan' && ( + + )} + {activeTab === 'places' && ( + + )} + {activeTab === 'food' && ( + + )} +
+ + + + p.id === detailsPlace?.id) || null} + onClose={handleCloseDetails} + onNavigate={handleNavigateToPlace} + /> +
+ ); +} + +export default App; diff --git a/src/components/DayTimeline.tsx b/src/components/DayTimeline.tsx new file mode 100644 index 0000000..50fd4be --- /dev/null +++ b/src/components/DayTimeline.tsx @@ -0,0 +1,208 @@ +import { Place } from '../types'; + +interface ScheduleItem { + time: string; + placeId?: number; + name: string; + note?: string; + type?: string; +} + +interface DaySchedule { + date: string; + dayOfWeek: string; + city: string; + title: string; + schedule: ScheduleItem[]; +} + +interface DayTimelineProps { + day: number; + dayData: DaySchedule; + places: Place[]; + onSelectPlace: (place: Place) => void; + isToday?: boolean; +} + +const getTypeIcon = (type?: string, category?: string): string => { + if (type === 'transport') return '🚃'; + if (type === 'lunch' || type === 'dinner') return '🍽️'; + if (type === 'hotel') return '🏨'; + if (type === 'onsen') return '♨️'; + if (type === 'free') return '✨'; + if (type === 'attraction') return '🎢'; + if (category === 'sight') return '🏯'; + if (category === 'restaurant') return '🍜'; + if (category === 'coffee') return '☕'; + if (category === 'snack') return '🍡'; + if (category === 'hotel') return '🏨'; + return '📍'; +}; + +const getCityEmoji = (city: string): string => { + const cities: Record = { + tokyo: '🗼', + kyoto: '⛩️', + osaka: '🏯', + nara: '🦌', + hiroshima: '🕊️', + miyajima: '⛩️', + hakone: '🗻', + nikko: '🌲', + himeji: '🏰', + }; + return cities[city] || '📍'; +}; + +const formatDuration = (minutes: number): string => { + if (minutes < 60) return `${minutes}мин`; + const hours = Math.floor(minutes / 60); + const mins = minutes % 60; + return mins > 0 ? `${hours}ч ${mins}мин` : `${hours}ч`; +}; + +const parseDuration = (duration?: string): number => { + if (!duration) return 60; + const match = duration.match(/([\d.]+)[-–]?([\d.]+)?\s*(час|ч|hour|минут|мин)/i); + if (match) { + const val = parseFloat(match[2] || match[1]); + if (duration.includes('час') || duration.includes('hour') || duration.includes('ч')) { + return Math.round(val * 60); + } + return Math.round(val); + } + return 60; +}; + +const getEndTime = (startTime: string, durationMinutes: number): string => { + const [hours, minutes] = startTime.split(':').map(Number); + const totalMinutes = hours * 60 + minutes + durationMinutes; + const endHours = Math.floor(totalMinutes / 60) % 24; + const endMinutes = totalMinutes % 60; + return `${endHours.toString().padStart(2, '0')}:${endMinutes.toString().padStart(2, '0')}`; +}; + +export function DayTimeline({ day, dayData, places, onSelectPlace, isToday }: DayTimelineProps) { + const formatDate = (dateStr: string) => { + const date = new Date(dateStr); + return date.toLocaleDateString('ru-RU', { + day: 'numeric', + month: 'long', + weekday: 'short' + }); + }; + + return ( +
+ {/* Day Header */} +
+
+ {getCityEmoji(dayData.city)} +
+
+

День {day}

+ {isToday && ( + + Сегодня + + )} +
+

{formatDate(dayData.date)}

+
+
+

{dayData.title}

+
+ + {/* Timeline - используем grid для точного выравнивания */} +
+ {dayData.schedule.map((item, index) => { + const place = item.placeId ? places.find(p => p.id === item.placeId) : null; + const duration = place ? parseDuration(place.duration) : 60; + const endTime = getEndTime(item.time, duration); + const icon = getTypeIcon(item.type, place?.category); + const isActivity = !item.type || item.type === 'attraction'; + const isLast = index === dayData.schedule.length - 1; + + return ( +
+ {/* Time column */} +
+ {item.time} +
+ + {/* Timeline column with dot and line */} +
+ {/* Вертикальная линия */} + {!isLast && ( +
+ )} + {/* Точка */} +
+
+ + {/* Content column */} +
+ {place ? ( + + ) : ( +
+
+ {icon} +
+

{item.name}

+ {item.note && ( +

{item.note}

+ )} +
+
+
+ )} +
+
+ ); + })} +
+
+ ); +} diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx new file mode 100644 index 0000000..a6aa3ae --- /dev/null +++ b/src/components/Navigation.tsx @@ -0,0 +1,47 @@ +import { TabType } from '../types'; + +interface NavigationProps { + activeTab: TabType; + onTabChange: (tab: TabType) => void; +} + +const tabs: { id: TabType; label: string; icon: string }[] = [ + { id: 'plan', label: 'Сегодня', icon: '📅' }, + { id: 'places', label: 'Дни', icon: '🗓️' }, + { id: 'map', label: 'Карта', icon: '🗺️' }, + { id: 'food', label: 'Еда', icon: '🍜' }, +]; + +export function Navigation({ activeTab, onTabChange }: NavigationProps) { + return ( + + ); +} diff --git a/src/components/PlaceCard.tsx b/src/components/PlaceCard.tsx new file mode 100644 index 0000000..54f8d62 --- /dev/null +++ b/src/components/PlaceCard.tsx @@ -0,0 +1,103 @@ +import { Place } from '../types'; + +interface PlaceCardProps { + place: Place; + onClick: () => void; + showDay?: boolean; + compact?: boolean; +} + +const getCategoryIcon = (category: string): string => { + const icons: Record = { + sight: '🏯', + restaurant: '🍜', + coffee: '☕', + snack: '🍡', + hotel: '🏨', + }; + return icons[category] || '📍'; +}; + +const getCategoryLabel = (category: string): string => { + const labels: Record = { + sight: 'Достопримечательность', + restaurant: 'Ресторан', + coffee: 'Кофейня', + snack: 'Перекус', + hotel: 'Отель', + }; + return labels[category] || 'Место'; +}; + +export function PlaceCard({ place, onClick, showDay = false, compact = false }: PlaceCardProps) { + if (compact) { + return ( + + ); + } + + return ( + + ); +} diff --git a/src/components/PlaceDetails.tsx b/src/components/PlaceDetails.tsx new file mode 100644 index 0000000..47b09cd --- /dev/null +++ b/src/components/PlaceDetails.tsx @@ -0,0 +1,197 @@ +import { Place } from '../types'; + +interface PlaceDetailsProps { + place: Place | null; + onClose: () => void; + onNavigate: (place: Place) => void; +} + +const getCategoryIcon = (category: string): string => { + const icons: Record = { + sight: '🏯', + restaurant: '🍜', + coffee: '☕', + snack: '🍡', + hotel: '🏨', + }; + return icons[category] || '📍'; +}; + +export function PlaceDetails({ place, onClose, onNavigate }: PlaceDetailsProps) { + if (!place) return null; + + const openGoogleMaps = () => { + const url = `https://www.google.com/maps/dir/?api=1&destination=${place.lat},${place.lng}&destination_place_id=${encodeURIComponent(place.name)}`; + window.open(url, '_blank'); + }; + + return ( + <> + {/* Backdrop */} +
+ + {/* Modal */} +
+
+ {/* Handle */} +
+
+
+ + {/* Header image/placeholder */} +
+
+ {getCategoryIcon(place.category)} +
+ + {place.rating === 5 && ( +
+ ★ Must See +
+ )} +
+ + {/* Content */} +
+ {/* Title */} +
+

{place.name}

+

{place.nameJp}

+
+ + {/* Quick info */} +
+ {place.day && ( + + 📅 День {place.day} + + )} + {place.duration && ( + + ⏱️ {place.duration} + + )} + {place.price && ( + + {place.price === 'Бесплатно' ? '✓ Бесплатно' : `💰 ${place.price}`} + + )} +
+ + {/* Description */} +

{place.description}

+ + {/* Details grid */} +
+ {place.bestTime && ( +
+ 🌅 +
+

Лучшее время

+

{place.bestTime}

+
+
+ )} + + {place.hours && ( +
+ 🕐 +
+

Часы работы

+

{place.hours}

+
+
+ )} + + {place.address && ( +
+ 📍 +
+

Адрес

+

{place.address}

+
+
+ )} +
+ + {/* History */} + {place.history && ( +
+

📜 История

+

{place.history}

+
+ )} + + {/* Facts */} + {place.facts && place.facts.length > 0 && ( +
+

💡 Интересные факты

+
    + {place.facts.map((fact, i) => ( +
  • + + {fact} +
  • + ))} +
+
+ )} + + {/* Tips */} + {(place.tips || place.localTips) && ( +
+

💡 Советы

+

{place.tips || place.localTips}

+
+ )} + + {/* Photo spots */} + {place.photoSpots && ( +
+

📸 Фото-споты

+

{place.photoSpots}

+
+ )} +
+ + {/* Action buttons */} +
+ + +
+
+
+ + ); +} diff --git a/src/components/TripProgress.tsx b/src/components/TripProgress.tsx new file mode 100644 index 0000000..d363b46 --- /dev/null +++ b/src/components/TripProgress.tsx @@ -0,0 +1,43 @@ +interface TripProgressProps { + currentDay: number; + totalDays: number; + tripStartDate: string; +} + +export function TripProgress({ currentDay, totalDays, tripStartDate }: TripProgressProps) { + const progress = Math.min((currentDay / totalDays) * 100, 100); + const startDate = new Date(tripStartDate); + const endDate = new Date(startDate); + endDate.setDate(endDate.getDate() + totalDays - 1); + + const formatDate = (date: Date) => { + return date.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' }); + }; + + return ( +
+
+
+ День {currentDay} + из {totalDays} +
+
+ 🇯🇵 + Japan Trip +
+
+ +
+
+
+ +
+ {formatDate(startDate)} + {formatDate(endDate)} +
+
+ ); +} diff --git a/src/data/places.json b/src/data/places.json new file mode 100644 index 0000000..811b6b9 --- /dev/null +++ b/src/data/places.json @@ -0,0 +1,1831 @@ +[ + { + "id": 1, + "name": "Сэнсодзи", + "nameJp": "浅草寺", + "category": "sight", + "city": "tokyo", + "lat": 35.7148, + "lng": 139.7967, + "description": "Древнейший храм Токио (645 г.), знаменитые ворота Каминаримон с гигантским фонарём. 20 млн паломников в год!", + "address": "2-3-1 Asakusa, Taito City, Tokyo", + "hours": "6:00-17:00", + "price": "Бесплатно", + "rating": 5, + "day": 1, + "links": [ + "https://www.senso-ji.jp/" + ], + "tips": "Приходите к 5:45 на утреннюю церемонию" + }, + { + "id": 2, + "name": "Парк Уэно", + "nameJp": "上野公園", + "category": "sight", + "city": "tokyo", + "lat": 35.7146, + "lng": 139.7732, + "description": "Первый европейский парк Японии (1873), пруд Синобадзу, 1200 деревьев сакуры. Рай для отдыха!", + "address": "Uenokoen, Taito City, Tokyo", + "hours": "5:00-23:00", + "price": "Бесплатно", + "rating": 4, + "day": 1, + "links": [] + }, + { + "id": 3, + "name": "Tokyo Skytree", + "nameJp": "東京スカイツリー", + "category": "sight", + "city": "tokyo", + "lat": 35.7101, + "lng": 139.8107, + "description": "Самая высокая телебашня мира (634м). Смотровые на 350м и 450м с панорамой на Фудзи!", + "address": "1-1-2 Oshiage, Sumida City, Tokyo", + "hours": "8:00-22:00", + "price": "¥2100-3100", + "rating": 5, + "day": 1, + "links": [ + "https://www.tokyo-skytree.jp/" + ], + "tips": "Приходите за час до заката" + }, + { + "id": 4, + "name": "Акихабара", + "nameJp": "秋葉原", + "category": "sight", + "city": "tokyo", + "lat": 35.7023, + "lng": 139.7745, + "description": "Район электроники и отаку-культуры. 500+ магазинов техники, аниме, мейд-кафе!", + "address": "Akihabara, Chiyoda City, Tokyo", + "hours": "10:00-20:00", + "price": "Бесплатно", + "rating": 4, + "day": 1, + "links": [] + }, + { + "id": 5, + "name": "Храм Мэйдзи", + "nameJp": "明治神宮", + "category": "sight", + "city": "tokyo", + "lat": 35.6764, + "lng": 139.6993, + "description": "Синтоистский храм в честь императора Мэйдзи. 100,000 деревьев, священный лес в центре мегаполиса.", + "address": "1-1 Yoyogikamizonocho, Shibuya City, Tokyo", + "hours": "Рассвет-закат", + "price": "Бесплатно", + "rating": 5, + "day": 2, + "links": [ + "https://www.meijijingu.or.jp/" + ] + }, + { + "id": 6, + "name": "Улица Такэсита", + "nameJp": "竹下通り", + "category": "sight", + "city": "tokyo", + "lat": 35.6718, + "lng": 139.7028, + "description": "Мекка молодёжной моды в Харадзюку. Крепы, косплей, необычные магазины!", + "address": "Takeshita Street, Shibuya, Tokyo", + "hours": "10:00-20:00", + "price": "Бесплатно", + "rating": 4, + "day": 2, + "links": [] + }, + { + "id": 7, + "name": "Сибуя — перекрёсток", + "nameJp": "渋谷スクランブル交差点", + "category": "sight", + "city": "tokyo", + "lat": 35.6595, + "lng": 139.7004, + "description": "Самый загруженный перекрёсток мира! До 3000 человек одновременно переходят улицу.", + "address": "Shibuya Scramble Crossing, Tokyo", + "hours": "24/7", + "price": "Бесплатно", + "rating": 5, + "day": 2, + "links": [] + }, + { + "id": 8, + "name": "Shibuya Sky", + "nameJp": "渋谷スカイ", + "category": "sight", + "city": "tokyo", + "lat": 35.6584, + "lng": 139.7022, + "description": "Смотровая на крыше Shibuya Scramble Square (230м). Потрясающий вид на закат!", + "address": "2-24-12 Shibuya, Shibuya City, Tokyo", + "hours": "10:00-22:30", + "price": "¥2000", + "rating": 5, + "day": 2, + "links": [ + "https://www.shibuya-scramble-square.com/sky/" + ], + "tips": "Бронируйте онлайн заранее!" + }, + { + "id": 9, + "name": "Синдзюку Гёэн", + "nameJp": "新宿御苑", + "category": "sight", + "city": "tokyo", + "lat": 35.6852, + "lng": 139.71, + "description": "Императорский сад с тремя стилями: японский, французский, английский. Лучшее место для сакуры!", + "address": "11 Naitomachi, Shinjuku City, Tokyo", + "hours": "9:00-16:30", + "price": "¥500", + "rating": 5, + "day": 2, + "links": [ + "https://fng.or.jp/shinjuku/" + ] + }, + { + "id": 10, + "name": "Цукидзи", + "nameJp": "築地場外市場", + "category": "restaurant", + "city": "tokyo", + "lat": 35.6654, + "lng": 139.7707, + "description": "Легендарный рыбный рынок. Свежайшие суши, тамаго-сандо, морепродукты на завтрак!", + "address": "Tsukiji, Chuo City, Tokyo", + "hours": "5:00-14:00", + "price": "¥1500-3000", + "rating": 5, + "day": 3, + "links": [], + "tips": "Приходите рано утром" + }, + { + "id": 11, + "name": "teamLab Planets", + "nameJp": "チームラボ プラネッツ", + "category": "sight", + "city": "tokyo", + "lat": 35.6427, + "lng": 139.7889, + "description": "Иммерсивное цифровое искусство — ходите босиком по воде и свету! Незабываемый опыт.", + "address": "6-1-16 Toyosu, Koto City, Tokyo", + "hours": "9:00-22:00", + "price": "¥3200", + "rating": 5, + "day": 3, + "links": [ + "https://planets.teamlab.art/" + ], + "tips": "Бронируйте за 2 недели!" + }, + { + "id": 12, + "name": "Одайба", + "nameJp": "お台場", + "category": "sight", + "city": "tokyo", + "lat": 35.6267, + "lng": 139.7768, + "description": "Футуристический остров: Радужный мост, статуя Gundam в натуральную величину, ночные огни залива.", + "address": "Odaiba, Minato City, Tokyo", + "hours": "24/7", + "price": "Бесплатно", + "rating": 4, + "day": 3, + "links": [] + }, + { + "id": 13, + "name": "Императорский дворец", + "nameJp": "皇居", + "category": "sight", + "city": "tokyo", + "lat": 35.6852, + "lng": 139.7528, + "description": "Резиденция императора Японии. Восточные сады бесплатны, экскурсии по бронированию.", + "address": "1-1 Chiyoda, Chiyoda City, Tokyo", + "hours": "9:00-16:00 (сады)", + "price": "Бесплатно", + "rating": 4, + "day": 3, + "links": [ + "https://sankan.kunaicho.go.jp/" + ] + }, + { + "id": 14, + "name": "Никко Тосё-гу", + "nameJp": "日光東照宮", + "category": "sight", + "city": "nikko", + "lat": 36.758, + "lng": 139.5988, + "description": "Роскошный храмовый комплекс UNESCO, мавзолей сёгуна Токугава Иэясу. Знаменитые \"три обезьяны\"!", + "address": "2301 Sannai, Nikko, Tochigi", + "hours": "8:00-17:00", + "price": "¥1300", + "rating": 5, + "day": 4, + "links": [ + "https://www.toshogu.jp/" + ], + "tips": "2 часа от Токио на поезде" + }, + { + "id": 15, + "name": "Водопад Кэгон", + "nameJp": "華厳の滝", + "category": "sight", + "city": "nikko", + "lat": 36.7383, + "lng": 139.5006, + "description": "Один из красивейших водопадов Японии (97м). Смотровая площадка на лифте вниз!", + "address": "Chugushi, Nikko, Tochigi", + "hours": "8:00-17:00", + "price": "¥570", + "rating": 5, + "day": 4, + "links": [] + }, + { + "id": 16, + "name": "Овакудани", + "nameJp": "大涌谷", + "category": "sight", + "city": "hakone", + "lat": 35.2429, + "lng": 139.0208, + "description": "Вулканическая долина с горячими источниками. Чёрные яйца +7 лет жизни! Вид на Фудзи.", + "address": "Owakudani, Hakone, Kanagawa", + "hours": "9:00-17:00", + "price": "¥1500 (канатка)", + "rating": 5, + "day": 5, + "links": [], + "tips": "Приезжайте в ясную погоду для вида на Фудзи" + }, + { + "id": 17, + "name": "Озеро Аси", + "nameJp": "芦ノ湖", + "category": "sight", + "city": "hakone", + "lat": 35.1953, + "lng": 139.0229, + "description": "Кратерное озеро с видом на Фудзи. Прогулка на \"пиратском корабле\" — must do!", + "address": "Lake Ashi, Hakone, Kanagawa", + "hours": "9:30-17:00", + "price": "¥1000 (корабль)", + "rating": 5, + "day": 6, + "links": [] + }, + { + "id": 18, + "name": "Храм Хаконэ-дзиндзя", + "nameJp": "箱根神社", + "category": "sight", + "city": "hakone", + "lat": 35.2042, + "lng": 139.0228, + "description": "Древний храм у озера Аси (757 г.). Знаменитые красные тории в воде!", + "address": "80-1 Motohakone, Hakone, Kanagawa", + "hours": "24/7", + "price": "Бесплатно", + "rating": 5, + "day": 6, + "links": [] + }, + { + "id": 19, + "name": "Фусими Инари", + "nameJp": "伏見稲荷大社", + "category": "sight", + "city": "kyoto", + "lat": 34.9671, + "lng": 135.7727, + "description": "10,000+ красных тории! Путь на вершину 2-3 часа. Самый фотогеничный храм Японии.", + "address": "68 Fukakusa Yabunouchicho, Fushimi Ward, Kyoto", + "hours": "24/7", + "price": "Бесплатно", + "rating": 5, + "day": 7, + "links": [ + "http://inari.jp/" + ], + "tips": "Приходите к 7:00 — почти никого нет!" + }, + { + "id": 20, + "name": "Кийомидзу-дэра", + "nameJp": "清水寺", + "category": "sight", + "city": "kyoto", + "lat": 34.9949, + "lng": 135.785, + "description": "Деревянный храм на сваях (778 г.), БЕЗ ЕДИНОГО ГВОЗДЯ! UNESCO, вид на весь Киото.", + "address": "1-294 Kiyomizu, Higashiyama Ward, Kyoto", + "hours": "6:00-18:00", + "price": "¥400", + "rating": 5, + "day": 7, + "links": [ + "https://www.kiyomizudera.or.jp/" + ] + }, + { + "id": 21, + "name": "Гион", + "nameJp": "祇園", + "category": "sight", + "city": "kyoto", + "lat": 35.0037, + "lng": 135.7756, + "description": "Легендарный квартал гейш. Вечером шанс увидеть настоящих майко! Атмосфера старой Японии.", + "address": "Gion, Higashiyama Ward, Kyoto", + "hours": "Гейши 17:00-19:00", + "price": "Бесплатно", + "rating": 5, + "day": 7, + "links": [], + "tips": "НЕ бегите за гейшами с камерой!" + }, + { + "id": 22, + "name": "Бамбуковый лес Арасияма", + "nameJp": "嵐山竹林", + "category": "sight", + "city": "kyoto", + "lat": 35.0173, + "lng": 135.6717, + "description": "Магический бамбуковый лес — звук ветра в стеблях признан сокровищем Японии!", + "address": "Sagaogurayama, Ukyo Ward, Kyoto", + "hours": "24/7", + "price": "Бесплатно", + "rating": 5, + "day": 8, + "links": [], + "tips": "Приходите к 7 утра" + }, + { + "id": 23, + "name": "Кинкаку-дзи (Золотой павильон)", + "nameJp": "金閣寺", + "category": "sight", + "city": "kyoto", + "lat": 35.0394, + "lng": 135.7292, + "description": "Самый фотографируемый храм Японии! 3 этажа покрыты настоящим золотом (5 кг).", + "address": "1 Kinkakujicho, Kita Ward, Kyoto", + "hours": "9:00-17:00", + "price": "¥400", + "rating": 5, + "day": 8, + "links": [ + "https://www.shokoku-ji.jp/kinkakuji/" + ] + }, + { + "id": 24, + "name": "Рёан-дзи", + "nameJp": "龍安寺", + "category": "sight", + "city": "kyoto", + "lat": 35.0345, + "lng": 135.7182, + "description": "Самый знаменитый сад камней в мире. 15 камней, которые невозможно увидеть все сразу!", + "address": "13 Ryoanji Goryonoshitacho, Ukyo Ward, Kyoto", + "hours": "8:00-17:00", + "price": "¥500", + "rating": 4, + "day": 8, + "links": [] + }, + { + "id": 25, + "name": "Гинкаку-дзи (Серебряный павильон)", + "nameJp": "銀閣寺", + "category": "sight", + "city": "kyoto", + "lat": 35.0271, + "lng": 135.7982, + "description": "Изысканный дзен-храм с садом. Начало Философского пути. Серебра нет, но красота есть!", + "address": "2 Ginkakujicho, Sakyo Ward, Kyoto", + "hours": "8:30-17:00", + "price": "¥500", + "rating": 4, + "day": 9, + "links": [] + }, + { + "id": 26, + "name": "Философский путь", + "nameJp": "哲学の道", + "category": "sight", + "city": "kyoto", + "lat": 35.02, + "lng": 135.795, + "description": "2км вдоль канала от Гинкаку-дзи до Нандзэн-дзи. Вишни, кафе, храмы. Назван в честь философа Нисида Китаро.", + "address": "Tetsugaku no Michi, Sakyo Ward, Kyoto", + "hours": "24/7", + "price": "Бесплатно", + "rating": 5, + "day": 9, + "links": [], + "tips": "Утро или после обеда, прогулка 1-1.5 часа" + }, + { + "id": 27, + "name": "Нисики Маркет", + "nameJp": "錦市場", + "category": "restaurant", + "city": "kyoto", + "lat": 35.005, + "lng": 135.7647, + "description": "\"Кухня Киото\" — 400-летний рынок. Маття мороженое, тамаго, моти, цукемоно!", + "address": "Nishiki Market, Nakagyo Ward, Kyoto", + "hours": "9:00-18:00", + "price": "¥500-2000", + "rating": 5, + "day": 9, + "links": [] + }, + { + "id": 28, + "name": "Замок Нидзо", + "nameJp": "二条城", + "category": "sight", + "city": "kyoto", + "lat": 35.0142, + "lng": 135.7481, + "description": "Резиденция сёгунов Токугава. \"Поющие полы\" предупреждали о ниндзя! UNESCO.", + "address": "541 Nijojocho, Nakagyo Ward, Kyoto", + "hours": "8:45-17:00", + "price": "¥1000", + "rating": 5, + "day": 11, + "links": [] + }, + { + "id": 29, + "name": "Нара Парк", + "nameJp": "奈良公園", + "category": "sight", + "city": "nara", + "lat": 34.6851, + "lng": 135.843, + "description": "1200+ священных оленей свободно гуляют по парку! Можно покормить и сфотографироваться.", + "address": "Nara Park, Nara", + "hours": "24/7", + "price": "Бесплатно (корм ¥200)", + "rating": 5, + "day": 10, + "links": [], + "tips": "Олени кусаются, если дразнить едой" + }, + { + "id": 30, + "name": "Тодай-дзи", + "nameJp": "東大寺", + "category": "sight", + "city": "nara", + "lat": 34.689, + "lng": 135.8399, + "description": "Гигантский бронзовый Будда (15м) в самом большом деревянном здании мира!", + "address": "406-1 Zoshicho, Nara", + "hours": "7:30-17:30", + "price": "¥600", + "rating": 5, + "day": 10, + "links": [ + "https://www.todaiji.or.jp/" + ] + }, + { + "id": 31, + "name": "Касуга-тайся", + "nameJp": "春日大社", + "category": "sight", + "city": "nara", + "lat": 34.6809, + "lng": 135.849, + "description": "3000 каменных и бронзовых фонарей! Дважды в год зажигают все — магическое зрелище.", + "address": "160 Kasuganocho, Nara", + "hours": "6:30-17:30", + "price": "¥500", + "rating": 4, + "day": 10, + "links": [] + }, + { + "id": 32, + "name": "Замок в Осаке", + "nameJp": "大阪城", + "category": "sight", + "city": "osaka", + "lat": 34.6873, + "lng": 135.5262, + "description": "Символ Осаки (1583). Музей внутри, смотровая на 8 этаже с панорамой города!", + "address": "1-1 Osakajo, Chuo Ward, Osaka", + "hours": "9:00-17:00", + "price": "¥600", + "rating": 5, + "day": 12, + "links": [] + }, + { + "id": 33, + "name": "Куромон Маркет", + "nameJp": "黒門市場", + "category": "restaurant", + "city": "osaka", + "lat": 34.6672, + "lng": 135.5065, + "description": "\"Кухня Осаки\" — вагю, тунец, фугу, устрицы! Лучший стрит-фуд в городе.", + "address": "Kuromon Market, Chuo Ward, Osaka", + "hours": "9:00-18:00", + "price": "¥1000-5000", + "rating": 5, + "day": 12, + "links": [] + }, + { + "id": 34, + "name": "Дотонбори", + "nameJp": "道頓堀", + "category": "sight", + "city": "osaka", + "lat": 34.6687, + "lng": 135.5013, + "description": "Неоновое сердце Осаки! Вывеска Glico Man, такояки, гёдза, ночная жизнь.", + "address": "Dotonbori, Chuo Ward, Osaka", + "hours": "24/7", + "price": "Бесплатно", + "rating": 5, + "day": 12, + "links": [] + }, + { + "id": 35, + "name": "Universal Studios Japan", + "nameJp": "ユニバーサル・スタジオ・ジャパン", + "category": "sight", + "city": "osaka", + "lat": 34.6654, + "lng": 135.4323, + "description": "Super Nintendo World + Harry Potter! Полный день приключений.", + "address": "2-1-33 Sakurajima, Konohana Ward, Osaka", + "hours": "9:00-21:00", + "price": "¥8600", + "rating": 5, + "day": 13, + "links": [ + "https://www.usj.co.jp/" + ], + "tips": "Билеты уже куплены! 🎟️" + }, + { + "id": 36, + "name": "Замок Химэдзи", + "nameJp": "姫路城", + "category": "sight", + "city": "himeji", + "lat": 34.8394, + "lng": 134.6939, + "description": "Самый красивый замок Японии — \"Белая цапля\". 400 лет без разрушений! UNESCO.", + "address": "68 Honmachi, Himeji, Hyogo", + "hours": "9:00-17:00", + "price": "¥1000", + "rating": 5, + "day": 14, + "links": [], + "tips": "45 мин от Осаки" + }, + { + "id": 37, + "name": "Синсэкай", + "nameJp": "新世界", + "category": "sight", + "city": "osaka", + "lat": 34.6524, + "lng": 135.5063, + "description": "Ретро-район с башней Цутэнкаку. Родина кусикацу! Атмосфера Осаки 60-х.", + "address": "Shinsekai, Naniwa Ward, Osaka", + "hours": "24/7", + "price": "Бесплатно", + "rating": 4, + "day": 14, + "links": [] + }, + { + "id": 38, + "name": "Мемориал мира", + "nameJp": "広島平和記念公園", + "category": "sight", + "city": "hiroshima", + "lat": 34.3955, + "lng": 132.4536, + "description": "Атомный купол, Мемориальный музей. Тяжело, но важно. Заложите 2-3 часа.", + "address": "1-2 Nakajimacho, Naka Ward, Hiroshima", + "hours": "8:30-18:00", + "price": "¥200", + "rating": 5, + "day": 15, + "links": [ + "https://hpmmuseum.jp/" + ] + }, + { + "id": 39, + "name": "Святилище Ицукусима", + "nameJp": "厳島神社", + "category": "sight", + "city": "miyajima", + "lat": 34.296, + "lng": 132.3198, + "description": "Иконические красные тории в воде! UNESCO, один из самых красивых видов Японии.", + "address": "1-1 Miyajimacho, Hatsukaichi, Hiroshima", + "hours": "6:30-18:00", + "price": "¥300", + "rating": 5, + "day": 16, + "links": [] + }, + { + "id": 40, + "name": "Гора Мисэн", + "nameJp": "弥山", + "category": "sight", + "city": "miyajima", + "lat": 34.2803, + "lng": 132.3158, + "description": "Священная гора острова Миядзима (535м). Канатка или 2 часа пешком. Потрясающие виды!", + "address": "Mount Misen, Miyajima, Hiroshima", + "hours": "9:00-17:00 (канатка)", + "price": "¥1800", + "rating": 5, + "day": 16, + "links": [] + }, + { + "id": 41, + "name": "Daikokuya Tempura", + "nameJp": "大黒家天麩羅", + "category": "restaurant", + "city": "tokyo", + "lat": 35.7134, + "lng": 139.7945, + "description": "Легендарная темпура с 1887 года! Хрустящие креветки и овощи.", + "address": "1-38-10 Asakusa, Taito City, Tokyo", + "hours": "11:00-20:30", + "price": "¥2000", + "rating": 5, + "day": 1, + "links": [] + }, + { + "id": 42, + "name": "Ichiran Ramen", + "nameJp": "一蘭", + "category": "restaurant", + "city": "tokyo", + "lat": 35.6598, + "lng": 139.6998, + "description": "Персональные кабинки для рамена! Тонкоцу бульон, выбираешь крепость, масло, чеснок.", + "address": "1-22-7 Jinnan, Shibuya City, Tokyo", + "hours": "24/7", + "price": "¥1200", + "rating": 5, + "day": 2, + "links": [] + }, + { + "id": 43, + "name": "Okonomimura", + "nameJp": "お好み村", + "category": "restaurant", + "city": "hiroshima", + "lat": 34.3942, + "lng": 132.4572, + "description": "Трёхэтажный комплекс с 25 магазинами окономияки! Хиросимский стиль с лапшой.", + "address": "5-13 Shintenchi, Naka Ward, Hiroshima", + "hours": "11:00-21:00", + "price": "¥1000-1500", + "rating": 5, + "day": 15, + "links": [] + }, + { + "id": 44, + "name": "Daruma Kushikatsu", + "nameJp": "串カツだるま", + "category": "restaurant", + "city": "osaka", + "lat": 34.6687, + "lng": 135.5018, + "description": "Знаменитые кусикацу с 1929 года! Панированные шашлычки, НЕ макать дважды в соус!", + "address": "2-3-9 Dotonbori, Chuo Ward, Osaka", + "hours": "11:00-22:30", + "price": "¥2000", + "rating": 5, + "day": 12, + "links": [] + }, + { + "id": 45, + "name": "Takoyaki Wanaka", + "nameJp": "たこ焼き わなか", + "category": "restaurant", + "city": "osaka", + "lat": 34.6685, + "lng": 135.5008, + "description": "Лучшие такояки в Осаке! Шарики с осьминогом, хрустящие снаружи, кремовые внутри.", + "address": "Dotonbori, Chuo Ward, Osaka", + "hours": "10:00-23:00", + "price": "¥600", + "rating": 5, + "day": 12, + "links": [] + }, + { + "id": 46, + "name": "Fuunji", + "nameJp": "風雲児", + "category": "restaurant", + "city": "tokyo", + "lat": 35.6896, + "lng": 139.6983, + "description": "Культовый цукемен в Синдзюку! Густой рыбный бульон, толстая лапша. Очередь того стоит.", + "address": "2-14-3 Yoyogi, Shibuya-ku, Tokyo", + "hours": "11:00-15:00, 17:00-21:00", + "price": "¥900-1200", + "rating": 5, + "cuisine": "tsukemen", + "mustTry": "Цукемен с дополнительной порцией чашу" + }, + { + "id": 47, + "name": "Afuri", + "nameJp": "阿夫利", + "category": "restaurant", + "city": "kyoto", + "lat": 35.6468, + "lng": 139.7104, + "description": "Юзу сио рамен — лёгкий цитрусовый бульон на морской соли. Хит Эбису!", + "address": "1-1-7 Ebisu, Shibuya-ku, Tokyo", + "hours": "11:00-22:00", + "price": "¥1000-1500", + "rating": 5, + "cuisine": "ramen", + "mustTry": "Yuzu Shio Ramen" + }, + { + "id": 48, + "name": "Ippudo", + "nameJp": "一風堂", + "category": "restaurant", + "city": "osaka", + "lat": 35.6621, + "lng": 139.6987, + "description": "Легендарный тонкоцу рамен из Фукуоки. Сливочный свиной бульон, идеальная лапша.", + "address": "1-3-13 Hiroo, Shibuya-ku, Tokyo", + "hours": "11:00-04:00", + "price": "¥900-1400", + "rating": 5, + "cuisine": "ramen", + "mustTry": "Shiromaru Motoaji — оригинальный белый" + }, + { + "id": 49, + "name": "Sushi Dai", + "nameJp": "寿司大", + "category": "restaurant", + "city": "tokyo", + "lat": 35.6426, + "lng": 139.7809, + "description": "Лучшие суши на рынке Тоёсу! Омакасе от мастера. Очередь 2-4 часа, но это легенда.", + "address": "6-5-1 Toyosu, Koto-ku, Tokyo (Toyosu Market)", + "hours": "5:30-14:00", + "price": "¥4000-5000", + "rating": 5, + "cuisine": "sushi", + "mustTry": "Omakase set — шеф выбирает лучшее" + }, + { + "id": 50, + "name": "Midori Sushi", + "nameJp": "梅丘寿司の美登利", + "category": "restaurant", + "city": "tokyo", + "lat": 35.6594, + "lng": 139.6984, + "description": "Суши высшего качества по разумной цене. Огромные куски рыбы! В Shibuya Mark City.", + "address": "1-12-3 Dogenzaka, Shibuya-ku, Tokyo", + "hours": "11:00-22:00", + "price": "¥2500-4000", + "rating": 5, + "cuisine": "sushi", + "mustTry": "Toro (жирный тунец) и Uni (морской ёж)" + }, + { + "id": 51, + "name": "Maisen", + "nameJp": "まい泉", + "category": "restaurant", + "city": "tokyo", + "lat": 35.6665, + "lng": 139.7071, + "description": "Храм тонкацу в бывшей бане! Свинина Berkshire, хрустящая панировка, капуста без лимита.", + "address": "4-8-5 Jingumae, Shibuya-ku, Tokyo", + "hours": "11:00-22:00", + "price": "¥1500-3000", + "rating": 5, + "cuisine": "tonkatsu", + "mustTry": "Kurobuta hire katsu (филе чёрной свиньи)" + }, + { + "id": 52, + "name": "Tsunahachi", + "nameJp": "つな八", + "category": "restaurant", + "city": "tokyo", + "lat": 35.6921, + "lng": 139.7041, + "description": "Премиум темпура с 1924 года. Готовят прямо перед вами. Хрустящие креветки и овощи.", + "address": "3-31-8 Shinjuku, Shinjuku-ku, Tokyo", + "hours": "11:00-22:30", + "price": "¥2000-4000", + "rating": 5, + "cuisine": "tempura", + "mustTry": "Tendon (темпура на рисе) или курс из 8 блюд" + }, + { + "id": 53, + "name": "Gonpachi Nishi-Azabu", + "nameJp": "権八 西麻布", + "category": "restaurant", + "city": "tokyo", + "lat": 35.6601, + "lng": 139.7257, + "description": "\"Kill Bill ресторан\"! Тарантино вдохновлялся этим местом. Соба, якитори, атмосфера.", + "address": "1-13-11 Nishi-Azabu, Minato-ku, Tokyo", + "hours": "11:30-03:30", + "price": "¥3000-5000", + "rating": 4, + "cuisine": "izakaya", + "mustTry": "Soba и якитори под саке" + }, + { + "id": 54, + "name": "Marugame Seimen", + "nameJp": "丸亀製麺", + "category": "restaurant", + "city": "tokyo", + "lat": 35.66, + "lng": 139.7002, + "description": "Быстрый и вкусный удон! Лапшу делают на месте. Выбираешь топпинги на конвейере.", + "address": "1-21-3 Jinnan, Shibuya-ku, Tokyo", + "hours": "10:00-22:00", + "price": "¥400-800", + "rating": 4, + "cuisine": "udon", + "mustTry": "Kamaage udon с темпурой" + }, + { + "id": 55, + "name": "Okutan", + "nameJp": "奥丹", + "category": "restaurant", + "city": "kyoto", + "lat": 35.0271, + "lng": 135.786, + "description": "Тофу-рёри с 1635 года! Самый старый ресторан тофу в Киото. Вегетарианский рай.", + "address": "86-30 Nanzenji Fukuchicho, Sakyo-ku, Kyoto", + "hours": "11:00-16:30", + "price": "¥3300", + "rating": 5, + "cuisine": "tofu", + "mustTry": "Yudofu set — тофу в горячем бульоне" + }, + { + "id": 56, + "name": "Tsujiri", + "nameJp": "辻利", + "category": "restaurant", + "city": "kyoto", + "lat": 35.0053, + "lng": 135.7782, + "description": "Матча-кафе с 1860 года в Гион! Чайные традиции Удзи. Зелёный рай.", + "address": "573-3 Gionmachi Minamigawa, Higashiyama-ku, Kyoto", + "hours": "10:00-22:00", + "price": "¥800-1500", + "rating": 5, + "cuisine": "matcha", + "mustTry": "Матча парфе и матча латте" + }, + { + "id": 57, + "name": "Nakamura Tokichi Honten", + "nameJp": "中村藤吉本店", + "category": "restaurant", + "city": "kyoto", + "lat": 34.8893, + "lng": 135.8054, + "description": "Главный храм матча в Удзи с 1854! Лучший зелёный чай Японии. Десерты — произведения искусства.", + "address": "10 Uji Ichiban, Uji, Kyoto", + "hours": "10:00-17:30", + "price": "¥1000-2000", + "rating": 5, + "cuisine": "matcha", + "mustTry": "Nama-cha jelly и матча соба" + }, + { + "id": 58, + "name": "Nishiki Warai", + "nameJp": "錦わらい", + "category": "restaurant", + "city": "kyoto", + "lat": 35.0047, + "lng": 135.7642, + "description": "Киотский окономияки на рынке Нисики! Пушистые блинчики с морепродуктами.", + "address": "Nishiki Market, Nakagyo-ku, Kyoto", + "hours": "11:00-22:00", + "price": "¥1000-1800", + "rating": 4, + "cuisine": "okonomiyaki", + "mustTry": "Seafood okonomiyaki" + }, + { + "id": 59, + "name": "Mizuno", + "nameJp": "美津の", + "category": "restaurant", + "city": "osaka", + "lat": 34.6688, + "lng": 135.502, + "description": "Лучший окономияки в Дотонбори с 1945! Готовят прямо на теппане перед вами.", + "address": "1-4-15 Dotonbori, Chuo-ku, Osaka", + "hours": "11:00-22:00", + "price": "¥1200-2000", + "rating": 5, + "cuisine": "okonomiyaki", + "mustTry": "Yamaimo-yaki (с ямсом, пушистый!)" + }, + { + "id": 60, + "name": "Kiji", + "nameJp": "きじ", + "category": "restaurant", + "city": "osaka", + "lat": 34.7024, + "lng": 135.4959, + "description": "Легендарный окономияки в подвале Umeda! Михелин рекомендует. Очередь — знак качества.", + "address": "Shin-Umeda Shokudogai, 9-20 Kakudacho, Kita-ku, Osaka", + "hours": "11:30-21:30", + "price": "¥900-1500", + "rating": 5, + "cuisine": "okonomiyaki", + "mustTry": "Modern-yaki с лапшой и свининой" + }, + { + "id": 61, + "name": "Kinryu Ramen", + "nameJp": "金龍ラーメン", + "category": "restaurant", + "city": "osaka", + "lat": 34.6686, + "lng": 135.5011, + "description": "Дракон на вывеске — символ Дотонбори! Тонкоцу рамен 24/7, бесплатный рис и кимчи.", + "address": "1-7-26 Dotonbori, Chuo-ku, Osaka", + "hours": "24/7", + "price": "¥600-900", + "rating": 4, + "cuisine": "ramen", + "mustTry": "Tonkotsu ramen с дополнительным чашу" + }, + { + "id": 62, + "name": "Nagataya", + "nameJp": "長田屋", + "category": "restaurant", + "city": "hiroshima", + "lat": 34.3939, + "lng": 132.4542, + "description": "Аутентичный хиросимский окономияки рядом с Мемориалом. Слои капусты, лапши, яйца!", + "address": "1-7-19 Otemachi, Naka-ku, Hiroshima", + "hours": "10:30-20:30", + "price": "¥900-1400", + "rating": 5, + "cuisine": "okonomiyaki", + "mustTry": "Special с креветками и кальмаром" + }, + { + "id": 63, + "name": "Lopez", + "nameJp": "ロペズ", + "category": "restaurant", + "city": "hiroshima", + "lat": 34.3943, + "lng": 132.4574, + "description": "Мексиканец влюбился в окономияки и открыл ресторан! Уникальный фьюжн с сыром.", + "address": "6-17 Tatemachi, Naka-ku, Hiroshima", + "hours": "11:00-14:30, 17:30-21:00", + "price": "¥900-1300", + "rating": 4, + "cuisine": "okonomiyaki", + "mustTry": "Special с сыром и острым соусом" + }, + { + "id": 64, + "name": "Kakinoha-zushi Tanaka", + "nameJp": "柿の葉すし 本舗たなか", + "category": "restaurant", + "city": "nara", + "lat": 34.6839, + "lng": 135.8319, + "description": "Фирменные суши Нары, завёрнутые в листья хурмы! Традиция 1400 лет.", + "address": "5 Noborioji-cho, Nara", + "hours": "9:00-19:00", + "price": "¥800-1500", + "rating": 5, + "cuisine": "sushi", + "mustTry": "Kakinoha-zushi set (лосось и макрель)" + }, + { + "id": 65, + "name": "Gyukatsu Motomura", + "nameJp": "牛かつもと村", + "category": "restaurant", + "city": "tokyo", + "lat": 35.6602, + "lng": 139.6988, + "description": "Гюкацу — панированная говядина, которую дожариваешь на камне! Уникальный формат.", + "address": "1-8-2 Dogenzaka, Shibuya-ku, Tokyo", + "hours": "11:00-23:00", + "price": "¥1500-2500", + "rating": 5, + "cuisine": "gyukatsu", + "mustTry": "Gyukatsu set с рисом, капустой, мисо" + }, + { + "id": 66, + "name": "Nakiryu", + "nameJp": "鳴龍", + "category": "restaurant", + "city": "tokyo", + "lat": 35.7295, + "lng": 139.7334, + "description": "Рамен со звездой Мишлен! Острый тантанмен с кунжутной пастой. Маленькое место, большой вкус.", + "address": "2-34-4 Minamiōtsuka, Toshima-ku, Tokyo", + "hours": "11:30-15:00, 18:00-21:00", + "price": "¥900-1200", + "rating": 5, + "cuisine": "ramen", + "mustTry": "Tantanmen (острый кунжутный)" + }, + { + "id": 67, + "name": "Katsukura", + "nameJp": "かつくら", + "category": "restaurant", + "city": "kyoto", + "lat": 35.0032, + "lng": 135.7685, + "description": "Лучший тонкацу Киото! Сам мелешь кунжут, 3 вида соуса. Свинина с ферм Кагосимы.", + "address": "Sanjo Takakura, Nakagyo-ku, Kyoto", + "hours": "11:00-22:00", + "price": "¥1500-2500", + "rating": 5, + "cuisine": "tonkatsu", + "mustTry": "Rosu katsu (корейка) с рисом и мисо" + }, + { + "id": 68, + "name": "Ramen Takayama", + "nameJp": "ラーメン高山", + "category": "restaurant", + "city": "osaka", + "lat": 34.7035, + "lng": 135.4992, + "description": "Легендарный рамен в Умэда. Густой бульон из свинины и морепродуктов.", + "address": "Sonezaki Shinchi, Kita-ku, Osaka", + "hours": "11:00-15:00, 17:00-23:00", + "price": "¥800-1200", + "rating": 4, + "cuisine": "ramen", + "mustTry": "Signature tonkotsu с чашу" + }, + { + "id": 69, + "name": "Pablo Cheese Tart", + "nameJp": "焼きたてチーズタルト専門店PABLO", + "category": "restaurant", + "city": "osaka", + "lat": 34.6684, + "lng": 135.5029, + "description": "Чизкейк-тарты родом из Осаки! Выбор прожарки: rare (жидкий) или medium.", + "address": "1-8-25 Shinsaibashi, Chuo-ku, Osaka", + "hours": "11:00-23:00", + "price": "¥900-1500", + "rating": 4, + "cuisine": "dessert", + "mustTry": "Rare cheese tart (кремовый внутри)" + }, + { + "id": 70, + "name": "Momiji Manju Honpo", + "nameJp": "もみじ饅頭", + "category": "restaurant", + "city": "hiroshima", + "lat": 34.2961, + "lng": 132.3188, + "description": "Момидзи-мандзю — пирожки в форме кленового листа! Символ Миядзимы с 1906.", + "address": "Miyajima, Hatsukaichi, Hiroshima", + "hours": "8:00-18:00", + "price": "¥150-500", + "rating": 4, + "cuisine": "wagashi", + "mustTry": "Свежие с анко или кремом" + }, + { + "id": 71, + "name": "Гиндза", + "nameJp": "銀座", + "category": "sight", + "city": "tokyo", + "lat": 35.6717, + "lng": 139.7649, + "description": "Главная shopping улица Токио. Роскошные бутики, галереи, архитектура. Вечером особенно красиво!", + "address": "Ginza, Chuo City, Tokyo", + "hours": "магазины до 20:00-21:00", + "price": "Бесплатно (прогулка)", + "rating": 5, + "day": 3, + "links": [], + "tips": "Лучше всего гулять вечером 18:00-21:00 (1.5-2 часа)" + }, + { + "id": 72, + "name": "Понтотё", + "nameJp": "ポンと町", + "category": "restaurant", + "city": "kyoto", + "lat": 35.0055, + "lng": 135.7706, + "description": "Атмосферный узкий переулок с традиционными ресторанами вдоль реки Камо. Гейши, фонари, старая Япония.", + "address": "Pontocho, Nakagyo Ward, Kyoto", + "hours": "17:00-23:00", + "price": "¥3000-8000 (ужин)", + "rating": 5, + "day": 7, + "links": [], + "tips": "Лучше вечером 18:00-22:00, заложить 1.5-2 часа с ужином" + }, + { + "id": 73, + "name": "Koffee Mameya", + "nameJp": "コーヒー マメヤ", + "category": "coffee", + "city": "tokyo", + "lat": 35.6657, + "lng": 139.7114, + "description": "Культовая specialty кофейня в Омотэсандо. Бариста подберут зёрна под ваш вкус. Без сидячих мест — кофе с собой.", + "address": "4-15-3 Jingumae, Shibuya-ku, Tokyo", + "hours": "10:00-18:00", + "price": "¥500-1500", + "rating": 5, + "day": 2, + "bestTime": "утро", + "duration": "15-30 мин" + }, + { + "id": 74, + "name": "Fuglen Tokyo", + "nameJp": "フグレン トウキョウ", + "category": "coffee", + "city": "tokyo", + "lat": 35.6658, + "lng": 139.6945, + "description": "Норвежская обжарка в Сибуя! Днём — кофе, вечером — коктейль-бар. Скандинавский дизайн.", + "address": "1-16-11 Tomigaya, Shibuya-ku, Tokyo", + "hours": "8:00-22:00", + "price": "¥500-900", + "rating": 5, + "day": 2, + "bestTime": "утро/вечер", + "duration": "30-60 мин" + }, + { + "id": 75, + "name": "Onibus Coffee", + "nameJp": "オニバスコーヒー", + "category": "coffee", + "city": "tokyo", + "lat": 35.6437, + "lng": 139.6987, + "description": "Минималистичная кофейня в Накамэгуро у канала. Своя обжарка, лофтовый стиль.", + "address": "2-14-1 Kamimeguro, Meguro-ku, Tokyo", + "hours": "9:00-18:00", + "price": "¥400-700", + "rating": 5, + "day": 2, + "bestTime": "после обеда", + "duration": "30 мин" + }, + { + "id": 76, + "name": "Blue Bottle Coffee Shinjuku", + "nameJp": "ブルーボトルコーヒー 新宿", + "category": "coffee", + "city": "tokyo", + "lat": 35.6901, + "lng": 139.7002, + "description": "Калифорнийский specialty coffee в Синдзюку. Идеальный флэт уайт и пуровер.", + "address": "NEWoMan Shinjuku, 4-1-6 Shinjuku, Tokyo", + "hours": "8:00-21:00", + "price": "¥500-800", + "rating": 4, + "day": 2, + "bestTime": "утро", + "duration": "20 мин" + }, + { + "id": 77, + "name": "Streamer Coffee", + "nameJp": "ストリーマーコーヒー", + "category": "coffee", + "city": "tokyo", + "lat": 35.6613, + "lng": 139.6948, + "description": "Хипстерская кофейня в Сибуя. Латте-арт от бариста-чемпионов, андеграундный вайб.", + "address": "1-20-28 Shibuya, Shibuya-ku, Tokyo", + "hours": "8:00-19:00", + "price": "¥500-800", + "rating": 4, + "day": 2, + "bestTime": "утро", + "duration": "20 мин" + }, + { + "id": 78, + "name": "% Arabica Arashiyama", + "nameJp": "アラビカ 嵐山", + "category": "coffee", + "city": "kyoto", + "lat": 35.0129, + "lng": 135.6718, + "description": "Культовая кофейня с видом на реку Кацура! Минимализм, очередь и лучший латте Киото.", + "address": "3-47 Sagatenryuji Susukinobabacho, Ukyo-ku, Kyoto", + "hours": "8:00-18:00", + "price": "¥500-800", + "rating": 5, + "day": 8, + "bestTime": "утро", + "duration": "20-30 мин" + }, + { + "id": 79, + "name": "Kurasu Kyoto", + "nameJp": "クラス 京都", + "category": "coffee", + "city": "kyoto", + "lat": 34.9873, + "lng": 135.7592, + "description": "Specialty кофе и японские обжарщики. Stand-up бар, пуроверы из регионов Японии.", + "address": "552 Higashiaburanokoji, Shimogyo-ku, Kyoto", + "hours": "8:00-18:00", + "price": "¥500-900", + "rating": 5, + "day": 7, + "bestTime": "утро", + "duration": "20 мин" + }, + { + "id": 80, + "name": "Weekenders Coffee", + "nameJp": "ウィークエンダーズコーヒー", + "category": "coffee", + "city": "kyoto", + "lat": 35.0062, + "lng": 135.7686, + "description": "Локальная обжарка в центре Киото. Сезонные зёрна, уютная атмосфера.", + "address": "560 Higashiyama, Nakagyo-ku, Kyoto", + "hours": "7:30-18:00", + "price": "¥400-700", + "rating": 4, + "day": 9, + "bestTime": "утро", + "duration": "20 мин" + }, + { + "id": 81, + "name": "LiLo Coffee Roasters", + "nameJp": "リロ コーヒー ロースターズ", + "category": "coffee", + "city": "osaka", + "lat": 34.6725, + "lng": 135.5025, + "description": "Прячется в переулке Синсайбаси. Своя обжарка, индустриальный стиль, молодые бариста.", + "address": "1-10-22 Nishi-Shinsaibashi, Chuo-ku, Osaka", + "hours": "11:00-19:00", + "price": "¥500-900", + "rating": 5, + "day": 12, + "bestTime": "после обеда", + "duration": "30 мин" + }, + { + "id": 82, + "name": "Glitch Coffee Osaka", + "nameJp": "グリッチコーヒー 大阪", + "category": "coffee", + "city": "osaka", + "lat": 34.6938, + "lng": 135.4988, + "description": "Токийская сеть specialty. Сложные пуроверы, light roast, кофе как вино.", + "address": "1-1-3 Sonezaki, Kita-ku, Osaka", + "hours": "8:00-20:00", + "price": "¥500-1200", + "rating": 5, + "day": 12, + "bestTime": "утро", + "duration": "20 мин" + }, + { + "id": 83, + "name": "Asakusa Kagetsudo", + "nameJp": "浅草花月堂", + "category": "snack", + "city": "tokyo", + "lat": 35.7159, + "lng": 139.7955, + "description": "Гигантский мелонпан размером с голову! Хрустящая сладкая корочка, мягкий внутри.", + "address": "2-7-13 Asakusa, Taito-ku, Tokyo", + "hours": "9:00-17:00", + "price": "¥220", + "rating": 5, + "day": 1, + "bestTime": "утро", + "duration": "10 мин" + }, + { + "id": 84, + "name": "Kibi Dango Azuma", + "nameJp": "きびだんご あづま", + "category": "snack", + "city": "tokyo", + "lat": 35.7147, + "lng": 139.7968, + "description": "Моти на палочке из Асакусы! 5 шариков за ¥330, присыпаны соевой мукой. Традиция 100+ лет.", + "address": "1-18-1 Asakusa, Taito-ku, Tokyo", + "hours": "9:00-19:00", + "price": "¥330", + "rating": 5, + "day": 1, + "bestTime": "утро/после обеда", + "duration": "10 мин" + }, + { + "id": 85, + "name": "Asakusa Menchi", + "nameJp": "浅草メンチ", + "category": "snack", + "city": "tokyo", + "lat": 35.7143, + "lng": 139.7952, + "description": "Хрустящие коро́кке с говядиной! Горячие, с пылу с жару. Лучший стритфуд в Асакусе.", + "address": "2-3-3 Asakusa, Taito-ku, Tokyo", + "hours": "10:00-19:00", + "price": "¥300", + "rating": 5, + "day": 1, + "bestTime": "обед", + "duration": "10 мин" + }, + { + "id": 86, + "name": "Marion Crepes", + "nameJp": "マリオンクレープ", + "category": "snack", + "city": "tokyo", + "lat": 35.6701, + "lng": 139.7025, + "description": "Икона Харадзюку с 1976! Японские крепы — мягкие, со взбитыми сливками и клубникой.", + "address": "1-6-15 Jingumae, Shibuya-ku, Tokyo", + "hours": "10:30-20:00", + "price": "¥500-700", + "rating": 5, + "day": 2, + "bestTime": "после обеда", + "duration": "15 мин" + }, + { + "id": 87, + "name": "Tsukiji Yamacho Tamagoyaki", + "nameJp": "築地山長 玉子焼き", + "category": "snack", + "city": "tokyo", + "lat": 35.6651, + "lng": 139.7703, + "description": "Пышная тамагояки на палочке! Сладкий омлет прямо с решётки. Must try на Цукидзи.", + "address": "4-10-10 Tsukiji, Chuo-ku, Tokyo", + "hours": "6:00-15:00", + "price": "¥150-300", + "rating": 5, + "day": 3, + "bestTime": "утро", + "duration": "10 мин" + }, + { + "id": 88, + "name": "Nishiki Market Snacks", + "nameJp": "錦市場 食べ歩き", + "category": "snack", + "city": "kyoto", + "lat": 35.005, + "lng": 135.7647, + "description": "Улица дегустаций! Моти, тамаго, цукемоно, осьминог на палочке. Пробуй на ходу!", + "address": "Nishiki Market, Nakagyo-ku, Kyoto", + "hours": "9:00-18:00", + "price": "¥200-500", + "rating": 5, + "day": 7, + "bestTime": "обед", + "duration": "60 мин" + }, + { + "id": 89, + "name": "Arashiyama Mochi", + "nameJp": "嵐山 もち", + "category": "snack", + "city": "kyoto", + "lat": 35.0158, + "lng": 139.6726, + "description": "Свежие моти у бамбукового леса. Варабимоти с кинако, данго на гриле.", + "address": "Sagatenryuji, Ukyo-ku, Kyoto", + "hours": "9:00-17:00", + "price": "¥300-500", + "rating": 4, + "day": 8, + "bestTime": "после прогулки", + "duration": "15 мин" + }, + { + "id": 90, + "name": "Takoyaki Wanaka Dotonbori", + "nameJp": "たこ焼き わなか 道頓堀", + "category": "snack", + "city": "osaka", + "lat": 34.6685, + "lng": 135.5008, + "description": "Лучшие такояки в Осаке! Хрустящие снаружи, кремовые внутри. Есть набор с разными соусами.", + "address": "7-13 Namba, Chuo-ku, Osaka", + "hours": "10:00-23:00", + "price": "¥500-700", + "rating": 5, + "day": 12, + "bestTime": "вечер", + "duration": "15 мин" + }, + { + "id": 91, + "name": "551 Horai", + "nameJp": "551蓬莱", + "category": "snack", + "city": "osaka", + "lat": 34.6656, + "lng": 135.5015, + "description": "Легендарные никуман (мясные булочки)! Символ Осаки с 1945. Горячие, сочные, огромные.", + "address": "3-2-11 Namba, Chuo-ku, Osaka", + "hours": "11:00-22:00", + "price": "¥210-500", + "rating": 5, + "day": 12, + "bestTime": "любое", + "duration": "10 мин" + }, + { + "id": 92, + "name": "Momiji Manju Shops", + "nameJp": "もみじ饅頭 店", + "category": "snack", + "city": "hiroshima", + "lat": 34.2961, + "lng": 132.3188, + "description": "Пирожки в форме кленового листа! Символ Миядзимы. Начинки: анко, крем, матча, шоколад.", + "address": "Omotesando, Miyajima, Hiroshima", + "hours": "8:00-18:00", + "price": "¥150-200", + "rating": 5, + "day": 16, + "bestTime": "днём", + "duration": "15 мин" + }, + { + "id": 93, + "name": "Unlimited Coffee Bar", + "nameJp": "アンリミテッドコーヒーバー", + "category": "coffee", + "city": "tokyo", + "lat": 35.7105, + "lng": 139.8095, + "description": "Кофейня рядом с Tokyo Skytree.", + "address": "Near Tokyo Skytree, Sumida, Tokyo", + "hours": "10:00-20:00", + "price": "¥500-800", + "rating": 4 + }, + { + "id": 94, + "name": "Allpress Espresso Tokyo", + "nameJp": "オールプレス エスプレッソ 東京", + "category": "coffee", + "city": "tokyo", + "lat": 35.6808, + "lng": 139.7977, + "description": "Новозеландская обжарка в Киёсуми-Сиракава. Индустриальный стиль.", + "address": "3-7-2 Hirano, Koto-ku, Tokyo", + "hours": "8:00-17:00", + "price": "¥500-800", + "rating": 5 + }, + { + "id": 95, + "name": "Fuglen Asakusa", + "nameJp": "フグレン浅草", + "category": "coffee", + "city": "tokyo", + "lat": 35.7148, + "lng": 139.796, + "description": "Норвежская кофейня в Асакусе.", + "address": "Asakusa, Taito-ku, Tokyo", + "hours": "8:00-20:00", + "price": "¥500-800", + "rating": 4 + }, + { + "id": 96, + "name": "Sarutahiko Coffee", + "nameJp": "猿田彦珈琲", + "category": "coffee", + "city": "tokyo", + "lat": 35.67, + "lng": 139.7028, + "description": "Specialty кофе на станции Харадзюку.", + "address": "Jingumae, Shibuya-ku, Tokyo", + "hours": "8:00-21:00", + "price": "¥400-700", + "rating": 4 + }, + { + "id": 97, + "name": "Bakery & Table Hakone", + "nameJp": "ベーカリー&テーブル箱根", + "category": "restaurant", + "city": "hakone", + "lat": 35.1953, + "lng": 139.0229, + "description": "Пекарня с видом на озеро Аси. Свежий хлеб и кофе.", + "address": "9-1 Motohakone, Hakone, Kanagawa", + "hours": "8:00-17:00", + "price": "¥800-1500", + "rating": 5 + }, + { + "id": 98, + "name": "Naraya Cafe", + "nameJp": "ナラヤカフェ", + "category": "coffee", + "city": "hakone", + "lat": 35.233, + "lng": 139.056, + "description": "Кафе с футбатом (онсэн для ног) в Мияносита.", + "address": "404-13 Miyanoshita, Hakone, Kanagawa", + "hours": "10:30-18:00", + "price": "¥500-900", + "rating": 5 + }, + { + "id": 99, + "name": "Cafe Ryusenkei", + "nameJp": "カフェ 龍泉渓", + "category": "coffee", + "city": "hakone", + "lat": 35.235, + "lng": 139.055, + "description": "Кофе в Airstream 1967 года в Хаконэ.", + "address": "Miyanoshita, Hakone, Kanagawa", + "hours": "10:00-17:00", + "price": "¥500-800", + "rating": 4 + }, + { + "id": 100, + "name": "Owakudani Kurotamago", + "nameJp": "大涌谷 黒たまご", + "category": "snack", + "city": "hakone", + "lat": 35.2429, + "lng": 139.0208, + "description": "Чёрные яйца, варёные в вулканических источниках. +7 лет жизни!", + "address": "Owakudani, Hakone, Kanagawa", + "hours": "9:00-16:00", + "price": "¥500", + "rating": 5 + }, + { + "id": 101, + "name": "Sanoki-ya Tsukiji", + "nameJp": "さのきや 築地", + "category": "snack", + "city": "tokyo", + "lat": 35.6654, + "lng": 139.7707, + "description": "Тайяки в форме тунца на рынке Цукидзи!", + "address": "Tsukiji, Chuo-ku, Tokyo", + "hours": "6:00-14:00", + "price": "¥200-400", + "rating": 4 + }, + { + "id": 102, + "name": "Ginza Kimuraya", + "nameJp": "銀座木村屋", + "category": "snack", + "city": "tokyo", + "lat": 35.6717, + "lng": 139.7649, + "description": "Оригинальный анпан (булочка с анко) с 1869!", + "address": "4-5-7 Ginza, Chuo-ku, Tokyo", + "hours": "10:00-21:00", + "price": "¥200-500", + "rating": 5 + }, + { + "id": 103, + "name": "Naniwaya Souhonten", + "nameJp": "浪花家総本店", + "category": "snack", + "city": "tokyo", + "lat": 35.67, + "lng": 139.715, + "description": "Оригинальный тайяки с 1909 года!", + "address": "1-2-10 Azabu-Juban, Minato-ku, Tokyo", + "hours": "10:00-19:00", + "price": "¥200-400", + "rating": 5 + }, + { + "id": 104, + "name": "Nakatanidou", + "nameJp": "中谷堂", + "category": "snack", + "city": "nara", + "lat": 34.6839, + "lng": 135.8319, + "description": "Вирусное шоу мочи! Самое быстрое моти в мире.", + "address": "29 Hashimotocho, Nara", + "hours": "10:00-19:00", + "price": "¥150-300", + "rating": 5 + }, + { + "id": 105, + "name": "ANY Specialty Coffee", + "nameJp": "ANY スペシャルティコーヒー", + "category": "coffee", + "city": "nara", + "lat": 34.683, + "lng": 135.832, + "description": "Specialty кофейня в Наре.", + "address": "Nara", + "hours": "10:00-18:00", + "price": "¥500-800", + "rating": 4 + }, + { + "id": 106, + "name": "Blue Bottle Coffee Kyoto", + "nameJp": "ブルーボトルコーヒー 京都", + "category": "coffee", + "city": "kyoto", + "lat": 35.012, + "lng": 135.77, + "description": "Blue Bottle в 100-летнем machiya.", + "address": "Nanzenji, Sakyo-ku, Kyoto", + "hours": "8:00-18:00", + "price": "¥500-800", + "rating": 5 + }, + { + "id": 107, + "name": "Nishiki Market (Food Walk)", + "nameJp": "錦市場 食べ歩き", + "category": "restaurant", + "city": "kyoto", + "lat": 35.005, + "lng": 135.7647, + "description": "Обед-прогулка по рынку Нисики.", + "address": "Nishiki Market, Nakagyo-ku, Kyoto", + "hours": "9:00-18:00", + "price": "¥500-2000", + "rating": 5 + }, + { + "id": 108, + "name": "Obscura Coffee Hondori", + "nameJp": "オブスキュラコーヒー 本通り", + "category": "coffee", + "city": "hiroshima", + "lat": 34.394, + "lng": 132.457, + "description": "Specialty кофейня в Хиросиме.", + "address": "Hondori, Naka-ku, Hiroshima", + "hours": "9:00-19:00", + "price": "¥500-800", + "rating": 4 + }, + { + "id": 109, + "name": "Dotonbori Akaoni", + "nameJp": "道頓堀 赤鬼", + "category": "snack", + "city": "osaka", + "lat": 34.6687, + "lng": 135.5013, + "description": "Michelin-рекомендованные такояки в Дотонбори!", + "address": "Dotonbori, Chuo-ku, Osaka", + "hours": "10:00-23:00", + "price": "¥500-700", + "rating": 5 + }, + { + "id": 110, + "name": "Arashiyama Food Street", + "nameJp": "嵐山 食べ歩き", + "category": "snack", + "city": "kyoto", + "lat": 35.0158, + "lng": 135.6726, + "description": "Снэки и данго на улочках Арасиямы.", + "address": "Arashiyama, Ukyo-ku, Kyoto", + "hours": "9:00-17:00", + "price": "¥200-500", + "rating": 4 + }, + { + "id": 111, + "name": "Miyajima Omotesando", + "nameJp": "宮島 表参道", + "category": "snack", + "city": "miyajima", + "lat": 34.2961, + "lng": 132.3188, + "description": "Торговая улица Миядзимы. Момидзи-мандзю, устрицы гриль!", + "address": "Omotesando, Miyajima, Hiroshima", + "hours": "8:00-18:00", + "price": "¥200-1000", + "rating": 5 + }, + { + "id": 112, + "name": "Сакура у реки Накагава", + "nameEn": "Kawazu-zakura at Old Nakagawa River", + "city": "tokyo", + "category": "sight", + "description": "Ранняя сакура кавадзудзакура + жёлтый рапс с видом на Skytree. Цветёт с февраля!", + "lat": 35.6885, + "lng": 139.8365, + "googleMaps": "https://maps.app.goo.gl/rqBYB7bcHb1wvoNq9" + }, + { + "id": 113, + "name": "Парк Йойоги", + "nameEn": "Yoyogi Park", + "nameJa": "代々木公園", + "category": "sight", + "city": "tokyo", + "address": "2-1 Yoyogikamizonocho, Shibuya City, Tokyo", + "coordinates": { + "lat": 35.6715, + "lng": 139.6949 + }, + "description": "Огромный парк рядом с храмом Мэйдзи. Популярное место для пикников и отдыха.", + "price": "Бесплатно", + "hours": "Круглосуточно" + } +] \ No newline at end of file diff --git a/src/data/places.json.bak b/src/data/places.json.bak new file mode 100644 index 0000000..dac0f07 --- /dev/null +++ b/src/data/places.json.bak @@ -0,0 +1,735 @@ +[ + { + "id": 1, + "name": "Сэнсодзи", + "nameJp": "浅草寺", + "category": "sight", + "city": "tokyo", + "lat": 35.7148, + "lng": 139.7967, + "description": "Древнейший храм Токио (645 г.), знаменитые ворота Каминаримон с гигантским фонарём. 20 млн паломников в год!", + "address": "2-3-1 Asakusa, Taito City, Tokyo", + "hours": "6:00-17:00", + "price": "Бесплатно", + "rating": 5, + "day": 1, + "links": ["https://www.senso-ji.jp/"], + "tips": "Приходите к 5:45 на утреннюю церемонию" + }, + { + "id": 2, + "name": "Парк Уэно", + "nameJp": "上野公園", + "category": "sight", + "city": "tokyo", + "lat": 35.7146, + "lng": 139.7732, + "description": "Первый европейский парк Японии (1873), пруд Синобадзу, 1200 деревьев сакуры. Рай для отдыха!", + "address": "Uenokoen, Taito City, Tokyo", + "hours": "5:00-23:00", + "price": "Бесплатно", + "rating": 4, + "day": 1, + "links": [] + }, + { + "id": 3, + "name": "Tokyo Skytree", + "nameJp": "東京スカイツリー", + "category": "sight", + "city": "tokyo", + "lat": 35.7101, + "lng": 139.8107, + "description": "Самая высокая телебашня мира (634м). Смотровые на 350м и 450м с панорамой на Фудзи!", + "address": "1-1-2 Oshiage, Sumida City, Tokyo", + "hours": "8:00-22:00", + "price": "¥2100-3100", + "rating": 5, + "day": 1, + "links": ["https://www.tokyo-skytree.jp/"], + "tips": "Приходите за час до заката" + }, + { + "id": 4, + "name": "Акихабара", + "nameJp": "秋葉原", + "category": "sight", + "city": "tokyo", + "lat": 35.7023, + "lng": 139.7745, + "description": "Район электроники и отаку-культуры. 500+ магазинов техники, аниме, мейд-кафе!", + "address": "Akihabara, Chiyoda City, Tokyo", + "hours": "10:00-20:00", + "price": "Бесплатно", + "rating": 4, + "day": 1, + "links": [] + }, + { + "id": 5, + "name": "Храм Мэйдзи", + "nameJp": "明治神宮", + "category": "sight", + "city": "tokyo", + "lat": 35.6764, + "lng": 139.6993, + "description": "Синтоистский храм в честь императора Мэйдзи. 100,000 деревьев, священный лес в центре мегаполиса.", + "address": "1-1 Yoyogikamizonocho, Shibuya City, Tokyo", + "hours": "Рассвет-закат", + "price": "Бесплатно", + "rating": 5, + "day": 2, + "links": ["https://www.meijijingu.or.jp/"] + }, + { + "id": 6, + "name": "Улица Такэсита", + "nameJp": "竹下通り", + "category": "sight", + "city": "tokyo", + "lat": 35.6718, + "lng": 139.7028, + "description": "Мекка молодёжной моды в Харадзюку. Крепы, косплей, необычные магазины!", + "address": "Takeshita Street, Shibuya, Tokyo", + "hours": "10:00-20:00", + "price": "Бесплатно", + "rating": 4, + "day": 2, + "links": [] + }, + { + "id": 7, + "name": "Сибуя — перекрёсток", + "nameJp": "渋谷スクランブル交差点", + "category": "sight", + "city": "tokyo", + "lat": 35.6595, + "lng": 139.7004, + "description": "Самый загруженный перекрёсток мира! До 3000 человек одновременно переходят улицу.", + "address": "Shibuya Scramble Crossing, Tokyo", + "hours": "24/7", + "price": "Бесплатно", + "rating": 5, + "day": 2, + "links": [] + }, + { + "id": 8, + "name": "Shibuya Sky", + "nameJp": "渋谷スカイ", + "category": "sight", + "city": "tokyo", + "lat": 35.6584, + "lng": 139.7022, + "description": "Смотровая на крыше Shibuya Scramble Square (230м). Потрясающий вид на закат!", + "address": "2-24-12 Shibuya, Shibuya City, Tokyo", + "hours": "10:00-22:30", + "price": "¥2000", + "rating": 5, + "day": 2, + "links": ["https://www.shibuya-scramble-square.com/sky/"], + "tips": "Бронируйте онлайн заранее!" + }, + { + "id": 9, + "name": "Синдзюку Гёэн", + "nameJp": "新宿御苑", + "category": "sight", + "city": "tokyo", + "lat": 35.6852, + "lng": 139.7100, + "description": "Императорский сад с тремя стилями: японский, французский, английский. Лучшее место для сакуры!", + "address": "11 Naitomachi, Shinjuku City, Tokyo", + "hours": "9:00-16:30", + "price": "¥500", + "rating": 5, + "day": 2, + "links": ["https://fng.or.jp/shinjuku/"] + }, + { + "id": 10, + "name": "Цукидзи", + "nameJp": "築地場外市場", + "category": "restaurant", + "city": "tokyo", + "lat": 35.6654, + "lng": 139.7707, + "description": "Легендарный рыбный рынок. Свежайшие суши, тамаго-сандо, морепродукты на завтрак!", + "address": "Tsukiji, Chuo City, Tokyo", + "hours": "5:00-14:00", + "price": "¥1500-3000", + "rating": 5, + "day": 3, + "links": [], + "tips": "Приходите рано утром" + }, + { + "id": 11, + "name": "teamLab Planets", + "nameJp": "チームラボ プラネッツ", + "category": "sight", + "city": "tokyo", + "lat": 35.6427, + "lng": 139.7889, + "description": "Иммерсивное цифровое искусство — ходите босиком по воде и свету! Незабываемый опыт.", + "address": "6-1-16 Toyosu, Koto City, Tokyo", + "hours": "9:00-22:00", + "price": "¥3200", + "rating": 5, + "day": 3, + "links": ["https://planets.teamlab.art/"], + "tips": "Бронируйте за 2 недели!" + }, + { + "id": 12, + "name": "Одайба", + "nameJp": "お台場", + "category": "sight", + "city": "tokyo", + "lat": 35.6267, + "lng": 139.7768, + "description": "Футуристический остров: Радужный мост, статуя Gundam в натуральную величину, ночные огни залива.", + "address": "Odaiba, Minato City, Tokyo", + "hours": "24/7", + "price": "Бесплатно", + "rating": 4, + "day": 3, + "links": [] + }, + { + "id": 13, + "name": "Императорский дворец", + "nameJp": "皇居", + "category": "sight", + "city": "tokyo", + "lat": 35.6852, + "lng": 139.7528, + "description": "Резиденция императора Японии. Восточные сады бесплатны, экскурсии по бронированию.", + "address": "1-1 Chiyoda, Chiyoda City, Tokyo", + "hours": "9:00-16:00 (сады)", + "price": "Бесплатно", + "rating": 4, + "day": 3, + "links": ["https://sankan.kunaicho.go.jp/"] + }, + { + "id": 14, + "name": "Никко Тосё-гу", + "nameJp": "日光東照宮", + "category": "sight", + "city": "tokyo", + "lat": 36.7580, + "lng": 139.5988, + "description": "Роскошный храмовый комплекс UNESCO, мавзолей сёгуна Токугава Иэясу. Знаменитые \"три обезьяны\"!", + "address": "2301 Sannai, Nikko, Tochigi", + "hours": "8:00-17:00", + "price": "¥1300", + "rating": 5, + "day": 4, + "links": ["https://www.toshogu.jp/"], + "tips": "2 часа от Токио на поезде" + }, + { + "id": 15, + "name": "Водопад Кэгон", + "nameJp": "華厳の滝", + "category": "sight", + "city": "tokyo", + "lat": 36.7383, + "lng": 139.5006, + "description": "Один из красивейших водопадов Японии (97м). Смотровая площадка на лифте вниз!", + "address": "Chugushi, Nikko, Tochigi", + "hours": "8:00-17:00", + "price": "¥570", + "rating": 5, + "day": 4, + "links": [] + }, + { + "id": 16, + "name": "Овакудани", + "nameJp": "大涌谷", + "category": "sight", + "city": "hakone", + "lat": 35.2429, + "lng": 139.0208, + "description": "Вулканическая долина с горячими источниками. Чёрные яйца +7 лет жизни! Вид на Фудзи.", + "address": "Owakudani, Hakone, Kanagawa", + "hours": "9:00-17:00", + "price": "¥1500 (канатка)", + "rating": 5, + "day": 5, + "links": [], + "tips": "Приезжайте в ясную погоду для вида на Фудзи" + }, + { + "id": 17, + "name": "Озеро Аси", + "nameJp": "芦ノ湖", + "category": "sight", + "city": "hakone", + "lat": 35.1953, + "lng": 139.0229, + "description": "Кратерное озеро с видом на Фудзи. Прогулка на \"пиратском корабле\" — must do!", + "address": "Lake Ashi, Hakone, Kanagawa", + "hours": "9:30-17:00", + "price": "¥1000 (корабль)", + "rating": 5, + "day": 6, + "links": [] + }, + { + "id": 18, + "name": "Храм Хаконэ-дзиндзя", + "nameJp": "箱根神社", + "category": "sight", + "city": "hakone", + "lat": 35.2042, + "lng": 139.0228, + "description": "Древний храм у озера Аси (757 г.). Знаменитые красные тории в воде!", + "address": "80-1 Motohakone, Hakone, Kanagawa", + "hours": "24/7", + "price": "Бесплатно", + "rating": 5, + "day": 6, + "links": [] + }, + { + "id": 19, + "name": "Фусими Инари", + "nameJp": "伏見稲荷大社", + "category": "sight", + "city": "kyoto", + "lat": 34.9671, + "lng": 135.7727, + "description": "10,000+ красных тории! Путь на вершину 2-3 часа. Самый фотогеничный храм Японии.", + "address": "68 Fukakusa Yabunouchicho, Fushimi Ward, Kyoto", + "hours": "24/7", + "price": "Бесплатно", + "rating": 5, + "day": 7, + "links": ["http://inari.jp/"], + "tips": "Приходите к 7:00 — почти никого нет!" + }, + { + "id": 20, + "name": "Кийомидзу-дэра", + "nameJp": "清水寺", + "category": "sight", + "city": "kyoto", + "lat": 34.9949, + "lng": 135.7850, + "description": "Деревянный храм на сваях (778 г.), БЕЗ ЕДИНОГО ГВОЗДЯ! UNESCO, вид на весь Киото.", + "address": "1-294 Kiyomizu, Higashiyama Ward, Kyoto", + "hours": "6:00-18:00", + "price": "¥400", + "rating": 5, + "day": 7, + "links": ["https://www.kiyomizudera.or.jp/"] + }, + { + "id": 21, + "name": "Гион", + "nameJp": "祇園", + "category": "sight", + "city": "kyoto", + "lat": 35.0037, + "lng": 135.7756, + "description": "Легендарный квартал гейш. Вечером шанс увидеть настоящих майко! Атмосфера старой Японии.", + "address": "Gion, Higashiyama Ward, Kyoto", + "hours": "Гейши 17:00-19:00", + "price": "Бесплатно", + "rating": 5, + "day": 7, + "links": [], + "tips": "НЕ бегите за гейшами с камерой!" + }, + { + "id": 22, + "name": "Бамбуковый лес Арасияма", + "nameJp": "嵐山竹林", + "category": "sight", + "city": "kyoto", + "lat": 35.0173, + "lng": 135.6717, + "description": "Магический бамбуковый лес — звук ветра в стеблях признан сокровищем Японии!", + "address": "Sagaogurayama, Ukyo Ward, Kyoto", + "hours": "24/7", + "price": "Бесплатно", + "rating": 5, + "day": 8, + "links": [], + "tips": "Приходите к 7 утра" + }, + { + "id": 23, + "name": "Кинкаку-дзи (Золотой павильон)", + "nameJp": "金閣寺", + "category": "sight", + "city": "kyoto", + "lat": 35.0394, + "lng": 135.7292, + "description": "Самый фотографируемый храм Японии! 3 этажа покрыты настоящим золотом (5 кг).", + "address": "1 Kinkakujicho, Kita Ward, Kyoto", + "hours": "9:00-17:00", + "price": "¥400", + "rating": 5, + "day": 8, + "links": ["https://www.shokoku-ji.jp/kinkakuji/"] + }, + { + "id": 24, + "name": "Рёан-дзи", + "nameJp": "龍安寺", + "category": "sight", + "city": "kyoto", + "lat": 35.0345, + "lng": 135.7182, + "description": "Самый знаменитый сад камней в мире. 15 камней, которые невозможно увидеть все сразу!", + "address": "13 Ryoanji Goryonoshitacho, Ukyo Ward, Kyoto", + "hours": "8:00-17:00", + "price": "¥500", + "rating": 4, + "day": 8, + "links": [] + }, + { + "id": 25, + "name": "Гинкаку-дзи (Серебряный павильон)", + "nameJp": "銀閣寺", + "category": "sight", + "city": "kyoto", + "lat": 35.0271, + "lng": 135.7982, + "description": "Изысканный дзен-храм с садом. Начало Философского пути. Серебра нет, но красота есть!", + "address": "2 Ginkakujicho, Sakyo Ward, Kyoto", + "hours": "8:30-17:00", + "price": "¥500", + "rating": 4, + "day": 9, + "links": [] + }, + { + "id": 26, + "name": "Философский путь", + "nameJp": "哲学の道", + "category": "sight", + "city": "kyoto", + "lat": 35.0200, + "lng": 135.7950, + "description": "2 км вдоль канала под вишнями. Идеален для медитативной прогулки!", + "address": "Tetsugaku no Michi, Sakyo Ward, Kyoto", + "hours": "24/7", + "price": "Бесплатно", + "rating": 5, + "day": 9, + "links": [] + }, + { + "id": 27, + "name": "Нисики Маркет", + "nameJp": "錦市場", + "category": "restaurant", + "city": "kyoto", + "lat": 35.0050, + "lng": 135.7647, + "description": "\"Кухня Киото\" — 400-летний рынок. Маття мороженое, тамаго, моти, цукемоно!", + "address": "Nishiki Market, Nakagyo Ward, Kyoto", + "hours": "9:00-18:00", + "price": "¥500-2000", + "rating": 5, + "day": 9, + "links": [] + }, + { + "id": 28, + "name": "Замок Нидзо", + "nameJp": "二条城", + "category": "sight", + "city": "kyoto", + "lat": 35.0142, + "lng": 135.7481, + "description": "Резиденция сёгунов Токугава. \"Поющие полы\" предупреждали о ниндзя! UNESCO.", + "address": "541 Nijojocho, Nakagyo Ward, Kyoto", + "hours": "8:45-17:00", + "price": "¥1000", + "rating": 5, + "day": 11, + "links": [] + }, + { + "id": 29, + "name": "Нара Парк", + "nameJp": "奈良公園", + "category": "sight", + "city": "nara", + "lat": 34.6851, + "lng": 135.8430, + "description": "1200+ священных оленей свободно гуляют по парку! Можно покормить и сфотографироваться.", + "address": "Nara Park, Nara", + "hours": "24/7", + "price": "Бесплатно (корм ¥200)", + "rating": 5, + "day": 10, + "links": [], + "tips": "Олени кусаются, если дразнить едой" + }, + { + "id": 30, + "name": "Тодай-дзи", + "nameJp": "東大寺", + "category": "sight", + "city": "nara", + "lat": 34.6890, + "lng": 135.8399, + "description": "Гигантский бронзовый Будда (15м) в самом большом деревянном здании мира!", + "address": "406-1 Zoshicho, Nara", + "hours": "7:30-17:30", + "price": "¥600", + "rating": 5, + "day": 10, + "links": ["https://www.todaiji.or.jp/"] + }, + { + "id": 31, + "name": "Касуга-тайся", + "nameJp": "春日大社", + "category": "sight", + "city": "nara", + "lat": 34.6809, + "lng": 135.8490, + "description": "3000 каменных и бронзовых фонарей! Дважды в год зажигают все — магическое зрелище.", + "address": "160 Kasuganocho, Nara", + "hours": "6:30-17:30", + "price": "¥500", + "rating": 4, + "day": 10, + "links": [] + }, + { + "id": 32, + "name": "Замок в Осаке", + "nameJp": "大阪城", + "category": "sight", + "city": "osaka", + "lat": 34.6873, + "lng": 135.5262, + "description": "Символ Осаки (1583). Музей внутри, смотровая на 8 этаже с панорамой города!", + "address": "1-1 Osakajo, Chuo Ward, Osaka", + "hours": "9:00-17:00", + "price": "¥600", + "rating": 5, + "day": 12, + "links": [] + }, + { + "id": 33, + "name": "Куромон Маркет", + "nameJp": "黒門市場", + "category": "restaurant", + "city": "osaka", + "lat": 34.6672, + "lng": 135.5065, + "description": "\"Кухня Осаки\" — вагю, тунец, фугу, устрицы! Лучший стрит-фуд в городе.", + "address": "Kuromon Market, Chuo Ward, Osaka", + "hours": "9:00-18:00", + "price": "¥1000-5000", + "rating": 5, + "day": 12, + "links": [] + }, + { + "id": 34, + "name": "Дотонбори", + "nameJp": "道頓堀", + "category": "sight", + "city": "osaka", + "lat": 34.6687, + "lng": 135.5013, + "description": "Неоновое сердце Осаки! Вывеска Glico Man, такояки, гёдза, ночная жизнь.", + "address": "Dotonbori, Chuo Ward, Osaka", + "hours": "24/7", + "price": "Бесплатно", + "rating": 5, + "day": 12, + "links": [] + }, + { + "id": 35, + "name": "Universal Studios Japan", + "nameJp": "ユニバーサル・スタジオ・ジャパン", + "category": "sight", + "city": "osaka", + "lat": 34.6654, + "lng": 135.4323, + "description": "Super Nintendo World + Harry Potter! Полный день приключений.", + "address": "2-1-33 Sakurajima, Konohana Ward, Osaka", + "hours": "9:00-21:00", + "price": "¥8600", + "rating": 5, + "day": 13, + "links": ["https://www.usj.co.jp/"], + "tips": "Билеты уже куплены! 🎟️" + }, + { + "id": 36, + "name": "Замок Химэдзи", + "nameJp": "姫路城", + "category": "sight", + "city": "osaka", + "lat": 34.8394, + "lng": 134.6939, + "description": "Самый красивый замок Японии — \"Белая цапля\". 400 лет без разрушений! UNESCO.", + "address": "68 Honmachi, Himeji, Hyogo", + "hours": "9:00-17:00", + "price": "¥1000", + "rating": 5, + "day": 14, + "links": [], + "tips": "45 мин от Осаки" + }, + { + "id": 37, + "name": "Синсэкай", + "nameJp": "新世界", + "category": "sight", + "city": "osaka", + "lat": 34.6524, + "lng": 135.5063, + "description": "Ретро-район с башней Цутэнкаку. Родина кусикацу! Атмосфера Осаки 60-х.", + "address": "Shinsekai, Naniwa Ward, Osaka", + "hours": "24/7", + "price": "Бесплатно", + "rating": 4, + "day": 14, + "links": [] + }, + { + "id": 38, + "name": "Мемориал мира", + "nameJp": "広島平和記念公園", + "category": "sight", + "city": "hiroshima", + "lat": 34.3955, + "lng": 132.4536, + "description": "Атомный купол, Мемориальный музей. Тяжело, но важно. Заложите 2-3 часа.", + "address": "1-2 Nakajimacho, Naka Ward, Hiroshima", + "hours": "8:30-18:00", + "price": "¥200", + "rating": 5, + "day": 15, + "links": ["https://hpmmuseum.jp/"] + }, + { + "id": 39, + "name": "Святилище Ицукусима", + "nameJp": "厳島神社", + "category": "sight", + "city": "hiroshima", + "lat": 34.2960, + "lng": 132.3198, + "description": "Иконические красные тории в воде! UNESCO, один из самых красивых видов Японии.", + "address": "1-1 Miyajimacho, Hatsukaichi, Hiroshima", + "hours": "6:30-18:00", + "price": "¥300", + "rating": 5, + "day": 16, + "links": [] + }, + { + "id": 40, + "name": "Гора Мисэн", + "nameJp": "弥山", + "category": "sight", + "city": "hiroshima", + "lat": 34.2803, + "lng": 132.3158, + "description": "Священная гора острова Миядзима (535м). Канатка или 2 часа пешком. Потрясающие виды!", + "address": "Mount Misen, Miyajima, Hiroshima", + "hours": "9:00-17:00 (канатка)", + "price": "¥1800", + "rating": 5, + "day": 16, + "links": [] + }, + { + "id": 41, + "name": "Daikokuya Tempura", + "nameJp": "大黒家天麩羅", + "category": "restaurant", + "city": "tokyo", + "lat": 35.7134, + "lng": 139.7945, + "description": "Легендарная темпура с 1887 года! Хрустящие креветки и овощи.", + "address": "1-38-10 Asakusa, Taito City, Tokyo", + "hours": "11:00-20:30", + "price": "¥2000", + "rating": 5, + "day": 1, + "links": [] + }, + { + "id": 42, + "name": "Ichiran Ramen", + "nameJp": "一蘭", + "category": "restaurant", + "city": "tokyo", + "lat": 35.6598, + "lng": 139.6998, + "description": "Персональные кабинки для рамена! Тонкоцу бульон, выбираешь крепость, масло, чеснок.", + "address": "1-22-7 Jinnan, Shibuya City, Tokyo", + "hours": "24/7", + "price": "¥1200", + "rating": 5, + "day": 2, + "links": [] + }, + { + "id": 43, + "name": "Okonomimura", + "nameJp": "お好み村", + "category": "restaurant", + "city": "hiroshima", + "lat": 34.3942, + "lng": 132.4572, + "description": "Трёхэтажный комплекс с 25 магазинами окономияки! Хиросимский стиль с лапшой.", + "address": "5-13 Shintenchi, Naka Ward, Hiroshima", + "hours": "11:00-21:00", + "price": "¥1000-1500", + "rating": 5, + "day": 15, + "links": [] + }, + { + "id": 44, + "name": "Daruma Kushikatsu", + "nameJp": "串カツだるま", + "category": "restaurant", + "city": "osaka", + "lat": 34.6687, + "lng": 135.5018, + "description": "Знаменитые кусикацу с 1929 года! Панированные шашлычки, НЕ макать дважды в соус!", + "address": "2-3-9 Dotonbori, Chuo Ward, Osaka", + "hours": "11:00-22:30", + "price": "¥2000", + "rating": 5, + "day": 12, + "links": [] + }, + { + "id": 45, + "name": "Takoyaki Wanaka", + "nameJp": "たこ焼き わなか", + "category": "restaurant", + "city": "osaka", + "lat": 34.6685, + "lng": 135.5008, + "description": "Лучшие такояки в Осаке! Шарики с осьминогом, хрустящие снаружи, кремовые внутри.", + "address": "Dotonbori, Chuo Ward, Osaka", + "hours": "10:00-23:00", + "price": "¥600", + "rating": 5, + "day": 12, + "links": [] + } +] diff --git a/src/data/schedule.json b/src/data/schedule.json new file mode 100644 index 0000000..55f12f2 --- /dev/null +++ b/src/data/schedule.json @@ -0,0 +1,1039 @@ +{ + "day1": { + "date": "2026-03-03", + "dayOfWeek": "вторник", + "city": "tokyo", + "title": "Асакуса, Уэно, Skytree, Акихабара", + "schedule": [ + { + "time": "06:30", + "placeId": 1, + "name": "Сэнсодзи", + "note": "Утренняя служба, минимум туристов" + }, + { + "time": "08:00", + "placeId": 95, + "name": "Fuglen Asakusa", + "note": "Кофе после храма" + }, + { + "time": "08:45", + "placeId": 83, + "name": "Asakusa Kagetsudo", + "note": "Гигантский мелонпан" + }, + { + "time": "09:15", + "placeId": 84, + "name": "Kibi Dango Azuma", + "note": "Традиционные данго" + }, + { + "time": "10:00", + "placeId": 2, + "name": "Парк Уэно", + "note": "Прогулка, музеи по желанию" + }, + { + "time": "12:00", + "type": "lunch", + "name": "Обед в районе Уэно" + }, + { + "time": "13:30", + "placeId": 4, + "name": "Акихабара", + "note": "Аниме, электроника, мейд-кафе" + }, + { + "time": "15:00", + "placeId": 112, + "name": "Сакура у реки Накагава", + "note": "Кавадзудзакура + рапс, фото со Skytree" + }, + { + "time": "16:00", + "placeId": 3, + "name": "Tokyo Skytree", + "note": "Закат с высоты 350м" + }, + { + "time": "18:00", + "placeId": 93, + "name": "Unlimited Coffee Bar", + "note": "Кофе у Skytree" + }, + { + "time": "19:00", + "placeId": 41, + "name": "Daikokuya Tempura", + "note": "Легендарная темпура" + }, + { + "time": "20:30", + "type": "hotel", + "placeId": 46, + "name": "Smile Hotel Tokyo Nihonbashi" + } + ] + }, + "day2": { + "date": "2026-03-04", + "dayOfWeek": "среда", + "city": "tokyo", + "title": "Мэйдзи, Йойоги, Одайба + teamLab", + "schedule": [ + { + "time": "08:00", + "placeId": 5, + "name": "Храм Мэйдзи", + "note": "Священный лес, тории" + }, + { + "time": "09:30", + "placeId": 113, + "name": "Парк Йойоги", + "note": "Прогулка после храма" + }, + { + "time": "10:30", + "placeId": 96, + "name": "Sarutahiko Coffee", + "note": "Кофе у станции" + }, + { + "time": "11:00", + "placeId": 86, + "name": "Marion Crepes", + "note": "Знаменитые крепы" + }, + { + "time": "11:30", + "type": "walk", + "name": "Омотэсандо", + "note": "Прогулка по бульвару" + }, + { + "time": "13:00", + "type": "lunch", + "name": "Обед в Омотэсандо" + }, + { + "time": "14:30", + "placeId": 9, + "name": "Синдзюку Гёэн", + "note": "Императорский сад, сакура" + }, + { + "time": "17:00", + "placeId": 12, + "name": "Одайба", + "note": "Футуристический остров, Gundam" + }, + { + "time": "18:00", + "type": "dinner", + "name": "Ужин в Одайбе" + }, + { + "time": "19:00", + "placeId": 11, + "name": "teamLab Planets", + "note": "Иммерсивное искусство босиком" + }, + { + "time": "21:00", + "placeId": 42, + "name": "Ichiran Ramen", + "note": "Персональные кабинки" + }, + { + "time": "22:00", + "type": "hotel", + "placeId": 46, + "name": "Smile Hotel Tokyo Nihonbashi" + } + ] + }, + "day3": { + "date": "2026-03-05", + "dayOfWeek": "четверг", + "city": "tokyo", + "title": "Сэнсо-дзи, Уэно, Цукидзи, Дворец, Сибуя", + "schedule": [ + { + "time": "07:00", + "placeId": 1, + "name": "Сэнсо-дзи", + "note": "Утром пусто и красиво" + }, + { + "time": "08:30", + "placeId": 83, + "name": "Asakusa Kagetsudo", + "note": "Гигантский мелонпан" + }, + { + "time": "09:00", + "placeId": 2, + "name": "Парк Уэно", + "note": "Прогулка, музеи по желанию" + }, + { + "time": "11:00", + "placeId": 10, + "name": "Цукидзи", + "note": "Свежайшие морепродукты" + }, + { + "time": "11:30", + "placeId": 87, + "name": "Tsukiji Yamacho", + "note": "Знаменитый тамагояки" + }, + { + "time": "12:00", + "placeId": 101, + "name": "Sanoki-ya Tsukiji", + "note": "Тайяки в форме тунца" + }, + { + "time": "13:00", + "placeId": 13, + "name": "Императорский дворец", + "note": "Восточные сады" + }, + { + "time": "15:00", + "placeId": 71, + "name": "Гиндза", + "note": "Шоппинг, главная улица" + }, + { + "time": "15:30", + "placeId": 102, + "name": "Ginza Kimuraya", + "note": "Оригинальный анпан" + }, + { + "time": "18:00", + "placeId": 7, + "name": "Сибуя — перекрёсток", + "note": "Самый загруженный перекрёсток" + }, + { + "time": "19:00", + "placeId": 8, + "name": "Shibuya Sky", + "note": "Забронировано на 19:00" + }, + { + "time": "21:00", + "type": "hotel", + "placeId": 46, + "name": "Smile Hotel Tokyo Nihonbashi" + } + ] + }, + "day4": { + "date": "2026-03-06", + "dayOfWeek": "пятница", + "city": "nikko", + "title": "Никко — храмы и водопады", + "schedule": [ + { + "time": "07:00", + "type": "transport", + "name": "Поезд Токио → Никко", + "note": "~2 часа, NIKKO PASS" + }, + { + "time": "09:30", + "placeId": 14, + "name": "Никко Тосё-гу", + "note": "UNESCO, три обезьяны" + }, + { + "time": "12:30", + "type": "lunch", + "name": "Обед в Никко", + "note": "Попробуй юба (соевая кожица)" + }, + { + "time": "14:00", + "placeId": 15, + "name": "Водопад Кэгон", + "note": "97м, смотровая на лифте" + }, + { + "time": "16:00", + "placeId": 103, + "name": "Naniwaya Souhonten", + "note": "Тайяки по дороге домой" + }, + { + "time": "17:00", + "type": "transport", + "name": "Поезд Никко → Токио" + }, + { + "time": "19:30", + "type": "dinner", + "name": "Ужин в Токио" + }, + { + "time": "21:00", + "type": "hotel", + "placeId": 46, + "name": "Smile Hotel Tokyo Nihonbashi" + } + ] + }, + "day5": { + "date": "2026-03-07", + "dayOfWeek": "суббота", + "city": "hakone", + "title": "Хаконэ — вулкан Овакудани", + "schedule": [ + { + "time": "08:00", + "type": "transport", + "name": "Поезд Токио → Хаконэ-Юмото", + "note": "~1.5 часа" + }, + { + "time": "09:30", + "type": "transport", + "name": "Канатная дорога до Овакудани" + }, + { + "time": "10:00", + "placeId": 16, + "name": "Овакудани", + "note": "Вулканическая долина, вид на Фудзи" + }, + { + "time": "10:30", + "placeId": 100, + "name": "Owakudani Kurotamago", + "note": "Чёрные яйца +7 лет жизни!" + }, + { + "time": "12:00", + "type": "lunch", + "name": "Обед в Хаконэ" + }, + { + "time": "14:00", + "placeId": 98, + "name": "Naraya Cafe", + "note": "Кофе + футбат (онсэн для ног)" + }, + { + "time": "15:30", + "placeId": 99, + "name": "Cafe Ryusenkei", + "note": "Кофе в Airstream 1967" + }, + { + "time": "17:00", + "type": "onsen", + "name": "Онсэн в рёкане", + "note": "Вечерняя баня" + }, + { + "time": "19:00", + "type": "dinner", + "name": "Кайсэки ужин в рёкане" + } + ] + }, + "day6": { + "date": "2026-03-08", + "dayOfWeek": "воскресенье", + "city": "hakone", + "title": "Хаконэ — озеро Аси и храм", + "schedule": [ + { + "time": "07:00", + "placeId": 18, + "name": "Храм Хаконэ-дзиндзя", + "note": "Тории в воде, пустынно" + }, + { + "time": "09:00", + "placeId": 97, + "name": "Bakery & Table Hakone", + "note": "Завтрак с видом на озеро" + }, + { + "time": "11:00", + "placeId": 17, + "name": "Озеро Аси", + "note": "Пиратский корабль" + }, + { + "time": "13:00", + "type": "lunch", + "name": "Обед у озера" + }, + { + "time": "15:00", + "type": "transport", + "name": "Переезд в Киото", + "note": "Shinkansen ~2.5 часа" + }, + { + "time": "18:00", + "type": "hotel", + "placeId": 47, + "name": "Waka Kyoto Kawaramachi", + "note": "Check-in" + }, + { + "time": "19:00", + "type": "dinner", + "name": "Ужин в Понтотё" + } + ] + }, + "day7": { + "date": "2026-03-09", + "dayOfWeek": "понедельник", + "city": "kyoto", + "title": "Фусими Инари, Кийомидзу-дэра, Гион", + "schedule": [ + { + "time": "06:30", + "placeId": 19, + "name": "Фусими Инари", + "note": "10,000 тории, пустынно утром!" + }, + { + "time": "09:00", + "placeId": 79, + "name": "Kurasu Kyoto", + "note": "Кофе у станции" + }, + { + "time": "10:00", + "placeId": 20, + "name": "Кийомидзу-дэра", + "note": "Храм без гвоздей" + }, + { + "time": "12:00", + "placeId": 107, + "name": "Nishiki Market", + "note": "Обед-прогулка по рынку" + }, + { + "time": "14:00", + "placeId": 80, + "name": "Weekenders Coffee", + "note": "Топ обжарщик Японии" + }, + { + "time": "15:00", + "type": "free", + "name": "Свободное время", + "note": "Отдых, шоппинг" + }, + { + "time": "17:00", + "placeId": 21, + "name": "Гион", + "note": "Шанс увидеть гейш!" + }, + { + "time": "19:00", + "placeId": 72, + "name": "Понтотё", + "note": "Ужин в атмосферном переулке у реки Камо" + }, + { + "time": "21:00", + "type": "hotel", + "placeId": 47, + "name": "Waka Kyoto Kawaramachi" + } + ] + }, + "day8": { + "date": "2026-03-10", + "dayOfWeek": "вторник", + "city": "kyoto", + "title": "Арасияма, Золотой павильон, Рёан-дзи", + "schedule": [ + { + "time": "07:00", + "placeId": 22, + "name": "Бамбуковый лес Арасияма", + "note": "До толп туристов!" + }, + { + "time": "08:00", + "placeId": 78, + "name": "% Arabica Arashiyama", + "note": "Культовый кофе с видом" + }, + { + "time": "09:00", + "placeId": 110, + "name": "Arashiyama Food Street", + "note": "Снэки и данго" + }, + { + "time": "10:00", + "placeId": 23, + "name": "Кинкаку-дзи", + "note": "Золотой павильон" + }, + { + "time": "11:30", + "placeId": 24, + "name": "Рёан-дзи", + "note": "Знаменитый сад камней" + }, + { + "time": "13:00", + "type": "lunch", + "name": "Обед в северном Киото" + }, + { + "time": "15:00", + "type": "free", + "name": "Свободное время", + "note": "Отдых или дополнительные храмы" + }, + { + "time": "19:00", + "type": "dinner", + "name": "Ужин" + }, + { + "time": "21:00", + "type": "hotel", + "placeId": 47, + "name": "Waka Kyoto Kawaramachi" + } + ] + }, + "day9": { + "date": "2026-03-11", + "dayOfWeek": "среда", + "city": "kyoto", + "title": "Серебряный павильон, Философский путь, Нисики", + "schedule": [ + { + "time": "08:30", + "placeId": 25, + "name": "Гинкаку-дзи", + "note": "Серебряный павильон" + }, + { + "time": "10:00", + "placeId": 26, + "name": "Философский путь", + "note": "2 км под сакурой" + }, + { + "time": "11:30", + "placeId": 106, + "name": "Blue Bottle Kyoto", + "note": "В 100-летнем machiya" + }, + { + "time": "12:30", + "placeId": 27, + "name": "Нисики Маркет", + "note": "Обед на рынке" + }, + { + "time": "14:30", + "type": "free", + "name": "Свободное время", + "note": "Шоппинг или отдых" + }, + { + "time": "19:00", + "type": "dinner", + "name": "Ужин" + }, + { + "time": "21:00", + "type": "hotel", + "placeId": 47, + "name": "Waka Kyoto Kawaramachi" + } + ] + }, + "day10": { + "date": "2026-03-12", + "dayOfWeek": "четверг", + "city": "nara", + "title": "Нара — олени и гигантский Будда", + "schedule": [ + { + "time": "08:00", + "type": "transport", + "name": "Поезд Киото → Нара", + "note": "~45 минут" + }, + { + "time": "09:00", + "placeId": 29, + "name": "Нара Парк", + "note": "1200+ священных оленей" + }, + { + "time": "10:30", + "placeId": 30, + "name": "Тодай-дзи", + "note": "15м Будда, дыра просветления" + }, + { + "time": "12:00", + "placeId": 104, + "name": "Nakatanidou", + "note": "Вирусное шоу мочи!" + }, + { + "time": "12:30", + "type": "lunch", + "name": "Обед в Нарамати" + }, + { + "time": "14:00", + "placeId": 105, + "name": "ANY Specialty Coffee", + "note": "Specialty кофе" + }, + { + "time": "15:00", + "placeId": 31, + "name": "Касуга-тайся", + "note": "3000 каменных фонарей" + }, + { + "time": "17:00", + "type": "transport", + "name": "Поезд Нара → Киото" + }, + { + "time": "19:00", + "type": "dinner", + "name": "Ужин в Киото" + }, + { + "time": "21:00", + "type": "hotel", + "placeId": 47, + "name": "Waka Kyoto Kawaramachi" + } + ] + }, + "day11": { + "date": "2026-03-13", + "dayOfWeek": "пятница", + "city": "kyoto", + "title": "Замок Нидзо + переезд в Осаку", + "schedule": [ + { + "time": "08:45", + "placeId": 28, + "name": "Замок Нидзо", + "note": "Поющие полы, UNESCO" + }, + { + "time": "11:00", + "type": "free", + "name": "Последние покупки в Киото" + }, + { + "time": "12:30", + "type": "lunch", + "name": "Обед в Киото" + }, + { + "time": "14:00", + "type": "transport", + "name": "Переезд в Осаку", + "note": "~30 минут Shinkansen" + }, + { + "time": "15:00", + "type": "hotel", + "placeId": 48, + "name": "Hotel Keihan Tenmabashi", + "note": "Check-in" + }, + { + "time": "16:00", + "type": "free", + "name": "Отдых" + }, + { + "time": "19:00", + "type": "dinner", + "name": "Ужин в Осаке" + }, + { + "time": "21:00", + "type": "hotel", + "placeId": 48, + "name": "Hotel Keihan Tenmabashi" + } + ] + }, + "day12": { + "date": "2026-03-14", + "dayOfWeek": "суббота", + "city": "osaka", + "title": "Замок Осаки, Куромон, Дотонбори", + "schedule": [ + { + "time": "08:00", + "placeId": 82, + "name": "Glitch Coffee Osaka", + "note": "Лучший specialty кофе" + }, + { + "time": "09:00", + "placeId": 32, + "name": "Замок в Осаке", + "note": "Символ города, музей" + }, + { + "time": "11:00", + "placeId": 33, + "name": "Куромон Маркет", + "note": "Вагю, устрицы, фугу" + }, + { + "time": "13:00", + "type": "lunch", + "name": "Обед на рынке" + }, + { + "time": "14:30", + "placeId": 81, + "name": "LiLo Coffee Roasters", + "note": "Крошечная легенда" + }, + { + "time": "16:00", + "type": "free", + "name": "Шоппинг в Shinsaibashi" + }, + { + "time": "18:00", + "placeId": 34, + "name": "Дотонбори", + "note": "Неоновое сердце Осаки" + }, + { + "time": "18:30", + "placeId": 90, + "name": "Takoyaki Wanaka", + "note": "Лучшие такояки" + }, + { + "time": "19:00", + "placeId": 109, + "name": "Dotonbori Akaoni", + "note": "Michelin такояки" + }, + { + "time": "20:00", + "placeId": 44, + "name": "Daruma Kushikatsu", + "note": "Кусикацу с 1929 года" + }, + { + "time": "22:00", + "type": "hotel", + "placeId": 48, + "name": "Hotel Keihan Tenmabashi" + } + ] + }, + "day13": { + "date": "2026-03-15", + "dayOfWeek": "воскресенье", + "city": "osaka", + "title": "Universal Studios Japan 🎢", + "schedule": [ + { + "time": "08:00", + "type": "transport", + "name": "Поезд до USJ", + "note": "30 минут от Осаки" + }, + { + "time": "08:30", + "placeId": 35, + "name": "Universal Studios Japan", + "note": "К открытию! Билеты куплены 🎟️" + }, + { + "time": "09:00", + "type": "attraction", + "name": "Super Nintendo World", + "note": "Первым делом!" + }, + { + "time": "12:00", + "type": "lunch", + "name": "Обед в парке" + }, + { + "time": "14:00", + "type": "attraction", + "name": "Harry Potter World", + "note": "Хогвартс!" + }, + { + "time": "17:00", + "type": "attraction", + "name": "Другие аттракционы" + }, + { + "time": "20:00", + "type": "transport", + "name": "Возвращение в отель" + }, + { + "time": "21:00", + "type": "dinner", + "name": "Лёгкий ужин" + }, + { + "time": "22:00", + "type": "hotel", + "placeId": 48, + "name": "Hotel Keihan Tenmabashi" + } + ] + }, + "day14": { + "date": "2026-03-16", + "dayOfWeek": "понедельник", + "city": "himeji", + "title": "Замок Химэдзи + Синсэкай", + "schedule": [ + { + "time": "08:00", + "type": "transport", + "name": "Shinkansen Осака → Химэдзи", + "note": "45 минут" + }, + { + "time": "09:00", + "placeId": 36, + "name": "Замок Химэдзи", + "note": "Белая цапля, UNESCO" + }, + { + "time": "12:00", + "type": "lunch", + "name": "Обед в Химэдзи" + }, + { + "time": "13:30", + "type": "transport", + "name": "Поезд обратно в Осаку" + }, + { + "time": "15:00", + "placeId": 37, + "name": "Синсэкай", + "note": "Ретро-район, башня Цутэнкаку" + }, + { + "time": "17:00", + "type": "dinner", + "name": "Кусикацу в Синсэкай", + "note": "Не макай дважды!" + }, + { + "time": "19:00", + "type": "transport", + "name": "Переезд в Хиросиму", + "note": "Shinkansen ~1.5 часа" + }, + { + "time": "21:00", + "type": "hotel", + "placeId": 49, + "name": "Hotel Hiroshima Ekimae Universal" + } + ] + }, + "day15": { + "date": "2026-03-17", + "dayOfWeek": "вторник", + "city": "hiroshima", + "title": "Хиросима — Мемориал мира", + "schedule": [ + { + "time": "09:00", + "placeId": 38, + "name": "Мемориал мира", + "note": "Атомный купол, музей" + }, + { + "time": "12:00", + "placeId": 108, + "name": "Obscura Coffee Hondori", + "note": "Кофе после музея" + }, + { + "time": "12:30", + "placeId": 43, + "name": "Okonomi-mura", + "note": "Деревня окономияки" + }, + { + "time": "13:30", + "placeId": 43, + "name": "Okonomimura", + "note": "Хиросимский стиль" + }, + { + "time": "15:00", + "type": "free", + "name": "Прогулка по городу" + }, + { + "time": "17:00", + "type": "free", + "name": "Отдых" + }, + { + "time": "19:00", + "type": "dinner", + "name": "Ужин в Хиросиме" + }, + { + "time": "21:00", + "type": "hotel", + "placeId": 49, + "name": "Hotel Hiroshima Ekimae Universal" + } + ] + }, + "day16": { + "date": "2026-03-18", + "dayOfWeek": "среда", + "city": "miyajima", + "title": "Миядзима — тории в воде, гора Мисэн", + "schedule": [ + { + "time": "07:30", + "type": "transport", + "name": "Поезд + паром на Миядзиму", + "note": "~1 час, JR Pass работает" + }, + { + "time": "09:00", + "placeId": 39, + "name": "Святилище Ицукусима", + "note": "Красные тории в воде" + }, + { + "time": "10:30", + "placeId": 40, + "name": "Гора Мисэн", + "note": "Канатка + вершина" + }, + { + "time": "13:00", + "placeId": 111, + "name": "Miyajima Omotesando", + "note": "Momiji manju, устрицы" + }, + { + "time": "14:00", + "type": "lunch", + "name": "Обед на острове", + "note": "Угорь или устрицы" + }, + { + "time": "16:00", + "type": "transport", + "name": "Паром обратно" + }, + { + "time": "17:00", + "type": "transport", + "name": "Возвращение/отправление", + "note": "Зависит от плана" + }, + { + "time": "19:00", + "type": "dinner", + "name": "Прощальный ужин" + } + ] + }, + "day0": { + "date": "2026-03-02", + "dayOfWeek": "понедельник", + "city": "tokyo", + "title": "Прилёт, Nihonbashi, встреча Светы", + "schedule": [ + { + "time": "13:30", + "name": "Прилёт в Narita T3", + "note": "Паспортный контроль, багаж" + }, + { + "time": "14:00", + "name": "JR Travel Service Center (T2)", + "note": "Welcome Suica (¥5,000) + N\"EX билет (¥3,070)" + }, + { + "time": "14:15", + "name": "Narita Express → Tokyo Station", + "note": "~60 мин" + }, + { + "time": "15:15", + "name": "Tokyo Station → Smile Hotel Nihonbashi", + "note": "Пешком ~7 мин, check-in" + }, + { + "time": "16:00", + "name": "ABC-MART Yaesu Underground", + "note": "Кроссовки, Tax Free с паспортом" + }, + { + "time": "17:00", + "name": "Прогулка по Ginza", + "note": "ASICS, Uniqlo flagship, магазины" + }, + { + "time": "18:30", + "name": "Ужин в районе Ginza/Nihonbashi", + "note": "" + }, + { + "time": "19:00", + "name": "Метро в Haneda Airport", + "note": "Nihonbashi → Asakusa Line → Keikyu → Haneda T3, ~40 мин, ¥620" + }, + { + "time": "20:00", + "name": "Встреча Светы в Haneda T3", + "note": "Прилёт в 20:00" + }, + { + "time": "20:30", + "name": "Haneda → Nihonbashi", + "note": "Keikyu → Asakusa Line → Nihonbashi, ~40 мин" + } + ] + } +} \ No newline at end of file diff --git a/src/hooks/useGeolocation.ts b/src/hooks/useGeolocation.ts new file mode 100644 index 0000000..fe03b7f --- /dev/null +++ b/src/hooks/useGeolocation.ts @@ -0,0 +1,64 @@ +import { useState, useEffect, useCallback } from "react"; +import { GeoPosition } from "../types"; + +export function useGeolocation() { + const [position, setPosition] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + const getCurrentPosition = useCallback(() => { + if (!navigator.geolocation) { + setError("Геолокация не поддерживается"); + return; + } + + setLoading(true); + setError(null); + + navigator.geolocation.getCurrentPosition( + (pos) => { + setPosition({ + lat: pos.coords.latitude, + lng: pos.coords.longitude, + accuracy: pos.coords.accuracy + }); + setLoading(false); + }, + (err) => { + setError(err.message); + setLoading(false); + }, + { + enableHighAccuracy: true, + timeout: 10000, + maximumAge: 60000 + } + ); + }, []); + + useEffect(() => { + getCurrentPosition(); + + const watchId = navigator.geolocation?.watchPosition( + (pos) => { + setPosition({ + lat: pos.coords.latitude, + lng: pos.coords.longitude, + accuracy: pos.coords.accuracy + }); + }, + () => {}, + { + enableHighAccuracy: true, + timeout: 30000, + maximumAge: 10000 + } + ); + + return () => { + if (watchId) navigator.geolocation.clearWatch(watchId); + }; + }, [getCurrentPosition]); + + return { position, error, loading, refresh: getCurrentPosition }; +} diff --git a/src/hooks/usePlaces.ts b/src/hooks/usePlaces.ts new file mode 100644 index 0000000..6b24fda --- /dev/null +++ b/src/hooks/usePlaces.ts @@ -0,0 +1,55 @@ +import { useMemo } from "react"; +import { Place, GeoPosition } from "../types"; +import placesData from "../data/places.json"; + +function getDistance(p1: GeoPosition, p2: { lat: number; lng: number }): number { + const R = 6371; // Earth radius in km + const dLat = ((p2.lat - p1.lat) * Math.PI) / 180; + const dLng = ((p2.lng - p1.lng) * Math.PI) / 180; + const a = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos((p1.lat * Math.PI) / 180) * + Math.cos((p2.lat * Math.PI) / 180) * + Math.sin(dLng / 2) * + Math.sin(dLng / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return R * c; +} + +export function formatDistance(km: number): string { + if (km < 1) return `${Math.round(km * 1000)}м`; + return `${km.toFixed(1)}км`; +} + +export function usePlaces(position: GeoPosition | null) { + const places = placesData as Place[]; + + const placesWithDistance = useMemo(() => { + if (!position) return places.map(p => ({ ...p, distance: undefined })); + + return places.map(place => ({ + ...place, + distance: getDistance(position, { lat: place.lat, lng: place.lng }) + })).sort((a, b) => (a.distance ?? 0) - (b.distance ?? 0)); + }, [places, position]); + + const getPlacesByDay = (day: number) => + placesWithDistance.filter(p => p.day === day); + + const getPlacesByCategory = (category: string) => + placesWithDistance.filter(p => p.category === category); + + const getPlacesByCity = (city: string) => + placesWithDistance.filter(p => p.city === city); + + const getNearby = (limit = 5) => + position ? placesWithDistance.slice(0, limit) : []; + + return { + places: placesWithDistance, + getPlacesByDay, + getPlacesByCategory, + getPlacesByCity, + getNearby + }; +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..e3669c3 --- /dev/null +++ b/src/index.css @@ -0,0 +1,187 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --bg-primary: #FAFAFA; + --bg-card: #FFFFFF; + --accent: #FF6B6B; + --accent-light: #FFE8E8; + --text-primary: #1A1A1A; + --text-secondary: #6B7280; + --text-muted: #9CA3AF; + --border: #E5E7EB; + --shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12); + --nav-height: 64px; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + height: 100%; + width: 100%; + overflow: hidden; +} + +#root { + height: 100%; + width: 100%; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Noto Sans JP', sans-serif; + background-color: var(--bg-primary); + color: var(--text-primary); + -webkit-font-smoothing: antialiased; +} + +.app-container { + display: flex; + flex-direction: column; + height: 100vh; + height: 100dvh; + overflow: hidden; + background: var(--bg-primary); + /* Оставляем место для фиксированной навигации */ + padding-bottom: var(--nav-height); +} + +.app-main { + flex: 1; + position: relative; + overflow: hidden; + min-height: 0; +} + +/* Map Styles - карта занимает всё пространство */ +.map-wrapper { + width: 100%; + height: 100%; + position: relative; +} + +.map-container { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1; +} + +/* Ограничиваем z-index leaflet чтобы не перекрывал навигацию */ +.leaflet-container { + width: 100% !important; + height: 100% !important; + background: #F3F4F6 !important; + z-index: 1 !important; +} + +.leaflet-pane { + z-index: 1 !important; +} + +.leaflet-top, +.leaflet-bottom { + z-index: 10 !important; +} + +.leaflet-control-zoom a { + background: var(--bg-card) !important; + color: var(--text-primary) !important; + border: 1px solid var(--border) !important; +} + +.leaflet-control-attribution { + background: rgba(255, 255, 255, 0.9) !important; + color: var(--text-muted) !important; + font-size: 10px !important; +} + +.leaflet-popup-content-wrapper { + background: var(--bg-card) !important; + color: var(--text-primary) !important; + border-radius: 16px !important; + box-shadow: var(--shadow-lg) !important; +} + +.leaflet-popup-tip { + background: var(--bg-card) !important; +} + +/* Scrollbar */ +.scrollbar-hide::-webkit-scrollbar { + display: none; +} +.scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; +} + +/* Custom scrollbar for desktop */ +::-webkit-scrollbar { + width: 6px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; +} +::-webkit-scrollbar-thumb:hover { + background: var(--text-muted); +} + +/* Animations */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes slideUp { + from { transform: translateY(100%); } + to { transform: translateY(0); } +} + +@keyframes pulse { + 0% { box-shadow: 0 0 0 0 rgba(255, 107, 107, 0.4); } + 70% { box-shadow: 0 0 0 10px rgba(255, 107, 107, 0); } + 100% { box-shadow: 0 0 0 0 rgba(255, 107, 107, 0); } +} + +.animate-fade-in { + animation: fadeIn 0.3s ease-out; +} + +.animate-slide-up { + animation: slideUp 0.3s ease-out; +} + +/* Card hover effect */ +.card-hover { + transition: transform 0.2s ease, box-shadow 0.2s ease; +} +.card-hover:active { + transform: scale(0.98); +} + +/* Touch feedback */ +@media (hover: hover) { + .card-hover:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); + } +} + +/* Safe area for PWA */ +@supports (padding-bottom: env(safe-area-inset-bottom)) { + .safe-bottom { + padding-bottom: env(safe-area-inset-bottom); + } +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..9b67590 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + +); diff --git a/src/pages/FoodPage.tsx b/src/pages/FoodPage.tsx new file mode 100644 index 0000000..741b72c --- /dev/null +++ b/src/pages/FoodPage.tsx @@ -0,0 +1,147 @@ +import { useState, useMemo } from 'react'; +import { Place, GeoPosition } from '../types'; + +interface FoodPageProps { + places: Place[]; + position: GeoPosition | null; + onSelectPlace: (place: Place) => void; +} + +type FoodCategory = 'all' | 'restaurant' | 'coffee' | 'snack'; + +const categoryLabels: Record = { + all: { label: 'Все', icon: '🍽️' }, + restaurant: { label: 'Рестораны', icon: '🍜' }, + coffee: { label: 'Кофейни', icon: '☕' }, + snack: { label: 'Перекусы', icon: '🍡' }, +}; + +const getCityName = (city: string): string => { + const names: Record = { + tokyo: 'Токио', + kyoto: 'Киото', + osaka: 'Осака', + nara: 'Нара', + hiroshima: 'Хиросима', + hakone: 'Хаконэ', + }; + return names[city] || city; +}; + +export function FoodPage({ places, onSelectPlace }: FoodPageProps) { + const [category, setCategory] = useState('all'); + + const foodPlaces = useMemo(() => { + return places.filter(p => + p.category === 'restaurant' || + p.category === 'coffee' || + p.category === 'snack' + ); + }, [places]); + + const filteredPlaces = useMemo(() => { + if (category === 'all') return foodPlaces; + return foodPlaces.filter(p => p.category === category); + }, [foodPlaces, category]); + + const groupedByCity = useMemo(() => { + const groups: Record = {}; + filteredPlaces.forEach(place => { + const city = place.city || 'other'; + if (!groups[city]) groups[city] = []; + groups[city].push(place); + }); + // Sort by day within each city + Object.keys(groups).forEach(city => { + groups[city].sort((a, b) => (a.day || 99) - (b.day || 99)); + }); + return groups; + }, [filteredPlaces]); + + const categories: FoodCategory[] = ['all', 'restaurant', 'coffee', 'snack']; + + return ( +
+ {/* Header */} +
+

Еда и кофе

+

+ {foodPlaces.length} мест для гурманов +

+ + {/* Category filter */} +
+ {categories.map((cat) => { + const isSelected = cat === category; + const { label, icon } = categoryLabels[cat]; + return ( + + ); + })} +
+
+ + {/* Content */} +
+
+ {Object.entries(groupedByCity).map(([city, cityPlaces]) => ( +
+

+ {getCityName(city)} + ({cityPlaces.length}) +

+
+ {cityPlaces.map((place) => ( + + ))} +
+
+ ))} +
+
+
+ ); +} diff --git a/src/pages/MapPage.tsx b/src/pages/MapPage.tsx new file mode 100644 index 0000000..d687e5e --- /dev/null +++ b/src/pages/MapPage.tsx @@ -0,0 +1,231 @@ +import { useEffect, useMemo, useState } from 'react'; +import { MapContainer, TileLayer, Marker, Popup, useMap, Polyline } from 'react-leaflet'; +import L from 'leaflet'; +import { Place, GeoPosition } from '../types'; +import scheduleData from '../data/schedule.json'; +import 'leaflet/dist/leaflet.css'; + +interface MapPageProps { + places: Place[]; + position: GeoPosition | null; + selectedPlace: Place | null; + onSelectPlace: (place: Place) => void; + showRouteForDay: number | null; + onClearRoute: () => void; +} + +// Fix marker icons +delete (L.Icon.Default.prototype as any)._getIconUrl; +L.Icon.Default.mergeOptions({ + iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon-2x.png', + iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png', + shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png', +}); + +const createIcon = (emoji: string, isSelected: boolean = false) => { + return L.divIcon({ + className: 'custom-marker', + html: ` +
+ ${emoji} +
+ `, + iconSize: [40, 40], + iconAnchor: [20, 20], + }); +}; + +const getCategoryEmoji = (category: string): string => { + const emojis: Record = { + sight: '🏯', + restaurant: '🍜', + coffee: '☕', + snack: '🍡', + hotel: '🏨', + }; + return emojis[category] || '📍'; +}; + +function MapController({ center, zoom }: { center: [number, number]; zoom: number }) { + const map = useMap(); + useEffect(() => { + map.flyTo(center, zoom, { duration: 0.5 }); + }, [center, zoom, map]); + return null; +} + +function LocationMarker({ position }: { position: GeoPosition | null }) { + if (!position) return null; + + return ( +
+ `, + iconSize: [22, 22], + iconAnchor: [11, 11], + })} + /> + ); +} + +export function MapPage({ places, position, selectedPlace, onSelectPlace, showRouteForDay, onClearRoute }: MapPageProps) { + const [mapCenter, setMapCenter] = useState<[number, number]>([35.6762, 139.6503]); + const [mapZoom, setMapZoom] = useState(11); + const schedule = scheduleData as Record; + + // Get route for selected day + const routeCoordinates = useMemo(() => { + if (!showRouteForDay) return []; + const dayData = schedule[`day${showRouteForDay}`]; + if (!dayData) return []; + + const coords: [number, number][] = []; + dayData.schedule.forEach((item: any) => { + if (item.placeId) { + const place = places.find(p => p.id === item.placeId); + if (place) { + coords.push([place.lat, place.lng]); + } + } + }); + return coords; + }, [showRouteForDay, places, schedule]); + + // Update center when selected place changes + useEffect(() => { + if (selectedPlace) { + setMapCenter([selectedPlace.lat, selectedPlace.lng]); + setMapZoom(15); + } + }, [selectedPlace]); + + // Update center for route + useEffect(() => { + if (routeCoordinates.length > 0) { + const bounds = L.latLngBounds(routeCoordinates); + const center = bounds.getCenter(); + setMapCenter([center.lat, center.lng]); + setMapZoom(12); + } + }, [routeCoordinates]); + + const visiblePlaces = useMemo(() => { + if (showRouteForDay) { + const dayData = schedule[`day${showRouteForDay}`]; + if (!dayData) return places; + const placeIds = dayData.schedule + .filter((item: any) => item.placeId) + .map((item: any) => item.placeId); + return places.filter(p => placeIds.includes(p.id)); + } + return places; + }, [places, showRouteForDay, schedule]); + + return ( +
+ + + + + + + {/* Route polyline */} + {routeCoordinates.length > 1 && ( + + )} + + {/* Place markers */} + {visiblePlaces.map((place) => ( + onSelectPlace(place), + }} + > + +
+

{place.name}

+

{place.nameJp}

+ {place.day && ( + + День {place.day} + + )} +
+
+
+ ))} +
+ + {/* Map controls */} +
+ {position && ( + + )} +
+ + {/* Route info bar */} + {showRouteForDay && ( +
+
+
+

Маршрут дня {showRouteForDay}

+

{visiblePlaces.length} мест

+
+ +
+
+ )} +
+ ); +} diff --git a/src/pages/PlacesPage.tsx b/src/pages/PlacesPage.tsx new file mode 100644 index 0000000..a886e13 --- /dev/null +++ b/src/pages/PlacesPage.tsx @@ -0,0 +1,208 @@ +import { useState, useMemo } from 'react'; +import { Place, GeoPosition } from '../types'; +import { PlaceCard } from '../components/PlaceCard'; +import scheduleData from '../data/schedule.json'; + +interface PlacesPageProps { + places: Place[]; + position: GeoPosition | null; + onSelectPlace: (place: Place) => void; +} + +type ViewMode = 'days' | 'places' | 'search'; + +const getCityEmoji = (city: string): string => { + const cities: Record = { + tokyo: '🗼', + kyoto: '⛩️', + osaka: '🏯', + nara: '🦌', + hiroshima: '🕊️', + miyajima: '⛩️', + hakone: '🗻', + nikko: '🌲', + himeji: '🏰', + }; + return cities[city] || '📍'; +}; + +const getCityName = (city: string): string => { + const names: Record = { + tokyo: 'Токио', + kyoto: 'Киото', + osaka: 'Осака', + nara: 'Нара', + hiroshima: 'Хиросима', + miyajima: 'Миядзима', + hakone: 'Хаконэ', + nikko: 'Никко', + himeji: 'Химэдзи', + }; + return names[city] || city; +}; + +export function PlacesPage({ places, onSelectPlace }: PlacesPageProps) { + const [viewMode, setViewMode] = useState('days'); + const [searchQuery, setSearchQuery] = useState(''); + const schedule = scheduleData as Record; + + const days = useMemo(() => { + return Object.entries(schedule).map(([key, data]: [string, any]) => ({ + day: parseInt(key.replace('day', '')), + ...data, + })).sort((a, b) => a.day - b.day); + }, []); + + const filteredPlaces = useMemo(() => { + if (!searchQuery.trim()) return places; + const query = searchQuery.toLowerCase(); + return places.filter(p => + p.name.toLowerCase().includes(query) || + p.nameJp?.toLowerCase().includes(query) || + p.description?.toLowerCase().includes(query) + ); + }, [places, searchQuery]); + + const groupedByCity = useMemo(() => { + const groups: Record = {}; + filteredPlaces.forEach(place => { + const city = place.city || 'other'; + if (!groups[city]) groups[city] = []; + groups[city].push(place); + }); + return groups; + }, [filteredPlaces]); + + return ( +
+ {/* Header */} +
+

Путешествие

+ + {/* View mode tabs */} +
+ + + +
+
+ + {/* Search input */} + {viewMode === 'search' && ( +
+ setSearchQuery(e.target.value)} + placeholder="Поиск мест..." + className="w-full px-4 py-3 bg-white rounded-xl border border-gray-200 text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#FF6B6B] focus:border-transparent" + autoFocus + /> +
+ )} + + {/* Content */} +
+ {viewMode === 'days' && ( +
+ {days.map((dayData) => ( +
+
+
+ {getCityEmoji(dayData.city)} + Day {dayData.day} +
+
+

{getCityName(dayData.city)}

+

{dayData.title}

+

+ {new Date(dayData.date).toLocaleDateString('ru-RU', { + day: 'numeric', + month: 'short', + weekday: 'short' + })} +

+
+
+
+ ))} +
+ )} + + {viewMode === 'places' && ( +
+ {Object.entries(groupedByCity).map(([city, cityPlaces]) => ( +
+
+ {getCityEmoji(city)} +

{getCityName(city)}

+ ({cityPlaces.length}) +
+
+ {cityPlaces.map((place) => ( + onSelectPlace(place)} + compact + /> + ))} +
+
+ ))} +
+ )} + + {viewMode === 'search' && ( +
+ {filteredPlaces.length > 0 ? ( + filteredPlaces.map((place) => ( + onSelectPlace(place)} + showDay + /> + )) + ) : ( +
+ 🔍 +

Ничего не найдено

+
+ )} +
+ )} +
+
+ ); +} diff --git a/src/pages/PlanPage.tsx b/src/pages/PlanPage.tsx new file mode 100644 index 0000000..f6dbc35 --- /dev/null +++ b/src/pages/PlanPage.tsx @@ -0,0 +1,124 @@ +import { useState, useMemo } from 'react'; +import { Place } from '../types'; +import { TripProgress } from '../components/TripProgress'; +import { DayTimeline } from '../components/DayTimeline'; +import scheduleData from '../data/schedule.json'; + +interface PlanPageProps { + places: Place[]; + onSelectPlace: (place: Place) => void; + onShowDayRoute: (day: number) => void; +} + +const TRIP_START_DATE = '2026-03-03'; +const TOTAL_DAYS = 16; + +export function PlanPage({ places, onSelectPlace, onShowDayRoute }: PlanPageProps) { + const schedule = scheduleData as Record; + + // Calculate current day based on date + const currentDay = useMemo(() => { + const now = new Date(); + // Compare dates at local midnight to avoid timezone issues + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const tripStart = new Date(2026, 2, 3); // March 3, 2026 (month is 0-indexed) + + const diffTime = today.getTime() - tripStart.getTime(); + const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24)) + 1; + + if (diffDays < 1) return 1; // Before trip + if (diffDays > TOTAL_DAYS) return TOTAL_DAYS; // After trip + return diffDays; + }, []); + + const [selectedDay, setSelectedDay] = useState(currentDay); + + const dayKey = `day${selectedDay}`; + const dayData = schedule[dayKey]; + + const isToday = useMemo(() => { + const now = new Date(); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const tripStart = new Date(2026, 2, 3); + const diffTime = today.getTime() - tripStart.getTime(); + const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24)) + 1; + return diffDays === selectedDay; + }, [selectedDay]); + + // Quick day navigation + const days = Array.from({ length: TOTAL_DAYS }, (_, i) => i + 1); + + return ( +
+ {/* Header */} +
+ +
+ + {/* Day selector - horizontal scroll */} +
+
+ {days.map((day) => { + const isSelected = day === selectedDay; + const isCurrent = day === currentDay; + + return ( + + ); + })} +
+
+ + {/* Timeline content */} +
+ {dayData ? ( + + ) : ( +
+ 📅 +

Расписание не найдено

+
+ )} +
+ + {/* Show route button */} + {dayData && ( +
+ +
+ )} +
+ ); +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..ca9ca0f --- /dev/null +++ b/src/types.ts @@ -0,0 +1,74 @@ +export interface Place { + id: number; + name: string; + nameJp: string; + category: "sight" | "restaurant" | "hotel" | "coffee" | "snack"; + city: "tokyo" | "kyoto" | "osaka" | "nara" | "hakone" | "hiroshima" | "miyajima" | "nikko" | "himeji"; + lat: number; + lng: number; + description: string; + address: string; + hours: string; + price: string; + rating: number; + day: number; + links: string[]; + tips?: string; + history?: string; + facts?: string[]; + duration?: string; + photoSpots?: string; + localTips?: string; + bestTime?: string; +} + +export interface GeoPosition { + lat: number; + lng: number; + accuracy?: number; +} + +export type TabType = "map" | "plan" | "places" | "food"; + +export const CITIES: Record = { + tokyo: { name: "Токио", emoji: "🗼" }, + kyoto: { name: "Киото", emoji: "⛩️" }, + osaka: { name: "Осака", emoji: "🏯" }, + nara: { name: "Нара", emoji: "🦌" }, + hakone: { name: "Хаконэ", emoji: "🗻" }, + hiroshima: { name: "Хиросима", emoji: "🕊️" }, + miyajima: { name: "Миядзима", emoji: "⛩️" }, + nikko: { name: "Никко", emoji: "🌲" }, + himeji: { name: "Химэдзи", emoji: "🏰" } +}; + +export const CATEGORIES: Record = { + sight: { name: "Достопримечательности", emoji: "📍" }, + restaurant: { name: "Рестораны", emoji: "🍜" }, + coffee: { name: "Кофе", emoji: "☕" }, + snack: { name: "Перекус", emoji: "🍡" }, + hotel: { name: "Отели", emoji: "🏨" } +}; + +// Trip dates: March 3-18, 2026 +export const TRIP_START = new Date("2026-03-03"); +export const TRIP_END = new Date("2026-03-18"); + +export const SCHEDULE = [ + { day: 1, date: "3 марта", city: "tokyo", title: "Асакуса + Уэно + Скайтри" }, + { day: 2, date: "4 марта", city: "tokyo", title: "Харадзюку → Сибуя → Синдзюку" }, + { day: 3, date: "5 марта", city: "tokyo", title: "Цукидзи → Гиндза → Одайба" }, + { day: 4, date: "6 марта", city: "tokyo", title: "Никко (дневная поездка)" }, + { day: 5, date: "7 марта", city: "hakone", title: "Переезд в Хаконэ" }, + { day: 6, date: "8 марта", city: "hakone", title: "Озеро Аси → Киото" }, + { day: 7, date: "9 марта", city: "kyoto", title: "Восточный Киото" }, + { day: 8, date: "10 марта", city: "kyoto", title: "Арасияма + Северо-запад" }, + { day: 9, date: "11 марта", city: "kyoto", title: "Философский путь" }, + { day: 10, date: "12 марта", city: "nara", title: "Нара (дневная поездка)" }, + { day: 11, date: "13 марта", city: "kyoto", title: "Храмы + Замок Нидзо" }, + { day: 12, date: "14 марта", city: "osaka", title: "Замок + Куромон + Дотонбори" }, + { day: 13, date: "15 марта", city: "osaka", title: "Universal Studios 🎢" }, + { day: 14, date: "16 марта", city: "osaka", title: "Химэдзи + Синсэкай" }, + { day: 15, date: "17 марта", city: "hiroshima", title: "Мемориал мира" }, + { day: 16, date: "18 марта", city: "hiroshima", title: "Миядзима" } +]; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..e847b90 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,37 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + './index.html', + './src/**/*.{js,ts,jsx,tsx}', + ], + theme: { + extend: { + colors: { + accent: '#FF6B6B', + 'accent-light': '#FFE8E8', + }, + fontFamily: { + sans: ['-apple-system', 'BlinkMacSystemFont', 'SF Pro Display', 'Noto Sans JP', 'sans-serif'], + }, + boxShadow: { + 'card': '0 2px 8px rgba(0, 0, 0, 0.08)', + 'card-lg': '0 8px 24px rgba(0, 0, 0, 0.12)', + }, + animation: { + 'fade-in': 'fadeIn 0.3s ease-out', + 'slide-up': 'slideUp 0.3s ease-out', + }, + keyframes: { + fadeIn: { + from: { opacity: '0', transform: 'translateY(10px)' }, + to: { opacity: '1', transform: 'translateY(0)' }, + }, + slideUp: { + from: { transform: 'translateY(100%)' }, + to: { transform: 'translateY(0)' }, + }, + }, + }, + }, + plugins: [], +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3934b8f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..fe5f4fd --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { VitePWA } from "vite-plugin-pwa"; + +export default defineConfig({ + plugins: [ + react(), + VitePWA({ + registerType: "autoUpdate", + includeAssets: ["favicon.ico", "robots.txt", "apple-touch-icon.png"], + manifest: { + name: "Japan Trip Companion", + short_name: "Japan Trip", + description: "Companion app for Japan trip March 3-18, 2026", + theme_color: "#1a1a2e", + background_color: "#1a1a2e", + display: "standalone", + orientation: "portrait", + start_url: "/", + icons: [ + { + src: "icon-192.png", + sizes: "192x192", + type: "image/png" + }, + { + src: "icon-512.png", + sizes: "512x512", + type: "image/png" + } + ] + }, + workbox: { + globPatterns: ["**/*.{js,css,html,ico,png,svg,json}"], + runtimeCaching: [ + { + urlPattern: /^https:\/\/[abc]\.tile\.openstreetmap\.org\/.*/i, + handler: "CacheFirst", + options: { + cacheName: "osm-tiles", + expiration: { + maxEntries: 500, + maxAgeSeconds: 60 * 60 * 24 * 30 + }, + cacheableResponse: { + statuses: [0, 200] + } + } + } + ] + } + }) + ], + server: { + host: true + } +});