/* ============ Function Map — IPCS スマート現場管理 × スマート BIM 機能マップ ============
   PC では SVG のリング図で表示し、スマホではカード形式の流れに切り替える。
   いずれのノードをクリックしてもモジュール説明のダイアログが開く。
   ========================================================================== */

const FM_MODULES = {
  entryCluster:{title:'入場と要員の把握',category:'午前｜入場管理',summary:'顔認証での入場、当日の入場者の確認、作業員数の把握を含みます。',desc:'作業員がゲートから入場すると、システムが入退場の記録を取得します。管理者はその時点の在場人数、入場している協力会社、職種の分布を確認できます。',features:['顔認証による入場記録','リアルタイムの在場人数','協力会社と職種の集計','日報と管理集計への反映'],pain:['手書きの受付は記入漏れや代筆が起きやすい。','現場の実人数と管理側のデータがずれる。','協力会社と職種の配置をその場で把握しにくい。'],related:['現場作業員管理','入退場ゲート連携','入退場者集計表'],type:'スマート現場管理モジュール'},
  safetyCluster:{title:'安全衛生教育と作業開始前の確認',category:'午前｜作業開始前の確認',summary:'当日の施工項目、作業リスク、安全上の注意事項を確認し、安全衛生教育を行います。',desc:'作業員は職種ごとの QR コードを読み取り、安全衛生教育の内容をオンラインで確認して署名できます。危険有害要因等の周知書の入力も可能です。',features:['施工項目とリスクの周知','職種ごとの QR コード','オンラインでの確認と署名','危険有害要因等の周知書をデジタル化'],pain:['紙の署名は保管が難しい。','教育を受けたかどうかの追跡に手間がかかる。','職種ごとの安全周知の内容が混同されやすい。'],related:['安全衛生管理','現場作業員管理','申請・承認'],type:'スマート現場管理モジュール'},
  workCluster:{title:'施工の実施',category:'日中｜施工の実施',summary:'協力会社と作業員が、当日予定された作業項目と施工区域で作業を開始します。',desc:'予定の工程に沿って、各作業項目の着手予定日、完了予定日、現在の実施状況を把握できます。危険度の高い作業について申請が済んでいるかも確認できます。',features:['作業項目と施工区域の対応','予定と実績の状態を把握','作業項目の実施状況を追跡','高リスク作業の申請状態を確認'],pain:['施工の現況と工程表がずれる。','高リスク作業の申請有無を確認しにくい。','各班の実際の作業範囲が把握しにくい。'],related:['現場作業員管理','安全衛生管理','ガントチャート'],type:'スマート現場管理モジュール'},
  checkCluster:{title:'検査・パトロール・設備の定期点検',category:'日中｜品質と安全の管理',summary:'工事検査、安全衛生パトロール、設備の定期点検を統合し、日中の現場管理の軸をつくります。',desc:'技術者は自主検査と段階別の検査を行えます。安全衛生の担当者はスマートフォンやタブレットでパトロールの帳票を入力できます。設備は QR コードを読み取って定期点検を行い、履歴も確認できます。',features:['工事検査と承認の記録','安全衛生パトロールと是正','設備の QR コードによる定期点検','写真・期限・再確認結果の保存'],pain:['検査、パトロール、設備の記録が別々に散らばる。','是正と担当者の追跡が難しい。','設備の検査証や点検の状態を把握しにくい。'],related:['工事検査','安全衛生管理','設備管理'],type:'スマート現場管理モジュール'},
  trackCluster:{title:'未対応事項・図面マークアップ・タスク追跡',category:'日中｜課題の追跡',summary:'現場で課題を見つけたら、未対応事項、図面へのマークアップ、会議の決定事項、追跡する作業項目として登録できます。',desc:'図面コラボレーション機能で図面上の位置を直接示し、その内容からタスクを割り当てて担当者と期限を設定します。通常の未対応事項や会議の決定事項もまとめて追跡できます。',features:['追跡する作業項目の登録','図面へのマークアップ','会議の決定事項をタスク化','未対応・期限超過・完了の状態'],pain:['現場の課題を口頭やチャットで伝えると抜けやすい。','図面上の位置を言葉で説明しにくい。','発生元の異なるタスクをまとめて追えない。'],related:['会議管理','図面コラボレーション','作業項目の追跡'],type:'管理・共通モジュール'},
  duskCluster:{title:'夕方の終業と工事日報',category:'夕方｜終業時の整理',summary:'顔認証での退場、終業ミーティング、翌日の施工割り当て、工事日報、出来高進捗率の集計を含みます。',desc:'退場の記録によって入退場のデータがそろいます。終業ミーティングでは翌日の施工項目、場所、担当する協力会社、予定人員を記録します。日報は複数人で入力でき、ゲートシステムから要員と機材の記録を自動で取り込めます。',features:['顔認証での退場と在場時間の集計','終業ミーティングと翌日の割り当て','複数人での日報入力','施工数量にもとづく出来高進捗率の算出'],pain:['翌日の段取りが口頭での引き継ぎになりがち。','日報で要員と機材を毎回入力し直している。','出来高進捗率の集計を手作業に頼っている。'],related:['会議管理','日報','ガントチャート'],type:'スマート現場管理モジュール'},
  nightCluster:{title:'夜間の残業と出面管理',category:'夜間｜残業要員',summary:'夜間の残業、出面、労働時間、出勤、労務費を把握します。',desc:'班や職種ごとの出面記録を管理し、出勤データと突き合わせることで、労務費と作業時間を明確にします。',features:['出面・労働時間の集計','夜勤班の出勤管理','労務費の管理','ゲートと日報の情報と連携可能'],pain:['夜間の残業時間が記録漏れになりやすい。','日雇いや臨時の要員のコストを集計しにくい。','協力会社の記録と現場の記録が食い違いやすい。'],related:['出面管理','現場作業員管理','工事日報'],type:'スマート現場管理モジュール'},
  bimCluster:{title:'スマート BIM と 3D の活用',category:'スマート BIM モジュール',summary:'建築管理 BIM、施工進捗 BIM、建築 3D GIS を含みます。',desc:'ブラウザ上で BIM モデル、部材の属性、設備の位置を確認できます。施工進捗と組み合わせ、モデルの部材を色分けして施工状態を表すこともできます。',features:['BIM モデルの閲覧','部材の属性検索','施工進捗 BIM の色分け','BIM と 3D GIS の統合'],pain:['現場の担当者が BIM 専用ソフトを直接使えない。','モデルの情報と現場のデータがつながらない。','工程表と実際の部材の位置を対照しにくい。'],related:['建築管理 BIM','施工進捗 BIM','建築 3D GIS'],type:'スマート BIM モジュール'},

  gate:{title:'顔認証で現場に入場',category:'午前｜入場管理',summary:'作業員が顔認証ゲートから入場し、実際の入場時刻、所属する協力会社、職種を記録します。',desc:'現場のゲートシステムと連携して入退場の記録を取得し、協力会社、職種、人数ごとに集計します。',features:['顔認証ゲートでの入場','入場時刻・協力会社・職種の記録','在場人数の集計','日報と管理集計への反映'],pain:['手書きの受付は記入漏れや代筆が起きやすい。','現場の実人数と管理データが一致しない。','協力会社と職種の配置をその場で把握しにくい。'],related:['現場作業員管理','入退場ゲート連携','入退場者集計表'],type:'スマート現場管理モジュール'},
  attendance:{title:'当日の入場者を確認',category:'午前｜現場の把握',summary:'現場の管理者が、当日の実際の入場人数、施工する協力会社、職種ごとの配置を確認します。',desc:'その時点の在場人数、入場している協力会社、職種の分布、入退場の状況を確認し、当日の実働人員を把握します。',features:['リアルタイムの在場人数','協力会社と職種の分布','入退場状況の検索','管理者がすばやく現場を把握'],pain:['管理者が現場の実人数をその場で把握できない。','外注と自社の要員をまとめて集計しにくい。','紙や Excel では更新が追いつかない。'],related:['現場作業員管理','入退場ゲート連携','工事日報'],type:'スマート現場管理モジュール'},
  safetyBriefing:{title:'施工項目の確認と安全衛生教育',category:'午前｜作業開始前の確認',summary:'当日の施工項目、作業リスク、安全上の注意事項について安全衛生教育を行います。',desc:'作業員に対して安全衛生教育を行います。職種ごとの QR コードを読み取り、内容をオンラインで確認して署名でき、危険有害要因等の周知書も入力できます。',features:['施工項目とリスクの周知','職種ごとの QR コード読み取り','オンラインでの確認と署名','危険有害要因等の周知書をデジタル化'],pain:['紙の周知と署名の保管が分散する。','新規入場者の教育完了を追跡しにくい。','職種ごとの安全周知の内容が混同されやすい。'],related:['安全衛生管理','現場作業員管理','申請・承認'],type:'スマート現場管理モジュール'},
  startWork:{title:'作業員が施工を開始',category:'日中｜施工の実施',summary:'協力会社と作業員が、当日予定された作業項目と施工区域で作業を開始します。',desc:'予定の工程に沿って各作業項目の着手予定日、完了予定日、現在の実施状況を把握し、危険度の高い作業の申請が済んでいるかも確認できます。',features:['予定された作業項目との対応','予定と実績の状態を把握','現在の実施状況を追跡','高リスク作業の申請状態を確認'],pain:['施工の現況と工程表がずれる。','高リスク作業の申請状態を確認しにくい。','各班の実際の作業範囲が把握しにくい。'],related:['現場作業員管理','安全衛生管理','ガントチャート'],type:'スマート現場管理モジュール'},
  inspection:{title:'工事検査の実施',category:'日中｜品質管理',summary:'現場の技術者が、当日の施工項目について自主検査、段階別の検査、品質の確認を行います。',desc:'工事検査の流れに沿って一次・二次・三次の検査を実施し、検査結果、写真、書類、承認の記録を残します。',features:['自主検査と段階別の検査','検査結果と写真の保存','書類と承認記録の管理','検査の段階と状態の追跡'],pain:['検査のデータが紙とチャットに散らばる。','検査結果、写真、承認記録がつながらない。','後から品質の経緯を調べるのに時間がかかる。'],related:['工事検査','工事検査表','申請・承認'],type:'スマート現場管理モジュール'},
  safetyPatrol:{title:'安全衛生パトロールの実施',category:'日中｜安全パトロール',summary:'安全衛生の担当者が、当日の施工項目と現場の作業状況に応じて安全パトロールを行います。',desc:'スマートフォンやタブレットでパトロールの帳票を入力し、点検結果、指摘内容、現場写真、是正期限、再確認の結果を記録します。',features:['スマホ／タブレットでの帳票入力','指摘内容と写真の記録','是正期限の追跡','再確認結果の管理'],pain:['パトロールの記録をその場で共有できない。','是正の状況を追跡しにくい。','写真と点検項目がひも付いていない。'],related:['安全衛生管理','安全衛生点検表','作業項目の追跡'],type:'スマート現場管理モジュール'},
  equipmentCheck:{title:'設備の定期点検',category:'日中｜設備の安全',summary:'安全衛生の担当者が、施工機械、設備、安全設備の定期点検を行います。',desc:'設備ごとの QR コードを読み取って定期点検を実施し、設備情報、過去の点検記録、現在の点検状態や認証の状態を確認できます。',features:['設備の QR コード読み取り','設備情報と履歴の検索','過去の点検記録','点検状態と検査証等の有効状況の把握'],pain:['設備情報と点検記録が別々に散らばる。','検査証等の有効期限をその場で確認しにくい。','設備の異常と保守の経緯をたどりにくい。'],related:['安全衛生管理','設備管理','建築 3D GIS'],type:'スマート現場管理モジュール'},
  todo:{title:'現場で未対応事項を発見',category:'日中｜課題の追跡',summary:'現場の管理者が、是正、調整、継続して追跡すべき事項を見つけたときに登録します。',desc:'追跡する作業項目を作成し、作業内容、担当者、期限、対応状態を設定して、完了まで継続して追跡します。',features:['追跡する作業項目の登録','担当者と期限の設定','対応状態の記録','完了まで追跡'],pain:['現場の未対応事項を口頭やチャットで伝えると抜けやすい。','担当者と期限がはっきりしない。','是正の経過に確認できる記録が残らない。'],related:['会議管理','図面コラボレーション','作業項目の追跡'],type:'管理・共通モジュール'},
  pdfMarkup:{title:'図面上で施工の課題を発見',category:'日中｜図面コラボレーション',summary:'技術者が PDF 図面上で施工の誤り、指摘事項、調整が必要な箇所を見つけます。',desc:'図面コラボレーションで図面上の位置を直接示し、その内容からタスクを割り当てて、担当者と期限を設定します。',features:['図面へのマークアップ','課題の位置を明示','マークアップからタスクを割り当て','担当者と期限の設定'],pain:['図面上の位置を言葉で説明しにくい。','注記、タスク、担当者が別々に管理される。','後からの追跡に図面上の根拠が残らない。'],related:['図面コラボレーション','作業項目の追跡','会議管理'],type:'管理・共通モジュール'},
  workMeeting:{title:'工務会議の開催',category:'日中｜調整会議',summary:'各部門が工事の進捗、施工の取り合い、品質、安全衛生、現場の課題について会議を行います。',desc:'会議の議題、出席者、議事録、関連資料を作成し、現場の各種会議の資料をまとめて保存します。',features:['議題と出席者の管理','議事録と添付資料の保存','決定事項からタスクを割り当て','作業項目の追跡と連動'],pain:['議事録と未対応事項が分かれている。','決定事項のその後の実行を追跡しにくい。','部門ごとに会議資料が散らばる。'],related:['会議管理','図面コラボレーション','作業項目の追跡'],type:'管理・共通モジュール'},
  taskFollow:{title:'現場タスクの実行状況を追跡',category:'午後｜追跡と管理',summary:'現場の管理者が、各部門、協力会社、担当者のタスクの対応状況を確認します。',desc:'通常の作業項目、会議の決定事項、図面コラボレーションから割り当てたタスクについて、未対応、対応中、期限超過、完了の状態をそれぞれ追跡します。',features:['通常の作業項目の追跡','会議の決定事項の追跡','図面コラボレーションのタスクの追跡','未対応・対応中・期限超過・完了の状態'],pain:['発生元の異なるタスクをまとめて把握しにくい。','期限超過をその場で知らせられない。','管理者が部門ごとの進捗を手作業でまとめている。'],related:['作業項目の追跡','会議管理','図面コラボレーション'],type:'管理・共通モジュール'},
  defectFollow:{title:'検査と安全衛生の是正を追跡',category:'午後｜是正の確認',summary:'当日の検査の進捗、不合格項目、安全衛生の指摘事項と是正状況を確認します。',desc:'検査案件の段階、承認状態、不合格項目、是正状況を確認できます。安全衛生の指摘事項については担当者、是正期限、写真、再確認結果、完了状態を確認できます。',features:['検査の段階と承認状態','不合格項目の追跡','安全衛生の指摘の担当者と期限','是正写真と再確認結果'],pain:['検査と安全衛生の指摘が別々に管理され、まとめにくい。','是正期限と担当者を追跡しにくい。','完了と判断する根拠がそろわない。'],related:['工事検査','安全衛生管理','作業項目の追跡'],type:'スマート現場管理モジュール'},
  leaveSite:{title:'顔認証で現場から退場',category:'夕方｜退場管理',summary:'作業員が当日の作業を終え、顔認証ゲートから退場します。',desc:'実際の退場時刻を記録して入退場のデータをそろえ、作業員の在場時間を集計します。',features:['顔認証での退場記録','入退場データがそろう','在場時間の集計','日報と出面データへの活用'],pain:['退場の記録がそろわない。','実際の在場時間を手作業で計算しにくい。','要員の集計と日報のデータが食い違いやすい。'],related:['現場作業員管理','入退場ゲート管理','日報'],type:'スマート現場管理モジュール'},
  closeMeeting:{title:'終業ミーティングの開催',category:'夕方｜終業時の整理',summary:'現場の管理者と各部門が、翌日に予定する施工項目を確認します。',desc:'終業ミーティングの記録を作成し、翌日の施工項目、施工場所、担当する協力会社、予定人員を記録します。決定事項から翌日の施工項目を割り当てることもできます。',features:['終業ミーティングの記録','翌日の施工項目の確認','担当する協力会社と要員の割り当て','翌日の施工計画として共有できる'],pain:['翌日の段取りが口頭での引き継ぎになりがち。','担当する協力会社と投入人員がはっきりしない。','当日の課題と翌日の段取りがつながらない。'],related:['会議管理','作業項目の追跡','ガントチャート'],type:'管理・共通モジュール'},
  dailyReport:{title:'工事日報と進捗の集計',category:'夕方｜日報と進捗',summary:'作業項目ごとの担当者が、当日の施工項目、施工数量、工事の進捗、現場の重要事項を入力します。',desc:'日報は複数人で編集でき、それぞれが担当する作業項目の施工内容と進捗実績を入力します。要員と機材の記録はゲートシステムから自動で取り込め、工事費内訳書と日々の施工数量にもとづいて出来高進捗率を算出します。',features:['複数人での日報入力','要員と機材のデータを自動取り込み','施工数量にもとづく出来高進捗率の算出','予定と実績の比較'],pain:['日報で要員と機材を毎回入力し直している。','出来高進捗率の集計を手作業に頼っている。','予定と実績の差をその場で把握しにくい。'],related:['日報','ガントチャート','入退場者集計表'],type:'スマート現場管理モジュール'},
  overtime:{title:'夜間の残業と出面管理',category:'夜間｜残業要員',summary:'出面、労働時間、出勤、労務費を管理し、夜間の残業と臨時の要員を把握します。',desc:'班や職種ごとの出面記録を管理し、出勤データと突き合わせることで、労務費と作業時間を明確にします。',features:['出面・労働時間の集計','夜勤班の出勤管理','労務費の管理','ゲートと日報の情報と連携可能'],pain:['夜間の残業時間が記録漏れになりやすい。','日雇いや臨時の要員のコストを集計しにくい。','協力会社の記録と現場の記録が食い違いやすい。'],related:['出面管理','現場作業員管理','工事日報'],type:'スマート現場管理モジュール'},

  dashboard:{title:'ダッシュボード',category:'管理側｜管理者の視点',summary:'視覚的な集計と分析、個人向けダッシュボードの作成、複数案件のデータ統合と共有の機能を提供します。',desc:'対象に応じたダッシュボードや複数案件の監視画面を作成でき、リンクを共有して他の方に見せることもできます。案件の進捗と管理指標をすばやく把握できます。',features:['個人向けダッシュボード','複数案件のデータ統合','年度と月での検索','ダッシュボードのリンク共有'],pain:['管理情報が複数の帳票に散らばる。','管理者が要点を見るまでに集計の手間がかかる。','役割ごとに見たい切り口が異なる。'],related:['入退場者集計','ガントチャート','工事日報'],type:'管理・共通モジュール'},
  gantt:{title:'ガントチャート',category:'管理側｜管理者の視点',summary:'案件の流れ、マイルストーン、工程上の作業項目を設定でき、メイン／サブのガントチャートと権限管理に対応します。',desc:'協力会社の進捗を確認でき、ガントチャート間のタスク関連と影響の通知を設定できます。会議管理や作業項目の追跡とも連動します。',features:['メインとサブのガントチャート','タスクの関連と影響の通知','協力会社の進捗管理','会議・作業項目の追跡と連動'],pain:['工程表と現場の実施状況が別々に管理される。','協力会社の進捗をまとめにくい。','遅れが他の作業に及ぼす影響が見えにくい。'],related:['カレンダー','会議管理','作業項目の追跡'],type:'管理・共通モジュール'},
  approval:{title:'申請・承認',category:'管理・共通モジュール',summary:'電子承認の仕組みを提供し、申請帳票、承認ワークフロー、帳票の管理を作成できます。',desc:'承認ワークフローは承認ステップを柔軟に設定でき、電子署名、独自の帳票、オンライン入力と組み合わせて、事務と工事の手続きをまとめて管理できます。',features:['電子承認','柔軟な承認ワークフロー','独自の帳票','電子署名とモバイル入力に対応'],pain:['紙の回覧は時間がかかり、状況も見えにくい。','案件ごとに手続きが異なり共用しにくい。','部門をまたぐやり取りで止まりやすい。'],related:['権限管理','安全衛生教育','ファイル管理'],type:'管理・共通モジュール'},
  file:{title:'ファイル管理',category:'管理・共通モジュール',summary:'視覚的に操作できるフォルダ画面、一括アップロード、自動バージョン管理、全文検索を提供します。',desc:'文書の版を自動で管理し、アップロード、改訂、削除の記録を残します。全文検索は DWG と DGN 図面内の文字にも対応し、PDF のモバイル閲覧とオンライン注記も利用できます。',features:['バージョン管理','全文検索','PDF のモバイル閲覧と注記','担当者の割り当てと注記の追跡'],pain:['文書の版が混乱し、誤った資料を使いやすい。','図面と添付資料を探すのに時間がかかる。','提出と再確認の手順に一貫性がない。'],related:['設計段階','竣工段階','成果物の提出'],type:'管理・共通モジュール'},
  permission:{title:'権限管理',category:'管理・共通モジュール',summary:'組織の職責に応じてグループを作成し、機能モジュール、案件、操作の権限を柔軟に設定できます。',desc:'案件をまたいだ個別の権限付与に対応し、同じ利用者が案件ごとに異なる役割の権限を持てます。管理の効率と情報の安全性を両立します。',features:['グループと役割の管理','案件ごとの個別権限','モジュール単位の権限制御','管理者・操作者・閲覧者に対応'],pain:['協力会社や部門ごとの権限を管理しにくい。','同じアカウントでも案件によって役割が異なる。','過剰な権限付与が情報セキュリティのリスクになる。'],related:['申請・承認','ファイル管理','ダッシュボード'],type:'管理・共通モジュール'},
  buildingBim:{title:'建築管理 BIM',category:'スマート BIM モジュール',summary:'BIM モデルの管理、モデルの閲覧、部材の属性検索、モデル情報の表示を提供します。',desc:'設計、施工、竣工、維持管理の各段階で利用できます。ブラウザ上で BIM モデル、階、設備、部材の属性を確認でき、書類や協業の情報と結び付けられます。',features:['モデルの閲覧','階／レイヤーの切り替え','部材の属性検索','書類と協業情報との連携'],pain:['現場の担当者が BIM 専用ソフトを直接使えない。','モデルの情報と現場のデータがつながらない。','書類と設備の位置を一緒に見る画面がない。'],related:['設計段階','施工段階','竣工段階','維持管理段階'],type:'スマート BIM モジュール'},
  progressBim:{title:'施工進捗 BIM',category:'スマート BIM モジュール',summary:'BIM モデルと施工進捗を組み合わせ、施工ステップの表示と工事進捗の管理を提供します。',desc:'鉄骨、カーテンウォール、その他の組み立て型の施工管理に適しています。進捗データに応じてモデルの部材を色分けし、現場と管理者が実際の施工状態をすばやく把握できます。',features:['モデルの進捗を色分け','部材と作業項目の対応','施工ステップの可視化','Excel の進捗データと併用可能'],pain:['従来の工程表は実際の部材と対照しにくい。','遅れている箇所と影響範囲が直感的に分からない。','会議と追跡に共通のビューがない。'],related:['施工段階','工事検査','ガントチャート'],type:'スマート BIM モジュール'},
  gis3d:{title:'建築 3D GIS',category:'スマート BIM モジュール',summary:'BIM と GIS を組み合わせ、建物の空間的な位置特定、モデル検索、空間情報の統合活用を提供します。',desc:'複数棟の建物や敷地、団地の一元管理、施設の維持管理に適しています。3D 地図の環境で建物と設備をすばやく特定でき、屋内外の情報を一体的に確認できます。',features:['BIM と GIS の統合','敷地と建物の位置特定','空間情報の統合','設備と維持管理への展開'],pain:['複数棟や団地全体を見渡す視点がない。','屋内モデルと屋外の空間情報が分かれている。','維持管理の担当者が設備の位置をすぐに特定できない。'],related:['維持管理段階','設備管理','建築管理 BIM'],type:'スマート BIM モジュール'}
};

/* 内側のリング：現場側 —— 日勤／夕方／夜勤で区切る */
/* labelAt：ラベルの角度は外側リング（管理側）のノード位置（45/90/135/225/270/315）を避けている */
const FM_OPS_BANDS = [
  { key:'day',   label:'日勤', from:335, to:205, labelAt:112.5 },
  { key:'dusk',  label:'夕方', from:205, to:275, labelAt:247.5 },
  { key:'night', label:'夜勤', from:275, to:335, labelAt:292.5 }
];
const FM_OPS_NODES = [
  { id:'entryCluster',  label:['入場','要員'], angle:350 },
  { id:'safetyCluster', label:['安全','教育'], angle: 35 },
  { id:'workCluster',   label:['施工','実施'], angle: 80 },
  { id:'checkCluster',  label:['検査','点検'], angle:125 },
  { id:'trackCluster',  label:['追跡','調整'], angle:170 },
  { id:'duskCluster',   label:['夕方','終業'], angle:240 },
  { id:'nightCluster',  label:['夜間','出面'], angle:305 }
];

/* 中間のリング：管理側 —— 上部は区画ラベル用に空ける */
const FM_MANAGE_RING = [
  { id:'dashboard',  label:'ダッシュボード', angle: 45 },
  { id:'gantt',      label:'ガントチャート', angle: 90 },
  { id:'approval',   label:'申請・承認',     angle:135 },
  { id:'taskFollow', label:'作業項目の追跡',     angle:225 },
  { id:'file',       label:'ファイル管理',   angle:270 },
  { id:'permission', label:'権限管理',       angle:315 }
];

/* 外側のリング：建物のライフサイクル */
const FM_STAGES = [
  { key:'plan',    label:'計画',     from:-36, to: 36, chips:[['ガントチャート','gantt'],['カレンダー','gantt']] },
  { key:'design',  label:'設計',     from: 36, to:108, chips:[['建築管理 BIM','buildingBim'],['ファイル管理','file']] },
  { key:'build',   label:'施工',     from:108, to:180, chips:[['建築管理 BIM','buildingBim'],['施工進捗 BIM','progressBim'],['工事検査','inspection'],['安全衛生パトロール','safetyPatrol']] },
  { key:'finish',  label:'竣工',     from:180, to:252, chips:[['建築管理 BIM','buildingBim'],['終業ミーティング','closeMeeting'],['日報の集計','dailyReport'],['ファイル管理','file']] },
  { key:'operate', label:'維持管理', from:252, to:324, chips:[['建築管理 BIM','buildingBim'],['建築 3D GIS','gis3d'],['設備管理','equipmentCheck']] }
];

/* スマホ版：カード形式の流れ */
const FM_MOBILE = [
  { title:'現場側｜日勤 · 入場と作業開始前', tone:'day', items:['gate','attendance','safetyBriefing'] },
  { title:'現場側｜日勤 · 施工、検査、パトロールと追跡', tone:'day', items:['startWork','inspection','safetyPatrol','equipmentCheck','todo','pdfMarkup','workMeeting','taskFollow','defectFollow'] },
  { title:'現場側｜夕方 · 退場、終業ミーティングと日報', tone:'dusk', items:['leaveSite','closeMeeting','dailyReport'] },
  { title:'現場側｜夜勤 · 残業要員の把握', tone:'night', items:['overtime'] },
  { title:'管理側｜管理者の視点', tone:'manage', items:['dashboard','gantt','approval','file','permission'] },
  { title:'建物のライフサイクル｜スマート BIM', tone:'bim', items:['buildingBim','progressBim','gis3d'] }
];

/* 下部のモジュール索引 */
const FM_INDEX = [
  { title:'現場業務の流れ', items:['gate','attendance','safetyBriefing','startWork','inspection','safetyPatrol','equipmentCheck','todo','pdfMarkup','workMeeting','taskFollow','defectFollow','leaveSite','closeMeeting','dailyReport','overtime'] },
  { title:'管理側・共通モジュール', items:['dashboard','gantt','approval','file','permission'] },
  { title:'スマート BIM モジュール', items:['buildingBim','progressBim','gis3d'] }
];

const FM_C = { x: 600, y: 470 };
const fmPolar = (r, deg) => {
  const rad = (deg - 90) * Math.PI / 180;
  return { x: FM_C.x + r * Math.cos(rad), y: FM_C.y + r * Math.sin(rad) };
};
const fmWedge = (r1, r2, from, to) => {
  let s = from, e = to;
  if (e < s) e += 360;
  const p1 = fmPolar(r2, s), p2 = fmPolar(r2, e), p3 = fmPolar(r1, e), p4 = fmPolar(r1, s);
  const large = (e - s) > 180 ? 1 : 0;
  return `M ${p1.x} ${p1.y} A ${r2} ${r2} 0 ${large} 1 ${p2.x} ${p2.y} L ${p3.x} ${p3.y} A ${r1} ${r1} 0 ${large} 0 ${p4.x} ${p4.y} Z`;
};

function FunctionMap({ open, onClose }) {
  const { useState: uS, useEffect: uE, useRef: uR } = React;
  const [active, setActive] = uS(null);
  const scrollRef = uR(null);

  // 開いている間は背面のスクロールを止め、スクロール位置を先頭に戻す
  uE(() => {
    if (!open) return;
    document.body.style.overflow = 'hidden';
    document.body.classList.add('is-pd-open');
    if (scrollRef.current) scrollRef.current.scrollTop = 0;
    return () => {
      document.body.style.overflow = '';
      document.body.classList.remove('is-pd-open');
    };
  }, [open]);

  // Esc：まずモジュールのダイアログを閉じ、次にページ全体を閉じる
  uE(() => {
    if (!open) return;
    const h = (e) => {
      if (e.key !== 'Escape') return;
      if (active) setActive(null); else onClose();
    };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [open, active, onClose]);

  const mod = active ? FM_MODULES[active] : null;

  return (
    <div className={`lx-fm ${open ? 'is-open' : ''}`} ref={scrollRef}>
      <div className="lx-pd-head">
        <button className="lx-pd-close" onClick={onClose}>
          <span>← CLOSE</span>
          <span style={{ color:'var(--lx-ink-4)', fontWeight:400, letterSpacing:'1px' }}>ESC</span>
        </button>
        <div className="lx-pd-crumb">FUNCTION MAP &nbsp;/&nbsp; <span>機能マップ</span></div>
      </div>

      <header className="lx-fm-hero">
        <span className="lx-eyebrow">IPCS スマート現場管理 × スマート BIM プラットフォーム</span>
        <h1>現場の 1 日が、そのまま機能マップになる</h1>
        <p>
          現場側の 1 日、管理側の視点、建物のライフサイクルという 3 つの角度から、
          IPCS とスマート BIM がそれぞれの場面で果たす役割を示します。
          いずれかの項目をクリックすると、機能の説明、特長、解決できる現場の課題を確認できます。
        </p>
        <div className="lx-fm-legend">
          <span><i className="is-site" />現場側｜日勤 · 夕方 · 夜勤</span>
          <span><i className="is-manage" />管理側｜管理者の視点</span>
          <span><i className="is-stage" />建物のライフサイクル</span>
        </div>
      </header>

      {/* ---------- PC：リング図 ---------- */}
      <div className="lx-fm-ring">
        <svg viewBox="170 40 860 860" role="img" aria-label="IPCS 機能マップ">
          {/* 外側リング：ライフサイクル */}
          {FM_STAGES.map((s) => {
            const mid = (s.from + s.to) / 2;
            const p = fmPolar(378, mid);
            return (
              <g key={s.key} className="lx-fm-stage">
                <path d={fmWedge(340, 416, s.from, s.to)} />
                <text x={p.x} y={p.y + 7}>{s.label}</text>
              </g>
            );
          })}

          {/* 管理側リング：帯 + ラベル */}
          <circle className="lx-fm-band is-manage" cx={FM_C.x} cy={FM_C.y} r={292} />
          <text className="lx-fm-bandlabel is-manage" x={FM_C.x} y={fmPolar(292, 0).y + 6}>管理側</text>

          {/* 現場側リング：日勤／夕方／夜勤の 3 区分 */}
          {FM_OPS_BANDS.map((band) => {
            const p = fmPolar(235, band.labelAt);
            return (
              <g key={band.key} className={`lx-fm-opsband is-${band.key}`}>
                <path d={fmWedge(218, 252, band.from, band.to)} />
                <text x={p.x} y={p.y + 6}>{band.label}</text>
              </g>
            );
          })}

          {/* 管理側のノード */}
          {FM_MANAGE_RING.map((n) => {
            const p = fmPolar(292, n.angle);
            return (
              <g key={n.id} className="lx-fm-node is-manage" onClick={() => setActive(n.id)} role="button" tabIndex={0}
                onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setActive(n.id); } }}>
                <rect x={p.x - 66} y={p.y - 19} width="132" height="38" rx="19" />
                <text x={p.x} y={p.y + 5}>{n.label}</text>
              </g>
            );
          })}

          {/* 現場側のノード */}
          {FM_OPS_NODES.map((n) => {
            const p = fmPolar(165, n.angle);
            return (
              <g key={n.id} className="lx-fm-node is-site"
                onClick={() => setActive(n.id)} role="button" tabIndex={0}
                onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setActive(n.id); } }}>
                <circle cx={p.x} cy={p.y} r="46" />
                <text x={p.x} y={p.y - 3}>
                  <tspan x={p.x}>{n.label[0]}</tspan>
                  <tspan x={p.x} dy="19">{n.label[1]}</tspan>
                </text>
              </g>
            );
          })}

          {/* 中心 */}
          <g className="lx-fm-core">
            <circle cx={FM_C.x} cy={FM_C.y} r="96" />
            <text x={FM_C.x} y={FM_C.y - 10}>
              <tspan x={FM_C.x}>IPCS スマート現場</tspan>
              <tspan x={FM_C.x} dy="26">×</tspan>
              <tspan x={FM_C.x} dy="26">スマート BIM</tspan>
            </text>
          </g>
        </svg>

        <div className="lx-fm-stagecards">
          {FM_STAGES.map((s) =>
            <div key={s.key} className="lx-fm-stagecard">
              <h4>{s.label}段階</h4>
              <div className="lx-fm-chips">
                {s.chips.map(([label, id], i) =>
                  <button key={i} onClick={() => setActive(id)}>{label}</button>
                )}
              </div>
            </div>
          )}
        </div>
      </div>

      {/* ---------- スマホ：カード形式 ---------- */}
      <div className="lx-fm-cards">
        {FM_MOBILE.map((g) =>
          <section key={g.title} className={`lx-fm-cardgroup is-${g.tone}`}>
            <h3>{g.title}</h3>
            <div className="lx-fm-cardlist">
              {g.items.map((id) => {
                const m = FM_MODULES[id];
                if (!m) return null;
                return (
                  <button key={id} className="lx-fm-card" onClick={() => setActive(id)}>
                    <span className="lx-fm-card-cat">{m.category}</span>
                    <span className="lx-fm-card-title">{m.title}</span>
                    <span className="lx-fm-card-sum">{m.summary}</span>
                  </button>
                );
              })}
            </div>
          </section>
        )}
      </div>

      {/* ---------- モジュール索引 ---------- */}
      <div className="lx-fm-index">
        {FM_INDEX.map((g) =>
          <section key={g.title}>
            <h3>{g.title}</h3>
            <div className="lx-fm-chips">
              {g.items.map((id) =>
                FM_MODULES[id] ? <button key={id} onClick={() => setActive(id)}>{FM_MODULES[id].title}</button> : null
              )}
            </div>
          </section>
        )}
      </div>

      {/* ---------- モジュール説明のダイアログ ---------- */}
      {mod &&
        <div className="lx-fm-modal" onClick={(e) => { if (e.target === e.currentTarget) setActive(null); }}>
          <div className="lx-fm-modal-panel" role="dialog" aria-modal="true" aria-label={mod.title}>
            <button className="lx-fm-modal-x" onClick={() => setActive(null)} aria-label="閉じる">×</button>
            <div className="lx-fm-modal-kicker">{mod.category}<em>{mod.type}</em></div>
            <h2>{mod.title}</h2>
            <p className="lx-fm-modal-sum">{mod.summary}</p>
            <p className="lx-fm-modal-desc">{mod.desc}</p>
            <div className="lx-fm-modal-grid">
              <div>
                <h4>主な特長</h4>
                <ul>{mod.features.map((f, i) => <li key={i}>{f}</li>)}</ul>
              </div>
              <div className="is-pain">
                <h4>解決できる課題</h4>
                <ul>{mod.pain.map((f, i) => <li key={i}>{f}</li>)}</ul>
              </div>
            </div>
            <div className="lx-fm-modal-related">
              <h4>関連モジュール</h4>
              <div className="lx-fm-chips">
                {mod.related.map((r, i) => <span key={i}>{r}</span>)}
              </div>
            </div>
          </div>
        </div>
      }
    </div>
  );
}

window.FunctionMap = FunctionMap;
