From c22aa15eb0c01b4206b3d3c55d1867503a0ca1e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 5 May 2026 16:55:37 +0000 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=E4=B8=96=E7=95=8C=E9=81=BA?= =?UTF-8?q?=E7=94=A3=E3=83=BB=E7=B5=B6=E6=99=AF=E3=83=93=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=82=A2=E3=82=A2=E3=83=97=E3=83=AA=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit YouTube動画のチャプターリスト(93か所)からサイトを選ぶとその映像が埋め込みプレイヤーで再生されるアプリ。サイドバーに全リストを表示し、クリックすると対応する開始秒数からYouTubeが自動再生される。 https://claude.ai/code/session_01G1Ase8ZwqLxkFBW1fBRYjo --- src/app/page.tsx | 4 +- .../components/HeritageViewerScene.tsx | 209 ++++++++++++++++++ src/features/schema/HeritageSite.ts | 103 +++++++++ 3 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 src/features/components/HeritageViewerScene.tsx create mode 100644 src/features/schema/HeritageSite.ts diff --git a/src/app/page.tsx b/src/app/page.tsx index f688279..475eb63 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,5 @@ -import TopScene from "~/features/components/TopScene"; +import HeritageViewerScene from "~/features/components/HeritageViewerScene"; -const PageIndex = () => ; +const PageIndex = () => ; export default PageIndex; diff --git a/src/features/components/HeritageViewerScene.tsx b/src/features/components/HeritageViewerScene.tsx new file mode 100644 index 0000000..55f9be9 --- /dev/null +++ b/src/features/components/HeritageViewerScene.tsx @@ -0,0 +1,209 @@ +"use client"; + +import { useState } from "react"; +import styled from "@emotion/styled"; +import { HERITAGE_SITES, type HeritageSite } from "~/features/schema/HeritageSite"; + +const VIDEO_ID = "3_zB4k1rcVk"; + +const Root = styled.div({ + display: "flex", + height: "100dvh", + background: "#0e0e0e", + color: "#f0f0f0", + fontFamily: "sans-serif", + overflow: "hidden" +}); + +const Sidebar = styled.div({ + width: 280, + flexShrink: 0, + display: "flex", + flexDirection: "column", + borderRight: "1px solid #2a2a2a", + overflow: "hidden" +}); + +const SidebarHeader = styled.div({ + padding: "20px 16px 12px", + borderBottom: "1px solid #2a2a2a", + flexShrink: 0 +}); + +const SidebarTitle = styled.h1({ + fontSize: 14, + fontWeight: 700, + letterSpacing: "0.08em", + color: "#ccc", + margin: 0 +}); + +const SidebarCount = styled.p({ + fontSize: 11, + color: "#666", + margin: "4px 0 0" +}); + +const SiteList = styled.ul({ + listStyle: "none", + margin: 0, + padding: "8px 0", + overflowY: "auto", + flex: 1, + scrollbarWidth: "thin", + scrollbarColor: "#333 transparent" +}); + +const SiteItem = styled.li<{ active: boolean }>(({ active }) => ({ + cursor: "pointer", + padding: "10px 16px", + borderLeft: active ? "3px solid #e0aa3e" : "3px solid transparent", + background: active ? "rgba(224,170,62,0.08)" : "transparent", + transition: "background 0.15s", + "&:hover": { + background: active ? "rgba(224,170,62,0.12)" : "rgba(255,255,255,0.04)" + } +})); + +const SiteNameJa = styled.span<{ active: boolean }>(({ active }) => ({ + display: "block", + fontSize: 13, + fontWeight: active ? 700 : 400, + color: active ? "#e0aa3e" : "#ddd", + lineHeight: 1.4 +})); + +const SiteNameEn = styled.span({ + display: "block", + fontSize: 11, + color: "#666", + marginTop: 2 +}); + +const Main = styled.div({ + flex: 1, + display: "flex", + flexDirection: "column", + overflow: "hidden" +}); + +const PlayerArea = styled.div({ + flex: 1, + position: "relative", + background: "#000" +}); + +const IframeWrapper = styled.div({ + position: "absolute", + inset: 0 +}); + +const StyledIframe = styled.iframe({ + width: "100%", + height: "100%", + border: "none", + display: "block" +}); + +const EmptyState = styled.div({ + position: "absolute", + inset: 0, + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + gap: 12 +}); + +const EmptyIcon = styled.div({ + fontSize: 48, + opacity: 0.3 +}); + +const EmptyText = styled.p({ + fontSize: 14, + color: "#555", + margin: 0 +}); + +const InfoBar = styled.div({ + flexShrink: 0, + padding: "12px 24px", + borderTop: "1px solid #1a1a1a", + background: "#121212", + display: "flex", + alignItems: "baseline", + gap: 12 +}); + +const InfoNameJa = styled.span({ + fontSize: 18, + fontWeight: 700, + color: "#f0f0f0" +}); + +const InfoNameEn = styled.span({ + fontSize: 13, + color: "#888" +}); + +const buildEmbedUrl = (site: HeritageSite) => + `https://www.youtube.com/embed/${VIDEO_ID}?start=${site.startSec}&autoplay=1`; + +const HeritageViewerScene = () => { + const [selected, setSelected] = useState(null); + + return ( + + + + 世界遺産・絶景ビューア + {HERITAGE_SITES.length} か所 + + + {HERITAGE_SITES.map(site => { + const active = selected?.nameEn === site.nameEn; + return ( + setSelected(site)} + > + {site.nameJa} + {site.nameEn} + + ); + })} + + +
+ + {selected ? ( + + + + ) : ( + + 🌍 + 左のリストから場所を選んでください + + )} + + {selected && ( + + {selected.nameJa} + {selected.nameEn} + + )} +
+
+ ); +}; + +export default HeritageViewerScene; diff --git a/src/features/schema/HeritageSite.ts b/src/features/schema/HeritageSite.ts new file mode 100644 index 0000000..21a56d2 --- /dev/null +++ b/src/features/schema/HeritageSite.ts @@ -0,0 +1,103 @@ +export type HeritageSite = { + nameJa: string; + nameEn: string; + startSec: number; +}; + +export const HERITAGE_SITES: HeritageSite[] = [ + { nameJa: "アマルフィ海岸", nameEn: "Amalfi", startSec: 49 }, + { nameJa: "ブラーノ島", nameEn: "Burano", startSec: 111 }, + { nameJa: "シヨン城", nameEn: "Chillon Castle", startSec: 150 }, + { nameJa: "テカポ湖", nameEn: "Lake Tekapo", startSec: 196 }, + { nameJa: "サントリーニ島", nameEn: "Santorini Island", startSec: 236 }, + { nameJa: "ウユニ塩湖", nameEn: "Uyuni Salt Lake", startSec: 301 }, + { nameJa: "シギリヤロック", nameEn: "Sigiriya Rock", startSec: 361 }, + { nameJa: "バオバブ並木", nameEn: "Avenue of the Baobabs", startSec: 415 }, + { nameJa: "十字架の丘", nameEn: "Hill of Crosses", startSec: 457 }, + { nameJa: "ハルシュタット", nameEn: "Hallstatt", startSec: 505 }, + { nameJa: "カッパドキア", nameEn: "Cappadocia", startSec: 550 }, + { nameJa: "イエローストーン", nameEn: "Yellowstone National Park", startSec: 606 }, + { nameJa: "ドゥブロヴニク", nameEn: "Dubrovnik", startSec: 664 }, + { nameJa: "ナヴァイオビーチ", nameEn: "Navagio Beach", startSec: 714 }, + { nameJa: "エアーズロック", nameEn: "Ayers Rock", startSec: 758 }, + { nameJa: "ワイトモ洞窟", nameEn: "Waitomo Caves", startSec: 815 }, + { nameJa: "マリーナベイサンズ", nameEn: "Marina Bay Sands", startSec: 858 }, + { nameJa: "サグラダ・ファミリア", nameEn: "Sagrada Família", startSec: 902 }, + { nameJa: "ロンダ", nameEn: "Ronda", startSec: 968 }, + { nameJa: "コムローイ", nameEn: "Khom Loi", startSec: 1035 }, + { nameJa: "チョコレート・ヒルズ", nameEn: "Chocolate Hills", startSec: 1092 }, + { nameJa: "ノイシュヴァンシュタイン城", nameEn: "Neuschwanstein Castle", startSec: 1129 }, + { nameJa: "アヤソフィア", nameEn: "Hagia Sophia", startSec: 1280 }, + { nameJa: "アルバラシン", nameEn: "Albarracín", startSec: 1319 }, + { nameJa: "ドバイ・フレーム", nameEn: "Dubai Frame", startSec: 1385 }, + { nameJa: "万里の長城", nameEn: "The Great Wall of China", startSec: 1431 }, + { nameJa: "アゲダ", nameEn: "Águeda", startSec: 1488 }, + { nameJa: "アンコール・トム", nameEn: "Angkor Thom", startSec: 1539 }, + { nameJa: "アルベロベッロのトゥルッリ", nameEn: "Trulli di Alberobello", startSec: 1572 }, + { nameJa: "グラナダ", nameEn: "Granada", startSec: 1644 }, + { nameJa: "メテオラ", nameEn: "Meteora", startSec: 1692 }, + { nameJa: "モニュメントバレー", nameEn: "Monument Valley", startSec: 1748 }, + { nameJa: "グアナファト", nameEn: "Guanajuato", startSec: 1797 }, + { nameJa: "メープル街道", nameEn: "Maple Road", startSec: 1862 }, + { nameJa: "エンジェルフォール", nameEn: "Angel Falls", startSec: 1915 }, + { nameJa: "九塞溝", nameEn: "Jiuzhaigou", startSec: 1973 }, + { nameJa: "ペリトモレノ大氷河", nameEn: "Glaciar Perito Moreno", startSec: 2037 }, + { nameJa: "ジャイプール", nameEn: "Jaipur", startSec: 2096 }, + { nameJa: "サハラ砂漠", nameEn: "Sahara Desert", startSec: 2193 }, + { nameJa: "クロンボー城", nameEn: "Kronborg Castle", startSec: 2236 }, + { nameJa: "ブルジュハリファ", nameEn: "Burj Khalifa", startSec: 2262 }, + { nameJa: "紫禁城", nameEn: "Forbidden City", startSec: 2333 }, + { nameJa: "ストックホルム", nameEn: "Stockholm", startSec: 2371 }, + { nameJa: "ラチャダー鉄道市場", nameEn: "Talat Rotfai Ratchada", startSec: 2418 }, + { nameJa: "ギザのピラミッド", nameEn: "Pyramids of Giza", startSec: 2462 }, + { nameJa: "ベルニナ・エクスプレス", nameEn: "Bernina Express", startSec: 2504 }, + { nameJa: "シャンボール城", nameEn: "Chambord", startSec: 2558 }, + { nameJa: "トレド", nameEn: "Toledo", startSec: 2609 }, + { nameJa: "チンクエ・テッレ", nameEn: "Cinque Terre", startSec: 2679 }, + { nameJa: "フリヒリアナ", nameEn: "Frigiliana", startSec: 2738 }, + { nameJa: "エズ", nameEn: "Eze", startSec: 2779 }, + { nameJa: "チャンギ空港", nameEn: "Changi Airport", startSec: 2828 }, + { nameJa: "パムッカレ", nameEn: "Pamukkale", startSec: 2881 }, + { nameJa: "レンソイス・マラニャンセス国立公園", nameEn: "Lencois Maranhenses National Park", startSec: 2938 }, + { nameJa: "ミコノス島", nameEn: "Mykonos Island", startSec: 2980 }, + { nameJa: "コルチュラ島", nameEn: "Korcula Island", startSec: 3037 }, + { nameJa: "プラハ城", nameEn: "Prague Castle", startSec: 3088 }, + { nameJa: "コルマール", nameEn: "Colmar", startSec: 3146 }, + { nameJa: "シンガポール・フライヤー", nameEn: "Singapore Flyer", startSec: 3200 }, + { nameJa: "エディンバラ", nameEn: "Edinburgh", startSec: 3254 }, + { nameJa: "フヴァル島", nameEn: "Hvar Island", startSec: 3300 }, + { nameJa: "グランドキャニオン", nameEn: "Grand Canyon", startSec: 3351 }, + { nameJa: "テオティワカン", nameEn: "Teotihuacan", startSec: 3409 }, + { nameJa: "イエローナイフ", nameEn: "Yellowknife", startSec: 3461 }, + { nameJa: "フスカル", nameEn: "Juzcar", startSec: 3516 }, + { nameJa: "クエンカ", nameEn: "Cuenca", startSec: 3579 }, + { nameJa: "セブンマイルブリッジ", nameEn: "Seven Mile Bridge", startSec: 3644 }, + { nameJa: "サルベーションマウンテン", nameEn: "Salvation Mountain", startSec: 3695 }, + { nameJa: "モレーン湖", nameEn: "Moraine Lake", startSec: 3749 }, + { nameJa: "マチュピチュ", nameEn: "Machu Picchu", startSec: 3803 }, + { nameJa: "ヴィレムスタット歴史地区", nameEn: "Willemstad", startSec: 3872 }, + { nameJa: "ジョードプル", nameEn: "Jodhpur", startSec: 3929 }, + { nameJa: "アンコールワット", nameEn: "Angkor Wat", startSec: 3975 }, + { nameJa: "シティブサイド", nameEn: "Sidi Bou Said", startSec: 4028 }, + { nameJa: "ポタラ宮", nameEn: "Potala Palace", startSec: 4080 }, + { nameJa: "コペンハーゲン", nameEn: "Copenhagen", startSec: 4188 }, + { nameJa: "ツェルマット", nameEn: "Zermatt", startSec: 4240 }, + { nameJa: "ザ・パール", nameEn: "The Pearl", startSec: 4285 }, + { nameJa: "オスロブ", nameEn: "Oslob", startSec: 4332 }, + { nameJa: "モンスターマンション", nameEn: "Yick Fat Building (Monster Mansion)", startSec: 4382 }, + { nameJa: "シチリア島", nameEn: "Sicily", startSec: 4433 }, + { nameJa: "シャウエン", nameEn: "Chefchaouen", startSec: 4490 }, + { nameJa: "九份", nameEn: "Jiufen", startSec: 4594 }, + { nameJa: "ジャイサルメール", nameEn: "Jaisalmer", startSec: 4646 }, + { nameJa: "ナイアガラの滝", nameEn: "Niagara Falls", startSec: 4701 }, + { nameJa: "アンテロープキャニオン", nameEn: "Antelope Canyon", startSec: 4755 }, + { nameJa: "レドニツェ城", nameEn: "Lednice Chateau", startSec: 4806 }, + { nameJa: "ガーデンズバイザベイ", nameEn: "Gardens by the Bay", startSec: 4868 }, + { nameJa: "スルタンアフメット・ジャミィ", nameEn: "Blue Mosque", startSec: 4921 }, + { nameJa: "チェスキークルムロフ", nameEn: "Cesky Krumlov", startSec: 4976 }, + { nameJa: "クレタ島", nameEn: "Crete Island", startSec: 5025 }, + { nameJa: "マラスの塩田", nameEn: "Salt Mines of Maras", startSec: 5071 }, + { nameJa: "キンデルダイク", nameEn: "Kinderdijk", startSec: 5133 }, + { nameJa: "ポルト", nameEn: "Porto", startSec: 5180 }, + { nameJa: "モトブン", nameEn: "Motovun", startSec: 5225 } +]; From 3c6d3abfc5ebd84a83e57e91ff91133af4a411a2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 5 May 2026 17:04:41 +0000 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=E5=85=A895=E3=81=8B=E6=89=80?= =?UTF-8?q?=E3=81=AE=E7=B7=AF=E5=BA=A6=E7=B5=8C=E5=BA=A6=E3=83=87=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HeritageSiteスキーマにlat/lngフィールドを追加し、全件に座標を付与。 https://claude.ai/code/session_01G1Ase8ZwqLxkFBW1fBRYjo --- src/features/schema/HeritageSite.ts | 192 ++++++++++++++-------------- 1 file changed, 97 insertions(+), 95 deletions(-) diff --git a/src/features/schema/HeritageSite.ts b/src/features/schema/HeritageSite.ts index 21a56d2..3a03d80 100644 --- a/src/features/schema/HeritageSite.ts +++ b/src/features/schema/HeritageSite.ts @@ -2,102 +2,104 @@ export type HeritageSite = { nameJa: string; nameEn: string; startSec: number; + lat: number; + lng: number; }; export const HERITAGE_SITES: HeritageSite[] = [ - { nameJa: "アマルフィ海岸", nameEn: "Amalfi", startSec: 49 }, - { nameJa: "ブラーノ島", nameEn: "Burano", startSec: 111 }, - { nameJa: "シヨン城", nameEn: "Chillon Castle", startSec: 150 }, - { nameJa: "テカポ湖", nameEn: "Lake Tekapo", startSec: 196 }, - { nameJa: "サントリーニ島", nameEn: "Santorini Island", startSec: 236 }, - { nameJa: "ウユニ塩湖", nameEn: "Uyuni Salt Lake", startSec: 301 }, - { nameJa: "シギリヤロック", nameEn: "Sigiriya Rock", startSec: 361 }, - { nameJa: "バオバブ並木", nameEn: "Avenue of the Baobabs", startSec: 415 }, - { nameJa: "十字架の丘", nameEn: "Hill of Crosses", startSec: 457 }, - { nameJa: "ハルシュタット", nameEn: "Hallstatt", startSec: 505 }, - { nameJa: "カッパドキア", nameEn: "Cappadocia", startSec: 550 }, - { nameJa: "イエローストーン", nameEn: "Yellowstone National Park", startSec: 606 }, - { nameJa: "ドゥブロヴニク", nameEn: "Dubrovnik", startSec: 664 }, - { nameJa: "ナヴァイオビーチ", nameEn: "Navagio Beach", startSec: 714 }, - { nameJa: "エアーズロック", nameEn: "Ayers Rock", startSec: 758 }, - { nameJa: "ワイトモ洞窟", nameEn: "Waitomo Caves", startSec: 815 }, - { nameJa: "マリーナベイサンズ", nameEn: "Marina Bay Sands", startSec: 858 }, - { nameJa: "サグラダ・ファミリア", nameEn: "Sagrada Família", startSec: 902 }, - { nameJa: "ロンダ", nameEn: "Ronda", startSec: 968 }, - { nameJa: "コムローイ", nameEn: "Khom Loi", startSec: 1035 }, - { nameJa: "チョコレート・ヒルズ", nameEn: "Chocolate Hills", startSec: 1092 }, - { nameJa: "ノイシュヴァンシュタイン城", nameEn: "Neuschwanstein Castle", startSec: 1129 }, - { nameJa: "アヤソフィア", nameEn: "Hagia Sophia", startSec: 1280 }, - { nameJa: "アルバラシン", nameEn: "Albarracín", startSec: 1319 }, - { nameJa: "ドバイ・フレーム", nameEn: "Dubai Frame", startSec: 1385 }, - { nameJa: "万里の長城", nameEn: "The Great Wall of China", startSec: 1431 }, - { nameJa: "アゲダ", nameEn: "Águeda", startSec: 1488 }, - { nameJa: "アンコール・トム", nameEn: "Angkor Thom", startSec: 1539 }, - { nameJa: "アルベロベッロのトゥルッリ", nameEn: "Trulli di Alberobello", startSec: 1572 }, - { nameJa: "グラナダ", nameEn: "Granada", startSec: 1644 }, - { nameJa: "メテオラ", nameEn: "Meteora", startSec: 1692 }, - { nameJa: "モニュメントバレー", nameEn: "Monument Valley", startSec: 1748 }, - { nameJa: "グアナファト", nameEn: "Guanajuato", startSec: 1797 }, - { nameJa: "メープル街道", nameEn: "Maple Road", startSec: 1862 }, - { nameJa: "エンジェルフォール", nameEn: "Angel Falls", startSec: 1915 }, - { nameJa: "九塞溝", nameEn: "Jiuzhaigou", startSec: 1973 }, - { nameJa: "ペリトモレノ大氷河", nameEn: "Glaciar Perito Moreno", startSec: 2037 }, - { nameJa: "ジャイプール", nameEn: "Jaipur", startSec: 2096 }, - { nameJa: "サハラ砂漠", nameEn: "Sahara Desert", startSec: 2193 }, - { nameJa: "クロンボー城", nameEn: "Kronborg Castle", startSec: 2236 }, - { nameJa: "ブルジュハリファ", nameEn: "Burj Khalifa", startSec: 2262 }, - { nameJa: "紫禁城", nameEn: "Forbidden City", startSec: 2333 }, - { nameJa: "ストックホルム", nameEn: "Stockholm", startSec: 2371 }, - { nameJa: "ラチャダー鉄道市場", nameEn: "Talat Rotfai Ratchada", startSec: 2418 }, - { nameJa: "ギザのピラミッド", nameEn: "Pyramids of Giza", startSec: 2462 }, - { nameJa: "ベルニナ・エクスプレス", nameEn: "Bernina Express", startSec: 2504 }, - { nameJa: "シャンボール城", nameEn: "Chambord", startSec: 2558 }, - { nameJa: "トレド", nameEn: "Toledo", startSec: 2609 }, - { nameJa: "チンクエ・テッレ", nameEn: "Cinque Terre", startSec: 2679 }, - { nameJa: "フリヒリアナ", nameEn: "Frigiliana", startSec: 2738 }, - { nameJa: "エズ", nameEn: "Eze", startSec: 2779 }, - { nameJa: "チャンギ空港", nameEn: "Changi Airport", startSec: 2828 }, - { nameJa: "パムッカレ", nameEn: "Pamukkale", startSec: 2881 }, - { nameJa: "レンソイス・マラニャンセス国立公園", nameEn: "Lencois Maranhenses National Park", startSec: 2938 }, - { nameJa: "ミコノス島", nameEn: "Mykonos Island", startSec: 2980 }, - { nameJa: "コルチュラ島", nameEn: "Korcula Island", startSec: 3037 }, - { nameJa: "プラハ城", nameEn: "Prague Castle", startSec: 3088 }, - { nameJa: "コルマール", nameEn: "Colmar", startSec: 3146 }, - { nameJa: "シンガポール・フライヤー", nameEn: "Singapore Flyer", startSec: 3200 }, - { nameJa: "エディンバラ", nameEn: "Edinburgh", startSec: 3254 }, - { nameJa: "フヴァル島", nameEn: "Hvar Island", startSec: 3300 }, - { nameJa: "グランドキャニオン", nameEn: "Grand Canyon", startSec: 3351 }, - { nameJa: "テオティワカン", nameEn: "Teotihuacan", startSec: 3409 }, - { nameJa: "イエローナイフ", nameEn: "Yellowknife", startSec: 3461 }, - { nameJa: "フスカル", nameEn: "Juzcar", startSec: 3516 }, - { nameJa: "クエンカ", nameEn: "Cuenca", startSec: 3579 }, - { nameJa: "セブンマイルブリッジ", nameEn: "Seven Mile Bridge", startSec: 3644 }, - { nameJa: "サルベーションマウンテン", nameEn: "Salvation Mountain", startSec: 3695 }, - { nameJa: "モレーン湖", nameEn: "Moraine Lake", startSec: 3749 }, - { nameJa: "マチュピチュ", nameEn: "Machu Picchu", startSec: 3803 }, - { nameJa: "ヴィレムスタット歴史地区", nameEn: "Willemstad", startSec: 3872 }, - { nameJa: "ジョードプル", nameEn: "Jodhpur", startSec: 3929 }, - { nameJa: "アンコールワット", nameEn: "Angkor Wat", startSec: 3975 }, - { nameJa: "シティブサイド", nameEn: "Sidi Bou Said", startSec: 4028 }, - { nameJa: "ポタラ宮", nameEn: "Potala Palace", startSec: 4080 }, - { nameJa: "コペンハーゲン", nameEn: "Copenhagen", startSec: 4188 }, - { nameJa: "ツェルマット", nameEn: "Zermatt", startSec: 4240 }, - { nameJa: "ザ・パール", nameEn: "The Pearl", startSec: 4285 }, - { nameJa: "オスロブ", nameEn: "Oslob", startSec: 4332 }, - { nameJa: "モンスターマンション", nameEn: "Yick Fat Building (Monster Mansion)", startSec: 4382 }, - { nameJa: "シチリア島", nameEn: "Sicily", startSec: 4433 }, - { nameJa: "シャウエン", nameEn: "Chefchaouen", startSec: 4490 }, - { nameJa: "九份", nameEn: "Jiufen", startSec: 4594 }, - { nameJa: "ジャイサルメール", nameEn: "Jaisalmer", startSec: 4646 }, - { nameJa: "ナイアガラの滝", nameEn: "Niagara Falls", startSec: 4701 }, - { nameJa: "アンテロープキャニオン", nameEn: "Antelope Canyon", startSec: 4755 }, - { nameJa: "レドニツェ城", nameEn: "Lednice Chateau", startSec: 4806 }, - { nameJa: "ガーデンズバイザベイ", nameEn: "Gardens by the Bay", startSec: 4868 }, - { nameJa: "スルタンアフメット・ジャミィ", nameEn: "Blue Mosque", startSec: 4921 }, - { nameJa: "チェスキークルムロフ", nameEn: "Cesky Krumlov", startSec: 4976 }, - { nameJa: "クレタ島", nameEn: "Crete Island", startSec: 5025 }, - { nameJa: "マラスの塩田", nameEn: "Salt Mines of Maras", startSec: 5071 }, - { nameJa: "キンデルダイク", nameEn: "Kinderdijk", startSec: 5133 }, - { nameJa: "ポルト", nameEn: "Porto", startSec: 5180 }, - { nameJa: "モトブン", nameEn: "Motovun", startSec: 5225 } + { nameJa: "アマルフィ海岸", nameEn: "Amalfi", startSec: 49, lat: 40.6340, lng: 14.6027 }, + { nameJa: "ブラーノ島", nameEn: "Burano", startSec: 111, lat: 45.4853, lng: 12.4167 }, + { nameJa: "シヨン城", nameEn: "Chillon Castle", startSec: 150, lat: 46.4142, lng: 6.9278 }, + { nameJa: "テカポ湖", nameEn: "Lake Tekapo", startSec: 196, lat: -43.8864, lng: 170.5284 }, + { nameJa: "サントリーニ島", nameEn: "Santorini Island", startSec: 236, lat: 36.3932, lng: 25.4615 }, + { nameJa: "ウユニ塩湖", nameEn: "Uyuni Salt Lake", startSec: 301, lat: -20.1338, lng: -67.4891 }, + { nameJa: "シギリヤロック", nameEn: "Sigiriya Rock", startSec: 361, lat: 7.9570, lng: 80.7603 }, + { nameJa: "バオバブ並木", nameEn: "Avenue of the Baobabs", startSec: 415, lat: -20.2506, lng: 44.4197 }, + { nameJa: "十字架の丘", nameEn: "Hill of Crosses", startSec: 457, lat: 56.0153, lng: 23.4169 }, + { nameJa: "ハルシュタット", nameEn: "Hallstatt", startSec: 505, lat: 47.5622, lng: 13.6493 }, + { nameJa: "カッパドキア", nameEn: "Cappadocia", startSec: 550, lat: 38.6431, lng: 34.8289 }, + { nameJa: "イエローストーン", nameEn: "Yellowstone National Park", startSec: 606, lat: 44.4280, lng: -110.5885 }, + { nameJa: "ドゥブロヴニク", nameEn: "Dubrovnik", startSec: 664, lat: 42.6507, lng: 18.0944 }, + { nameJa: "ナヴァイオビーチ", nameEn: "Navagio Beach", startSec: 714, lat: 37.8594, lng: 20.6243 }, + { nameJa: "エアーズロック", nameEn: "Ayers Rock", startSec: 758, lat: -25.3444, lng: 131.0369 }, + { nameJa: "ワイトモ洞窟", nameEn: "Waitomo Caves", startSec: 815, lat: -38.2626, lng: 175.1046 }, + { nameJa: "マリーナベイサンズ", nameEn: "Marina Bay Sands", startSec: 858, lat: 1.2838, lng: 103.8607 }, + { nameJa: "サグラダ・ファミリア", nameEn: "Sagrada Família", startSec: 902, lat: 41.4036, lng: 2.1744 }, + { nameJa: "ロンダ", nameEn: "Ronda", startSec: 968, lat: 36.7468, lng: -5.1641 }, + { nameJa: "コムローイ", nameEn: "Khom Loi", startSec: 1035, lat: 18.7883, lng: 98.9853 }, + { nameJa: "チョコレート・ヒルズ", nameEn: "Chocolate Hills", startSec: 1092, lat: 9.9000, lng: 124.1736 }, + { nameJa: "ノイシュヴァンシュタイン城", nameEn: "Neuschwanstein Castle", startSec: 1129, lat: 47.5576, lng: 10.7498 }, + { nameJa: "アヤソフィア", nameEn: "Hagia Sophia", startSec: 1280, lat: 41.0086, lng: 28.9802 }, + { nameJa: "アルバラシン", nameEn: "Albarracín", startSec: 1319, lat: 40.4097, lng: -1.4417 }, + { nameJa: "ドバイ・フレーム", nameEn: "Dubai Frame", startSec: 1385, lat: 25.2350, lng: 55.3006 }, + { nameJa: "万里の長城", nameEn: "The Great Wall of China", startSec: 1431, lat: 40.4319, lng: 116.5704 }, + { nameJa: "アゲダ", nameEn: "Águeda", startSec: 1488, lat: 40.5752, lng: -8.4425 }, + { nameJa: "アンコール・トム", nameEn: "Angkor Thom", startSec: 1539, lat: 13.4411, lng: 103.8585 }, + { nameJa: "アルベロベッロのトゥルッリ", nameEn: "Trulli di Alberobello", startSec: 1572, lat: 40.7852, lng: 17.2396 }, + { nameJa: "グラナダ", nameEn: "Granada", startSec: 1644, lat: 37.1773, lng: -3.5986 }, + { nameJa: "メテオラ", nameEn: "Meteora", startSec: 1692, lat: 39.7217, lng: 21.6306 }, + { nameJa: "モニュメントバレー", nameEn: "Monument Valley", startSec: 1748, lat: 36.9983, lng: -110.0985 }, + { nameJa: "グアナファト", nameEn: "Guanajuato", startSec: 1797, lat: 21.0190, lng: -101.2574 }, + { nameJa: "メープル街道", nameEn: "Maple Road", startSec: 1862, lat: 45.4215, lng: -75.6972 }, + { nameJa: "エンジェルフォール", nameEn: "Angel Falls", startSec: 1915, lat: 5.9701, lng: -62.5362 }, + { nameJa: "九塞溝", nameEn: "Jiuzhaigou", startSec: 1973, lat: 33.2600, lng: 103.9170 }, + { nameJa: "ペリトモレノ大氷河", nameEn: "Glaciar Perito Moreno", startSec: 2037, lat: -50.4968, lng: -73.1286 }, + { nameJa: "ジャイプール", nameEn: "Jaipur", startSec: 2096, lat: 26.9124, lng: 75.7873 }, + { nameJa: "サハラ砂漠", nameEn: "Sahara Desert", startSec: 2193, lat: 23.4162, lng: 25.6628 }, + { nameJa: "クロンボー城", nameEn: "Kronborg Castle", startSec: 2236, lat: 56.0386, lng: 12.6215 }, + { nameJa: "ブルジュハリファ", nameEn: "Burj Khalifa", startSec: 2262, lat: 25.1972, lng: 55.2744 }, + { nameJa: "紫禁城", nameEn: "Forbidden City", startSec: 2333, lat: 39.9163, lng: 116.3972 }, + { nameJa: "ストックホルム", nameEn: "Stockholm", startSec: 2371, lat: 59.3293, lng: 18.0686 }, + { nameJa: "ラチャダー鉄道市場", nameEn: "Talat Rotfai Ratchada", startSec: 2418, lat: 13.7649, lng: 100.5601 }, + { nameJa: "ギザのピラミッド", nameEn: "Pyramids of Giza", startSec: 2462, lat: 29.9792, lng: 31.1342 }, + { nameJa: "ベルニナ・エクスプレス", nameEn: "Bernina Express", startSec: 2504, lat: 46.2142, lng: 10.1614 }, + { nameJa: "シャンボール城", nameEn: "Chambord", startSec: 2558, lat: 47.6161, lng: 1.5171 }, + { nameJa: "トレド", nameEn: "Toledo", startSec: 2609, lat: 39.8628, lng: -4.0273 }, + { nameJa: "チンクエ・テッレ", nameEn: "Cinque Terre", startSec: 2679, lat: 44.1461, lng: 9.6439 }, + { nameJa: "フリヒリアナ", nameEn: "Frigiliana", startSec: 2738, lat: 36.7927, lng: -3.8967 }, + { nameJa: "エズ", nameEn: "Eze", startSec: 2779, lat: 43.7272, lng: 7.3601 }, + { nameJa: "チャンギ空港", nameEn: "Changi Airport", startSec: 2828, lat: 1.3644, lng: 103.9915 }, + { nameJa: "パムッカレ", nameEn: "Pamukkale", startSec: 2881, lat: 37.9137, lng: 29.1187 }, + { nameJa: "レンソイス・マラニャンセス国立公園", nameEn: "Lencois Maranhenses National Park", startSec: 2938, lat: -2.5242, lng: -43.1249 }, + { nameJa: "ミコノス島", nameEn: "Mykonos Island", startSec: 2980, lat: 37.4467, lng: 25.3289 }, + { nameJa: "コルチュラ島", nameEn: "Korcula Island", startSec: 3037, lat: 42.9600, lng: 17.1347 }, + { nameJa: "プラハ城", nameEn: "Prague Castle", startSec: 3088, lat: 50.0911, lng: 14.4003 }, + { nameJa: "コルマール", nameEn: "Colmar", startSec: 3146, lat: 48.0793, lng: 7.3585 }, + { nameJa: "シンガポール・フライヤー", nameEn: "Singapore Flyer", startSec: 3200, lat: 1.2893, lng: 103.8631 }, + { nameJa: "エディンバラ", nameEn: "Edinburgh", startSec: 3254, lat: 55.9533, lng: -3.1883 }, + { nameJa: "フヴァル島", nameEn: "Hvar Island", startSec: 3300, lat: 43.1729, lng: 16.4412 }, + { nameJa: "グランドキャニオン", nameEn: "Grand Canyon", startSec: 3351, lat: 36.1069, lng: -112.1129 }, + { nameJa: "テオティワカン", nameEn: "Teotihuacan", startSec: 3409, lat: 19.6925, lng: -98.8438 }, + { nameJa: "イエローナイフ", nameEn: "Yellowknife", startSec: 3461, lat: 62.4540, lng: -114.3718 }, + { nameJa: "フスカル", nameEn: "Juzcar", startSec: 3516, lat: 36.5950, lng: -5.1739 }, + { nameJa: "クエンカ", nameEn: "Cuenca", startSec: 3579, lat: 40.0704, lng: -2.1374 }, + { nameJa: "セブンマイルブリッジ", nameEn: "Seven Mile Bridge", startSec: 3644, lat: 24.7014, lng: -81.1247 }, + { nameJa: "サルベーションマウンテン", nameEn: "Salvation Mountain", startSec: 3695, lat: 33.2561, lng: -115.4683 }, + { nameJa: "モレーン湖", nameEn: "Moraine Lake", startSec: 3749, lat: 51.3217, lng: -116.1860 }, + { nameJa: "マチュピチュ", nameEn: "Machu Picchu", startSec: 3803, lat: -13.1631, lng: -72.5450 }, + { nameJa: "ヴィレムスタット歴史地区", nameEn: "Willemstad", startSec: 3872, lat: 12.1084, lng: -68.9335 }, + { nameJa: "ジョードプル", nameEn: "Jodhpur", startSec: 3929, lat: 26.2389, lng: 73.0243 }, + { nameJa: "アンコールワット", nameEn: "Angkor Wat", startSec: 3975, lat: 13.4125, lng: 103.8670 }, + { nameJa: "シティブサイド", nameEn: "Sidi Bou Said", startSec: 4028, lat: 36.8706, lng: 10.3417 }, + { nameJa: "ポタラ宮", nameEn: "Potala Palace", startSec: 4080, lat: 29.6575, lng: 91.1175 }, + { nameJa: "コペンハーゲン", nameEn: "Copenhagen", startSec: 4188, lat: 55.6761, lng: 12.5683 }, + { nameJa: "ツェルマット", nameEn: "Zermatt", startSec: 4240, lat: 46.0207, lng: 7.7491 }, + { nameJa: "ザ・パール", nameEn: "The Pearl", startSec: 4285, lat: 25.3694, lng: 51.5500 }, + { nameJa: "オスロブ", nameEn: "Oslob", startSec: 4332, lat: 9.5236, lng: 123.3898 }, + { nameJa: "モンスターマンション", nameEn: "Yick Fat Building (Monster Mansion)", startSec: 4382, lat: 22.2807, lng: 114.1604 }, + { nameJa: "シチリア島", nameEn: "Sicily", startSec: 4433, lat: 37.5999, lng: 14.0154 }, + { nameJa: "シャウエン", nameEn: "Chefchaouen", startSec: 4490, lat: 35.1688, lng: -5.2687 }, + { nameJa: "九份", nameEn: "Jiufen", startSec: 4594, lat: 25.1089, lng: 121.8442 }, + { nameJa: "ジャイサルメール", nameEn: "Jaisalmer", startSec: 4646, lat: 26.9157, lng: 70.9083 }, + { nameJa: "ナイアガラの滝", nameEn: "Niagara Falls", startSec: 4701, lat: 43.0962, lng: -79.0377 }, + { nameJa: "アンテロープキャニオン", nameEn: "Antelope Canyon", startSec: 4755, lat: 36.8619, lng: -111.3743 }, + { nameJa: "レドニツェ城", nameEn: "Lednice Chateau", startSec: 4806, lat: 48.8003, lng: 16.8029 }, + { nameJa: "ガーデンズバイザベイ", nameEn: "Gardens by the Bay", startSec: 4868, lat: 1.2816, lng: 103.8636 }, + { nameJa: "スルタンアフメット・ジャミィ", nameEn: "Blue Mosque", startSec: 4921, lat: 41.0054, lng: 28.9768 }, + { nameJa: "チェスキークルムロフ", nameEn: "Cesky Krumlov", startSec: 4976, lat: 48.8127, lng: 14.3175 }, + { nameJa: "クレタ島", nameEn: "Crete Island", startSec: 5025, lat: 35.2401, lng: 24.8093 }, + { nameJa: "マラスの塩田", nameEn: "Salt Mines of Maras", startSec: 5071, lat: -13.3343, lng: -72.1501 }, + { nameJa: "キンデルダイク", nameEn: "Kinderdijk", startSec: 5133, lat: 51.8833, lng: 4.6333 }, + { nameJa: "ポルト", nameEn: "Porto", startSec: 5180, lat: 41.1579, lng: -8.6291 }, + { nameJa: "モトブン", nameEn: "Motovun", startSec: 5225, lat: 45.3369, lng: 13.8298 } ]; From a19b0380f920dbc924a7213f635eea83791bf54b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 5 May 2026 17:12:53 +0000 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20Google=20Maps=E5=9C=B0=E5=9B=B3?= =?UTF-8?q?=E3=83=93=E3=83=A5=E3=83=BC=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 初期表示を世界地図に変更。全スポットに金色ピンを配置し、 ピンクリックでInfoWindowが開き「動画を見る」ボタンから 右パネルに動画を表示する。サイドバーリストクリックで 地図をズームインしInfoWindowを自動表示。 https://claude.ai/code/session_01G1Ase8ZwqLxkFBW1fBRYjo --- package.json | 2 + pnpm-lock.yaml | 75 +++++++ src/features/components/HeritageMap.tsx | 189 ++++++++++++++++++ .../components/HeritageViewerScene.tsx | 187 +++++++++-------- 4 files changed, 357 insertions(+), 96 deletions(-) create mode 100644 src/features/components/HeritageMap.tsx diff --git a/package.json b/package.json index 3cc05b6..c7abeb0 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", + "@types/google.maps": "^3.64.0", "@types/jest": "^30.0.0", "@types/node": "^17.0.21", "@types/react": "^19.2.14", @@ -50,6 +51,7 @@ "@emotion/react": "^11.11.1", "@emotion/serialize": "^1.1.2", "@emotion/styled": "^11.11.0", + "@react-google-maps/api": "^2.20.8", "css-transform-builder": "^1.1.2", "emotion-reset": "^3.0.1", "next": "^16.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8f6011..b443fea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@emotion/styled': specifier: ^11.11.0 version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5) + '@react-google-maps/api': + specifier: ^2.20.8 + version: 2.20.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5) css-transform-builder: specifier: ^1.1.2 version: 1.3.0 @@ -51,6 +54,9 @@ importers: '@testing-library/react': specifier: ^16.3.2 version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@types/google.maps': + specifier: ^3.64.0 + version: 3.64.0 '@types/jest': specifier: ^30.0.0 version: 30.0.0 @@ -598,6 +604,12 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@googlemaps/js-api-loader@1.16.8': + resolution: {integrity: sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ==} + + '@googlemaps/markerclusterer@2.5.3': + resolution: {integrity: sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==} + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -979,6 +991,18 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@react-google-maps/api@2.20.8': + resolution: {integrity: sha512-wtLYFtCGXK3qbIz1H5to3JxbosPnKsvjDKhqGylXUb859EskhzR7OpuNt0LqdLarXUtZCJTKzPn3BNaekNIahg==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 + react-dom: ^16.8 || ^17 || ^18 || ^19 + + '@react-google-maps/infobox@2.20.0': + resolution: {integrity: sha512-03PJHjohhaVLkX6+NHhlr8CIlvUxWaXhryqDjyaZ8iIqqix/nV8GFdz9O3m5OsjtxtNho09F/15j14yV0nuyLQ==} + + '@react-google-maps/marker-clusterer@2.20.0': + resolution: {integrity: sha512-tieX9Va5w1yP88vMgfH1pHTacDQ9TgDTjox3tLlisKDXRQWdjw+QeVVghhf5XqqIxXHgPdcGwBvKY6UP+SIvLw==} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1047,6 +1071,12 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/google.maps@3.58.1': + resolution: {integrity: sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==} + + '@types/google.maps@3.64.0': + resolution: {integrity: sha512-dN0H6tB4lgLQLovcbPXFYYOEV41TpyyJghzb5jrzjB96FZmjeOghevVdC+BMGd6YqyCqXaggyEtqRXLRjzCBZA==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -2427,6 +2457,9 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -2778,6 +2811,9 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + kdbush@4.0.2: + resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -3467,6 +3503,9 @@ packages: stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + supercluster@8.0.1: + resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4303,6 +4342,13 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@googlemaps/js-api-loader@1.16.8': {} + + '@googlemaps/markerclusterer@2.5.3': + dependencies: + fast-deep-equal: 3.1.3 + supercluster: 8.0.1 + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -4703,6 +4749,21 @@ snapshots: '@pkgr/core@0.2.9': {} + '@react-google-maps/api@2.20.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@googlemaps/js-api-loader': 1.16.8 + '@googlemaps/markerclusterer': 2.5.3 + '@react-google-maps/infobox': 2.20.0 + '@react-google-maps/marker-clusterer': 2.20.0 + '@types/google.maps': 3.58.1 + invariant: 2.2.4 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@react-google-maps/infobox@2.20.0': {} + + '@react-google-maps/marker-clusterer@2.20.0': {} + '@rtsao/scc@1.1.0': {} '@sinclair/typebox@0.34.49': {} @@ -4796,6 +4857,10 @@ snapshots: '@types/estree@1.0.8': {} + '@types/google.maps@3.58.1': {} + + '@types/google.maps@3.64.0': {} + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -6401,6 +6466,10 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -6964,6 +7033,8 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + kdbush@4.0.2: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -7718,6 +7789,10 @@ snapshots: stylis@4.2.0: {} + supercluster@8.0.1: + dependencies: + kdbush: 4.0.2 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 diff --git a/src/features/components/HeritageMap.tsx b/src/features/components/HeritageMap.tsx new file mode 100644 index 0000000..fea49b4 --- /dev/null +++ b/src/features/components/HeritageMap.tsx @@ -0,0 +1,189 @@ +"use client"; + +import { useState, useCallback, useEffect, useRef } from "react"; +import { useJsApiLoader, GoogleMap, Marker, InfoWindow } from "@react-google-maps/api"; +import styled from "@emotion/styled"; +import { HERITAGE_SITES, type HeritageSite } from "~/features/schema/HeritageSite"; + +const API_KEY = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY ?? ""; + +const CONTAINER_STYLE = { width: "100%", height: "100%" }; +const DEFAULT_CENTER = { lat: 20, lng: 10 }; + +const NIGHT_STYLE: google.maps.MapTypeStyle[] = [ + { elementType: "geometry", stylers: [{ color: "#1a1a2e" }] }, + { elementType: "labels.text.stroke", stylers: [{ color: "#1a1a2e" }] }, + { elementType: "labels.text.fill", stylers: [{ color: "#8a9bb5" }] }, + { + featureType: "administrative", + elementType: "geometry", + stylers: [{ color: "#2d3561" }] + }, + { + featureType: "administrative.country", + elementType: "labels.text.fill", + stylers: [{ color: "#9e9e9e" }] + }, + { featureType: "landscape", stylers: [{ color: "#0e1628" }] }, + { featureType: "poi", stylers: [{ visibility: "off" }] }, + { featureType: "road", stylers: [{ visibility: "off" }] }, + { featureType: "transit", stylers: [{ visibility: "off" }] }, + { + featureType: "water", + elementType: "geometry", + stylers: [{ color: "#0e2a40" }] + }, + { + featureType: "water", + elementType: "labels.text.fill", + stylers: [{ color: "#4e6d8c" }] + } +]; + +// google.maps.SymbolPath.CIRCLE = 0 +const MARKER_ICON: google.maps.Symbol = { + path: 0 as google.maps.SymbolPath, + fillColor: "#e0aa3e", + fillOpacity: 1, + strokeColor: "#111", + strokeWeight: 1.5, + scale: 6 +}; + +const Overlay = styled.div({ + width: "100%", + height: "100%", + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + gap: 8, + background: "#0a0a0a", + color: "#666", + fontSize: 14 +}); + +export type FocusRequest = { site: HeritageSite; id: number }; + +type Props = { + focusRequest: FocusRequest | null; + onPlayVideo: (site: HeritageSite) => void; +}; + +// InfoWindow内はGoogle Mapsの独自DOMに挿入されるためインラインスタイルを使う +const InfoBox = ({ + site, + onPlay +}: { + site: HeritageSite; + onPlay: () => void; +}) => ( +
+

+ {site.nameJa} +

+

+ {site.nameEn} +

+ +
+); + +const HeritageMap = ({ focusRequest, onPlayVideo }: Props) => { + const { isLoaded } = useJsApiLoader({ googleMapsApiKey: API_KEY }); + const mapRef = useRef(null); + const [infoSite, setInfoSite] = useState(null); + + const onMapLoad = useCallback((map: google.maps.Map) => { + mapRef.current = map; + }, []); + + useEffect(() => { + if (!focusRequest || !mapRef.current) return; + mapRef.current.panTo({ lat: focusRequest.site.lat, lng: focusRequest.site.lng }); + mapRef.current.setZoom(7); + setInfoSite(focusRequest.site); + }, [focusRequest]); + + if (!API_KEY) { + return ( + + 🗺️ + 地図を表示するには + + NEXT_PUBLIC_GOOGLE_MAPS_API_KEY + + を .env.local に設定してください + + ); + } + + if (!isLoaded) { + return 地図を読み込み中...; + } + + return ( + + {HERITAGE_SITES.map(site => ( + setInfoSite(site)} + /> + ))} + {infoSite && ( + setInfoSite(null)} + > + { + onPlayVideo(infoSite); + setInfoSite(null); + }} + /> + + )} + + ); +}; + +export default HeritageMap; diff --git a/src/features/components/HeritageViewerScene.tsx b/src/features/components/HeritageViewerScene.tsx index 55f9be9..a208f04 100644 --- a/src/features/components/HeritageViewerScene.tsx +++ b/src/features/components/HeritageViewerScene.tsx @@ -1,8 +1,9 @@ "use client"; -import { useState } from "react"; +import { useState, useCallback } from "react"; import styled from "@emotion/styled"; import { HERITAGE_SITES, type HeritageSite } from "~/features/schema/HeritageSite"; +import HeritageMap, { type FocusRequest } from "~/features/components/HeritageMap"; const VIDEO_ID = "3_zB4k1rcVk"; @@ -11,12 +12,11 @@ const Root = styled.div({ height: "100dvh", background: "#0e0e0e", color: "#f0f0f0", - fontFamily: "sans-serif", overflow: "hidden" }); const Sidebar = styled.div({ - width: 280, + width: 260, flexShrink: 0, display: "flex", flexDirection: "column", @@ -25,29 +25,29 @@ const Sidebar = styled.div({ }); const SidebarHeader = styled.div({ - padding: "20px 16px 12px", + padding: "18px 16px 12px", borderBottom: "1px solid #2a2a2a", flexShrink: 0 }); const SidebarTitle = styled.h1({ - fontSize: 14, + fontSize: 13, fontWeight: 700, letterSpacing: "0.08em", - color: "#ccc", + color: "#bbb", margin: 0 }); const SidebarCount = styled.p({ fontSize: 11, - color: "#666", + color: "#555", margin: "4px 0 0" }); const SiteList = styled.ul({ listStyle: "none", margin: 0, - padding: "8px 0", + padding: "6px 0", overflowY: "auto", flex: 1, scrollbarWidth: "thin", @@ -56,7 +56,7 @@ const SiteList = styled.ul({ const SiteItem = styled.li<{ active: boolean }>(({ active }) => ({ cursor: "pointer", - padding: "10px 16px", + padding: "9px 16px", borderLeft: active ? "3px solid #e0aa3e" : "3px solid transparent", background: active ? "rgba(224,170,62,0.08)" : "transparent", transition: "background 0.15s", @@ -67,91 +67,91 @@ const SiteItem = styled.li<{ active: boolean }>(({ active }) => ({ const SiteNameJa = styled.span<{ active: boolean }>(({ active }) => ({ display: "block", - fontSize: 13, + fontSize: 12, fontWeight: active ? 700 : 400, - color: active ? "#e0aa3e" : "#ddd", + color: active ? "#e0aa3e" : "#ccc", lineHeight: 1.4 })); const SiteNameEn = styled.span({ display: "block", - fontSize: 11, - color: "#666", - marginTop: 2 + fontSize: 10, + color: "#555", + marginTop: 1 }); -const Main = styled.div({ +const MapArea = styled.div({ flex: 1, - display: "flex", - flexDirection: "column", overflow: "hidden" }); -const PlayerArea = styled.div({ - flex: 1, - position: "relative", - background: "#000" -}); - -const IframeWrapper = styled.div({ - position: "absolute", - inset: 0 -}); - -const StyledIframe = styled.iframe({ - width: "100%", - height: "100%", - border: "none", - display: "block" -}); - -const EmptyState = styled.div({ - position: "absolute", - inset: 0, +const VideoPanel = styled.div({ + width: 420, + flexShrink: 0, display: "flex", flexDirection: "column", - alignItems: "center", - justifyContent: "center", - gap: 12 + borderLeft: "1px solid #2a2a2a", + background: "#0a0a0a" }); -const EmptyIcon = styled.div({ - fontSize: 48, - opacity: 0.3 +const VideoPanelHeader = styled.div({ + padding: "10px 14px", + borderBottom: "1px solid #1a1a1a", + display: "flex", + alignItems: "center", + gap: 8, + flexShrink: 0 }); -const EmptyText = styled.p({ - fontSize: 14, - color: "#555", - margin: 0 +const VideoNameJa = styled.span({ + fontSize: 15, + fontWeight: 700, + color: "#f0f0f0" }); -const InfoBar = styled.div({ - flexShrink: 0, - padding: "12px 24px", - borderTop: "1px solid #1a1a1a", - background: "#121212", - display: "flex", - alignItems: "baseline", - gap: 12 +const VideoNameEn = styled.span({ + fontSize: 11, + color: "#666", + marginLeft: 6 }); -const InfoNameJa = styled.span({ - fontSize: 18, - fontWeight: 700, - color: "#f0f0f0" +const CloseButton = styled.button({ + marginLeft: "auto", + background: "none", + border: "none", + color: "#555", + cursor: "pointer", + fontSize: 15, + padding: "2px 4px", + lineHeight: 1, + "&:hover": { color: "#ccc" } }); -const InfoNameEn = styled.span({ - fontSize: 13, - color: "#888" +const IframeWrapper = styled.div({ + flex: 1, + background: "#000", + "& iframe": { + width: "100%", + height: "100%", + border: "none", + display: "block" + } }); const buildEmbedUrl = (site: HeritageSite) => `https://www.youtube.com/embed/${VIDEO_ID}?start=${site.startSec}&autoplay=1`; const HeritageViewerScene = () => { - const [selected, setSelected] = useState(null); + const [focusRequest, setFocusRequest] = useState(null); + const [videoSite, setVideoSite] = useState(null); + + const handleListClick = useCallback((site: HeritageSite) => { + setFocusRequest({ site, id: Date.now() }); + }, []); + + const handlePlayVideo = useCallback((site: HeritageSite) => { + setVideoSite(site); + }, []); return ( @@ -162,13 +162,9 @@ const HeritageViewerScene = () => { {HERITAGE_SITES.map(site => { - const active = selected?.nameEn === site.nameEn; + const active = videoSite?.nameEn === site.nameEn; return ( - setSelected(site)} - > + handleListClick(site)}> {site.nameJa} {site.nameEn} @@ -176,32 +172,31 @@ const HeritageViewerScene = () => { })} -
- - {selected ? ( - - - - ) : ( - - 🌍 - 左のリストから場所を選んでください - - )} - - {selected && ( - - {selected.nameJa} - {selected.nameEn} - - )} -
+ + + + + + {videoSite && ( + + +
+ {videoSite.nameJa} + {videoSite.nameEn} +
+ setVideoSite(null)}>✕ +
+ +